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 removeValue(PROPERTY_PICTURE); 505 } 506 507 @Override 508 public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException 509 { 510 if (siblingObject instanceof DefaultLink) 511 { 512 DefaultLink sibling = (DefaultLink) siblingObject; 513 sibling.getPath(); 514 Node node = getNode(); 515 String siblingPath = ""; 516 try 517 { 518 siblingPath = sibling.getNode().getPath(); 519 if (siblingPath.contains("/")) 520 { 521 siblingPath = StringUtils.substringAfterLast(siblingPath, "/"); 522 } 523 String path = node.getPath(); 524 if (path.contains("/")) 525 { 526 path = StringUtils.substringAfterLast(path, "/"); 527 } 528 node.getParent().orderBefore(path, siblingPath); 529 } 530 catch (RepositoryException e) 531 { 532 throw new AmetysRepositoryException(String.format("Unable to order link '%s' before link '%s'", this, siblingPath), e); 533 } 534 } 535 else 536 { 537 throw new AmetysRepositoryException("A link can only be moved before another link."); 538 } 539 } 540 541 @Override 542 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 543 { 544 if (getParent().equals(newParent)) 545 { 546 try 547 { 548 // Just order current node to the end. 549 Node node = getNode(); 550 String path = node.getPath(); 551 if (path.contains("/")) 552 { 553 path = StringUtils.substringAfterLast(path, "/"); 554 } 555 node.getParent().orderBefore(path, null); 556 } 557 catch (RepositoryException e) 558 { 559 throw new AmetysRepositoryException(String.format("Unable to move link '%s' outside the root link node.", this), e); 560 } 561 } 562 else 563 { 564 throw new AmetysRepositoryException("A link can only be moved before another link."); 565 } 566 } 567 568 @Override 569 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 570 { 571 return getParent().equals(newParent); 572 } 573 574 /** 575 * Retrieves the list of values corresponding to the metadata name passed as 576 * parameter 577 * 578 * @param metadataName the name of the metadata to retrieve 579 * @return the list corresponding to the metadata name 580 */ 581 private String[] _getListFromMetadataName(String metadataName) 582 { 583 try 584 { 585 if (getNode().hasProperty(metadataName)) 586 { 587 Value[] values = getNode().getProperty(metadataName).getValues(); 588 589 String[] list = new String[values.length]; 590 591 for (int i = 0; i < values.length; i++) 592 { 593 list[i] = values[i].getString(); 594 } 595 596 return list; 597 } 598 else 599 { 600 return new String[0]; 601 } 602 } 603 catch (RepositoryException e) 604 { 605 throw new AmetysRepositoryException("An error occurred while trying to get the property '" + metadataName + "'.", e); 606 } 607 } 608 609 /** 610 * Sets the list of values to the node corresponding to the metadata name 611 * passed as a parameter 612 * 613 * @param list the list of value to be set 614 * @param metadataName the name of the metadata 615 */ 616 private void _setListWithMetadataName(String[] list, String metadataName) 617 { 618 try 619 { 620 getNode().setProperty(metadataName, list); 621 } 622 catch (RepositoryException e) 623 { 624 throw new AmetysRepositoryException("An error occurred while trying to set the property '" + metadataName + "'.", e); 625 } 626 } 627 628 @Override 629 public String getDynamicInformationProvider() throws AmetysRepositoryException 630 { 631 try 632 { 633 return getNode().getProperty(PROPERTY_DYNAMIC_INFO_PROVIDER).getString(); 634 } 635 catch (PathNotFoundException e) 636 { 637 return null; 638 } 639 catch (RepositoryException e) 640 { 641 throw new AmetysRepositoryException("Error getting the URL property.", e); 642 } 643 } 644 645 @Override 646 public void setDynamicInformationProvider(String providerId) throws AmetysRepositoryException 647 { 648 try 649 { 650 getNode().setProperty(PROPERTY_DYNAMIC_INFO_PROVIDER, providerId); 651 } 652 catch (RepositoryException e) 653 { 654 throw new AmetysRepositoryException("Error setting the URL property.", e); 655 } 656 } 657 658 @Override 659 public String getColor() throws AmetysRepositoryException 660 { 661 try 662 { 663 return getNode().getProperty(PROPERTY_COLOR).getString(); 664 } 665 catch (PathNotFoundException e) 666 { 667 return null; 668 } 669 catch (RepositoryException e) 670 { 671 throw new AmetysRepositoryException("Error getting the color property.", e); 672 } 673 } 674 675 @Override 676 public void setColor(String color) throws AmetysRepositoryException 677 { 678 try 679 { 680 getNode().setProperty(PROPERTY_COLOR, color); 681 } 682 catch (RepositoryException e) 683 { 684 throw new AmetysRepositoryException("Error setting the color property.", e); 685 } 686 } 687 688 @Override 689 public String getPage() throws AmetysRepositoryException 690 { 691 try 692 { 693 return getNode().getProperty(PROPERTY_PAGE).getString(); 694 } 695 catch (PathNotFoundException e) 696 { 697 return null; 698 } 699 catch (RepositoryException e) 700 { 701 throw new AmetysRepositoryException("Error getting the page property.", e); 702 } 703 } 704 705 @Override 706 public void setPage(String page) throws AmetysRepositoryException 707 { 708 try 709 { 710 getNode().setProperty(PROPERTY_PAGE, page); 711 } 712 catch (RepositoryException e) 713 { 714 throw new AmetysRepositoryException("Error setting the page property.", e); 715 } 716 } 717 718 @Override 719 public LinkStatus getStatus() throws AmetysRepositoryException 720 { 721 try 722 { 723 return LinkStatus.valueOf(getNode().getProperty(PROPERTY_STATUS).getString()); 724 } 725 catch (PathNotFoundException e) 726 { 727 return null; 728 } 729 catch (RepositoryException e) 730 { 731 throw new AmetysRepositoryException("Error getting the status property.", e); 732 } 733 } 734 735 @Override 736 public void setStatus(LinkStatus status) throws AmetysRepositoryException 737 { 738 try 739 { 740 getNode().setProperty(PROPERTY_STATUS, status.name()); 741 } 742 catch (RepositoryException e) 743 { 744 throw new AmetysRepositoryException("Error setting the status property.", e); 745 } 746 } 747 748 public ModifiableModelLessDataHolder getDataHolder() 749 { 750 JCRRepositoryData repositoryData = new JCRRepositoryData(getNode()); 751 return new DefaultModifiableModelLessDataHolder(_getFactory().getLinkDataTypeExtensionPoint(), repositoryData); 752 } 753}