001/* 002 * Copyright 2012 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.plugins.linkdirectory.repository; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.time.ZonedDateTime; 021 022import javax.jcr.Node; 023import javax.jcr.PathNotFoundException; 024import javax.jcr.RepositoryException; 025import javax.jcr.Value; 026 027import org.apache.commons.lang3.ArrayUtils; 028import org.apache.commons.lang3.StringUtils; 029 030import org.ametys.cms.data.Binary; 031import org.ametys.plugins.linkdirectory.Link; 032import org.ametys.plugins.repository.AmetysObject; 033import org.ametys.plugins.repository.AmetysRepositoryException; 034import org.ametys.plugins.repository.MovableAmetysObject; 035import org.ametys.plugins.repository.RepositoryConstants; 036import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 037import org.ametys.plugins.repository.data.ametysobject.ModifiableModelLessDataAwareAmetysObject; 038import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 039import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 040import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 041import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 042import org.ametys.web.repository.SiteAwareAmetysObject; 043import org.ametys.web.repository.site.Site; 044 045/** 046 * Repository implementation of a directory link. 047 */ 048public class DefaultLink extends DefaultTraversableAmetysObject<DefaultLinkFactory> implements Link, SiteAwareAmetysObject, MovableAmetysObject, ModifiableModelLessDataAwareAmetysObject 049{ 050 /** Constant for URL property. */ 051 public static final String PROPERTY_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url"; 052 053 /** Constant for id of provider of dynamic information. */ 054 public static final String PROPERTY_DYNAMIC_INFO_PROVIDER = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":dynamic-information"; 055 056 /** Constant for internal URL property. */ 057 public static final String PROPERTY_INTERNAL_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":internal-url"; 058 059 /** Constant for URL type property. */ 060 public static final String PROPERTY_URLTYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-type"; 061 062 /** Constant for title property. */ 063 public static final String PROPERTY_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title"; 064 065 /** Constant for title property. */ 066 public static final String PROPERTY_CONTENT = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":content"; 067 068 /** Constant for URL alternative property. */ 069 public static final String PROPERTY_URL_ALTERNATIVE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-alternative"; 070 071 /** Constant for picture type property. */ 072 public static final String PROPERTY_PICTURE_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-type"; 073 074 /** Constant for picture data property. */ 075 public static final String PROPERTY_PICTURE = "picture"; 076 077 /** Constant for picture id property. */ 078 public static final String PROPERTY_PICTURE_ID = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-id"; 079 080 /** Constant for picture glyph property. */ 081 public static final String PROPERTY_PICTURE_GLYPH = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-glyph"; 082 083 /** Constant for picture alternative property. */ 084 public static final String PROPERTY_PICTURE_ALTERNATIVE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-alternative"; 085 086 /** Constant for themes property. */ 087 public static final String PROPERTY_THEMES = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":themes"; 088 089 /** Constant for color property. */ 090 public static final String PROPERTY_COLOR = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":color"; 091 092 /** Constant for page property. */ 093 public static final String PROPERTY_PAGE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":page"; 094 095 /** Constant for status property. */ 096 public static final String PROPERTY_STATUS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":status"; 097 098 /** Constant for default visibilty property. */ 099 public static final String PROPERTY_DEFAULT_VISIBILITY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":default-visibility"; 100 101 102 /** 103 * Create a {@link DefaultLink}. 104 * 105 * @param node the node backing this {@link AmetysObject}. 106 * @param parentPath the parent path in the Ametys hierarchy. 107 * @param factory the {@link DefaultLinkFactory} which creates the 108 * AmetysObject. 109 */ 110 public DefaultLink(Node node, String parentPath, DefaultLinkFactory factory) 111 { 112 super(node, parentPath, factory); 113 } 114 115 @Override 116 public String getUrl() throws AmetysRepositoryException 117 { 118 try 119 { 120 return getNode().getProperty(PROPERTY_URL).getString(); 121 } 122 catch (PathNotFoundException e) 123 { 124 return null; 125 } 126 catch (RepositoryException e) 127 { 128 throw new AmetysRepositoryException("Error getting the URL property.", e); 129 } 130 } 131 132 @Override 133 public void setUrl(LinkType urlType, String url) throws AmetysRepositoryException 134 { 135 try 136 { 137 getNode().setProperty(PROPERTY_URLTYPE, urlType.toString()); 138 getNode().setProperty(PROPERTY_URL, url); 139 } 140 catch (RepositoryException e) 141 { 142 throw new AmetysRepositoryException("Error setting the URL property.", e); 143 } 144 } 145 146 @Override 147 public String getInternalUrl() throws AmetysRepositoryException 148 { 149 try 150 { 151 return getNode().getProperty(PROPERTY_INTERNAL_URL).getString(); 152 } 153 catch (PathNotFoundException e) 154 { 155 return null; 156 } 157 catch (RepositoryException e) 158 { 159 throw new AmetysRepositoryException("Error getting the internal URL property.", e); 160 } 161 } 162 163 @Override 164 public void setInternalUrl(String url) throws AmetysRepositoryException 165 { 166 try 167 { 168 getNode().setProperty(PROPERTY_INTERNAL_URL, url); 169 } 170 catch (RepositoryException e) 171 { 172 throw new AmetysRepositoryException("Error setting the internal URL property.", e); 173 } 174 } 175 176 @Override 177 public LinkType getUrlType() throws AmetysRepositoryException 178 { 179 try 180 { 181 return LinkType.valueOf(getNode().getProperty(PROPERTY_URLTYPE).getString()); 182 } 183 catch (RepositoryException e) 184 { 185 throw new AmetysRepositoryException("Unable to get URL type property", e); 186 } 187 } 188 189 @Override 190 public String getTitle() throws AmetysRepositoryException 191 { 192 try 193 { 194 return getNode().getProperty(PROPERTY_TITLE).getString(); 195 } 196 catch (PathNotFoundException e) 197 { 198 return null; 199 } 200 catch (RepositoryException e) 201 { 202 throw new AmetysRepositoryException("Error getting the title property.", e); 203 } 204 } 205 206 @Override 207 public void setTitle(String title) throws AmetysRepositoryException 208 { 209 try 210 { 211 getNode().setProperty(PROPERTY_TITLE, title); 212 } 213 catch (RepositoryException e) 214 { 215 throw new AmetysRepositoryException("Error setting the title property.", e); 216 } 217 } 218 219 @Override 220 public String getContent() throws AmetysRepositoryException 221 { 222 try 223 { 224 return getNode().getProperty(PROPERTY_CONTENT).getString(); 225 } 226 catch (PathNotFoundException e) 227 { 228 return null; 229 } 230 catch (RepositoryException e) 231 { 232 throw new AmetysRepositoryException("Error getting the content property.", e); 233 } 234 } 235 236 @Override 237 public void setContent(String content) throws AmetysRepositoryException 238 { 239 try 240 { 241 getNode().setProperty(PROPERTY_CONTENT, content); 242 } 243 catch (RepositoryException e) 244 { 245 throw new AmetysRepositoryException("Error setting the content property.", e); 246 } 247 } 248 249 @Override 250 public String getAlternative() throws AmetysRepositoryException 251 { 252 try 253 { 254 return getNode().getProperty(PROPERTY_URL_ALTERNATIVE).getString(); 255 } 256 catch (PathNotFoundException e) 257 { 258 return null; 259 } 260 catch (RepositoryException e) 261 { 262 throw new AmetysRepositoryException("Error getting the alternative property.", e); 263 } 264 } 265 266 @Override 267 public void setAlternative(String alternative) throws AmetysRepositoryException 268 { 269 try 270 { 271 getNode().setProperty(PROPERTY_URL_ALTERNATIVE, alternative); 272 } 273 catch (RepositoryException e) 274 { 275 throw new AmetysRepositoryException("Error setting the alternative property.", e); 276 } 277 } 278 279 @Override 280 public Binary getExternalPicture() throws AmetysRepositoryException 281 { 282 return getValue(PROPERTY_PICTURE); 283 } 284 285 @Override 286 public void setExternalPicture(String mimeType, String filename, InputStream stream) throws AmetysRepositoryException 287 { 288 try 289 { 290 removePictureMetas(); 291 292 setPictureType("external"); 293 294 Binary pic = new Binary(); 295 296 pic.setLastModificationDate(ZonedDateTime.now()); 297 pic.setInputStream(stream); 298 pic.setMimeType(mimeType); 299 pic.setFilename(filename); 300 301 setValue(PROPERTY_PICTURE, pic); 302 } 303 catch (IOException | RepositoryException e) 304 { 305 throw new AmetysRepositoryException("Error setting the external picture property.", e); 306 } 307 } 308 309 @Override 310 public String getResourcePictureId() throws AmetysRepositoryException 311 { 312 try 313 { 314 return getNode().getProperty(PROPERTY_PICTURE_ID).getString(); 315 } 316 catch (PathNotFoundException e) 317 { 318 return null; 319 } 320 catch (RepositoryException e) 321 { 322 throw new AmetysRepositoryException("Error getting the picture ID property.", e); 323 } 324 } 325 326 @Override 327 public void setResourcePicture(String resourceId) throws AmetysRepositoryException 328 { 329 try 330 { 331 removePictureMetas(); 332 333 setPictureType("resource"); 334 335 getNode().setProperty(PROPERTY_PICTURE_ID, resourceId); 336 } 337 catch (RepositoryException e) 338 { 339 throw new AmetysRepositoryException("Error setting the alternative property.", e); 340 } 341 } 342 343 @Override 344 public void setNoPicture() throws AmetysRepositoryException 345 { 346 try 347 { 348 setPictureType(""); 349 350 removePictureMetas(); 351 } 352 catch (RepositoryException e) 353 { 354 throw new AmetysRepositoryException("Error setting the alternative property.", e); 355 } 356 } 357 358 @Override 359 public String getPictureType() throws AmetysRepositoryException 360 { 361 try 362 { 363 return getNode().getProperty(PROPERTY_PICTURE_TYPE).getString(); 364 } 365 catch (RepositoryException e) 366 { 367 throw new AmetysRepositoryException("Error getting the type property.", e); 368 } 369 } 370 371 @Override 372 public void setPictureType(String type) throws AmetysRepositoryException 373 { 374 try 375 { 376 getNode().setProperty(PROPERTY_PICTURE_TYPE, type); 377 } 378 catch (RepositoryException e) 379 { 380 throw new AmetysRepositoryException("Error setting the type property.", e); 381 } 382 } 383 384 public String getPictureGlyph() throws AmetysRepositoryException 385 { 386 try 387 { 388 return getNode().getProperty(PROPERTY_PICTURE_GLYPH).getString(); 389 } 390 catch (RepositoryException e) 391 { 392 throw new AmetysRepositoryException("Error getting the picture glyph property.", e); 393 } 394 } 395 396 public void setPictureGlyph(String glyph) throws AmetysRepositoryException 397 { 398 try 399 { 400 setPictureType("glyph"); 401 402 getNode().setProperty(PROPERTY_PICTURE_GLYPH, glyph); 403 } 404 catch (RepositoryException e) 405 { 406 throw new AmetysRepositoryException("Error setting the picture glyph property.", e); 407 } 408 } 409 410 @Override 411 public String getPictureAlternative() throws AmetysRepositoryException 412 { 413 try 414 { 415 return getNode().getProperty(PROPERTY_PICTURE_ALTERNATIVE).getString(); 416 } 417 catch (PathNotFoundException e) 418 { 419 return null; 420 } 421 catch (RepositoryException e) 422 { 423 throw new AmetysRepositoryException("Error getting the alternative property.", e); 424 } 425 } 426 427 @Override 428 public void setPictureAlternative(String alternative) throws AmetysRepositoryException 429 { 430 try 431 { 432 getNode().setProperty(PROPERTY_PICTURE_ALTERNATIVE, alternative); 433 } 434 catch (RepositoryException e) 435 { 436 throw new AmetysRepositoryException("Error setting the alternative property.", e); 437 } 438 } 439 440 @Override 441 public String[] getThemes() throws AmetysRepositoryException 442 { 443 return _getListFromMetadataName(PROPERTY_THEMES); 444 } 445 446 @Override 447 public void setThemes(String[] themes) throws AmetysRepositoryException 448 { 449 _setListWithMetadataName(themes, PROPERTY_THEMES); 450 } 451 452 @Override 453 public void removeTheme(String themeId) throws AmetysRepositoryException 454 { 455 try 456 { 457 String[] themes = getThemes(); 458 String[] updatedThemes = ArrayUtils.removeElement(themes, themeId); 459 getNode().setProperty(PROPERTY_THEMES, updatedThemes); 460 } 461 catch (RepositoryException e) 462 { 463 throw new AmetysRepositoryException("Error removing theme of id " + themeId, e); 464 } 465 } 466 467 @Override 468 public Site getSite() throws AmetysRepositoryException 469 { 470 AmetysObject parent = getParent(); 471 while (parent != null) 472 { 473 if (parent instanceof Site) 474 { 475 return (Site) parent; 476 } 477 parent = parent.getParent(); 478 } 479 480 return null; 481 } 482 483 @Override 484 public String getSiteName() throws AmetysRepositoryException 485 { 486 return getSite().getName(); 487 } 488 489 /** 490 * Get the theme language. 491 * 492 * @return the theme language. 493 */ 494 public String getLanguage() 495 { 496 return getParent().getParent().getName(); 497 } 498 499 /** 500 * Remove all picture metas (picture ID and picture binary). 501 * 502 * @throws RepositoryException if an error occurs. 503 */ 504 protected void removePictureMetas() throws RepositoryException 505 { 506 getNode().setProperty(PROPERTY_PICTURE_ID, StringUtils.EMPTY); 507 removeValue(PROPERTY_PICTURE); 508 } 509 510 @Override 511 public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException 512 { 513 if (siblingObject instanceof DefaultLink) 514 { 515 DefaultLink sibling = (DefaultLink) siblingObject; 516 sibling.getPath(); 517 Node node = getNode(); 518 String siblingPath = ""; 519 try 520 { 521 siblingPath = sibling.getNode().getPath(); 522 if (siblingPath.contains("/")) 523 { 524 siblingPath = StringUtils.substringAfterLast(siblingPath, "/"); 525 } 526 String path = node.getPath(); 527 if (path.contains("/")) 528 { 529 path = StringUtils.substringAfterLast(path, "/"); 530 } 531 node.getParent().orderBefore(path, siblingPath); 532 } 533 catch (RepositoryException e) 534 { 535 throw new AmetysRepositoryException(String.format("Unable to order link '%s' before link '%s'", this, siblingPath), e); 536 } 537 } 538 else 539 { 540 throw new AmetysRepositoryException("A link can only be moved before another link."); 541 } 542 } 543 544 @Override 545 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 546 { 547 if (getParent().equals(newParent)) 548 { 549 try 550 { 551 // Just order current node to the end. 552 Node node = getNode(); 553 String path = node.getPath(); 554 if (path.contains("/")) 555 { 556 path = StringUtils.substringAfterLast(path, "/"); 557 } 558 node.getParent().orderBefore(path, null); 559 } 560 catch (RepositoryException e) 561 { 562 throw new AmetysRepositoryException(String.format("Unable to move link '%s' outside the root link node.", this), e); 563 } 564 } 565 else 566 { 567 throw new AmetysRepositoryException("A link can only be moved before another link."); 568 } 569 } 570 571 @Override 572 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 573 { 574 return getParent().equals(newParent); 575 } 576 577 /** 578 * Retrieves the list of values corresponding to the metadata name passed as 579 * parameter 580 * 581 * @param metadataName the name of the metadata to retrieve 582 * @return the list corresponding to the metadata name 583 */ 584 private String[] _getListFromMetadataName(String metadataName) 585 { 586 try 587 { 588 if (getNode().hasProperty(metadataName)) 589 { 590 Value[] values = getNode().getProperty(metadataName).getValues(); 591 592 String[] list = new String[values.length]; 593 594 for (int i = 0; i < values.length; i++) 595 { 596 list[i] = values[i].getString(); 597 } 598 599 return list; 600 } 601 else 602 { 603 return new String[0]; 604 } 605 } 606 catch (RepositoryException e) 607 { 608 throw new AmetysRepositoryException("An error occurred while trying to get the property '" + metadataName + "'.", e); 609 } 610 } 611 612 /** 613 * Sets the list of values to the node corresponding to the metadata name 614 * passed as a parameter 615 * 616 * @param list the list of value to be set 617 * @param metadataName the name of the metadata 618 */ 619 private void _setListWithMetadataName(String[] list, String metadataName) 620 { 621 try 622 { 623 getNode().setProperty(metadataName, list); 624 } 625 catch (RepositoryException e) 626 { 627 throw new AmetysRepositoryException("An error occurred while trying to set the property '" + metadataName + "'.", e); 628 } 629 } 630 631 @Override 632 public String getDynamicInformationProvider() throws AmetysRepositoryException 633 { 634 try 635 { 636 return getNode().getProperty(PROPERTY_DYNAMIC_INFO_PROVIDER).getString(); 637 } 638 catch (PathNotFoundException e) 639 { 640 return null; 641 } 642 catch (RepositoryException e) 643 { 644 throw new AmetysRepositoryException("Error getting the URL property.", e); 645 } 646 } 647 648 @Override 649 public void setDynamicInformationProvider(String providerId) throws AmetysRepositoryException 650 { 651 try 652 { 653 getNode().setProperty(PROPERTY_DYNAMIC_INFO_PROVIDER, providerId); 654 } 655 catch (RepositoryException e) 656 { 657 throw new AmetysRepositoryException("Error setting the URL property.", e); 658 } 659 } 660 661 @Override 662 public String getColor() throws AmetysRepositoryException 663 { 664 try 665 { 666 return getNode().getProperty(PROPERTY_COLOR).getString(); 667 } 668 catch (PathNotFoundException e) 669 { 670 return null; 671 } 672 catch (RepositoryException e) 673 { 674 throw new AmetysRepositoryException("Error getting the color property.", e); 675 } 676 } 677 678 @Override 679 public void setColor(String color) throws AmetysRepositoryException 680 { 681 try 682 { 683 getNode().setProperty(PROPERTY_COLOR, color); 684 } 685 catch (RepositoryException e) 686 { 687 throw new AmetysRepositoryException("Error setting the color property.", e); 688 } 689 } 690 691 @Override 692 public String getPage() throws AmetysRepositoryException 693 { 694 try 695 { 696 return getNode().getProperty(PROPERTY_PAGE).getString(); 697 } 698 catch (PathNotFoundException e) 699 { 700 return null; 701 } 702 catch (RepositoryException e) 703 { 704 throw new AmetysRepositoryException("Error getting the page property.", e); 705 } 706 } 707 708 @Override 709 public void setPage(String page) throws AmetysRepositoryException 710 { 711 try 712 { 713 getNode().setProperty(PROPERTY_PAGE, page); 714 } 715 catch (RepositoryException e) 716 { 717 throw new AmetysRepositoryException("Error setting the page property.", e); 718 } 719 } 720 721 @Override 722 public LinkStatus getStatus() throws AmetysRepositoryException 723 { 724 try 725 { 726 return LinkStatus.valueOf(getNode().getProperty(PROPERTY_STATUS).getString()); 727 } 728 catch (PathNotFoundException e) 729 { 730 return null; 731 } 732 catch (RepositoryException e) 733 { 734 throw new AmetysRepositoryException("Error getting the status property.", e); 735 } 736 } 737 738 @Override 739 public void setStatus(LinkStatus status) throws AmetysRepositoryException 740 { 741 try 742 { 743 getNode().setProperty(PROPERTY_STATUS, status.name()); 744 } 745 catch (RepositoryException e) 746 { 747 throw new AmetysRepositoryException("Error setting the status property.", e); 748 } 749 } 750 751 @Override 752 public LinkVisibility getDefaultVisibility() throws AmetysRepositoryException 753 { 754 try 755 { 756 Node node = getNode(); 757 if (node.hasProperty(PROPERTY_DEFAULT_VISIBILITY)) 758 { 759 return LinkVisibility.valueOf(getNode().getProperty(PROPERTY_DEFAULT_VISIBILITY).getString()); 760 } 761 else 762 { 763 return LinkVisibility.VISIBLE; 764 } 765 } 766 catch (RepositoryException e) 767 { 768 throw new AmetysRepositoryException("Error getting the visibility property.", e); 769 } 770 } 771 772 @Override 773 public void setDefaultVisibility(LinkVisibility visibility) throws AmetysRepositoryException 774 { 775 try 776 { 777 getNode().setProperty(PROPERTY_DEFAULT_VISIBILITY, visibility.name()); 778 } 779 catch (RepositoryException e) 780 { 781 throw new AmetysRepositoryException("Error setting the visibility property.", e); 782 } 783 } 784 785 public ModifiableModelLessDataHolder getDataHolder() 786 { 787 JCRRepositoryData repositoryData = new JCRRepositoryData(getNode()); 788 return new DefaultModifiableModelLessDataHolder(_getFactory().getLinkDataTypeExtensionPoint(), repositoryData); 789 } 790}