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