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.plugins.explorer.resources.jcr; 017 018import java.io.InputStream; 019import java.util.Calendar; 020import java.util.Date; 021import java.util.GregorianCalendar; 022import java.util.List; 023 024import javax.jcr.Binary; 025import javax.jcr.Node; 026import javax.jcr.NodeIterator; 027import javax.jcr.RepositoryException; 028import javax.jcr.Value; 029import javax.jcr.version.VersionHistory; 030 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.core.user.UserIdentity; 034import org.ametys.plugins.explorer.ExplorerNode; 035import org.ametys.plugins.explorer.resources.ModifiableResource; 036import org.ametys.plugins.explorer.resources.Resource; 037import org.ametys.plugins.explorer.threads.Thread; 038import org.ametys.plugins.explorer.threads.jcr.JCRThread; 039import org.ametys.plugins.explorer.threads.jcr.JCRThreadFactory; 040import org.ametys.plugins.repository.AmetysObject; 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.dublincore.DCMITypes; 046import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 047import org.ametys.plugins.repository.jcr.DublinCoreHelper; 048import org.ametys.plugins.repository.jcr.NodeTypeHelper; 049import org.ametys.plugins.repository.metadata.UnknownMetadataException; 050 051/** 052 * Default implementation of an {@link Resource}, backed by a JCR node.<br> 053 * @param <F> the actual type of factory. 054 */ 055public class JCRResource<F extends JCRResourceFactory> extends DefaultAmetysObject<F> implements ModifiableResource, CopiableAmetysObject 056{ 057 /** The name of node holding the creator */ 058 public static final String CREATOR_NODE_NAME = "creator"; 059 /** Constants for lastModified Metadata */ 060 public static final String CREATION_DATE = "creationDate"; 061 /** The name of node holding the last contributor */ 062 public static final String CONTRIBUTOR_NODE_NAME = "contributor"; 063 064 /** 065 * Creates an {@link JCRResource}. 066 * @param node the node backing this {@link AmetysObject} 067 * @param parentPath the parentPath in the Ametys hierarchy 068 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 069 */ 070 public JCRResource(Node node, String parentPath, F factory) 071 { 072 super(node, parentPath, factory); 073 } 074 075 @Override 076 public void setData(InputStream stream, String mimeType, Date lastModified, UserIdentity author) 077 { 078 Node fileNode = getNode(); 079 080 try 081 { 082 setLastContributor(author); 083 084 Node resourceNode = null; 085 086 if (fileNode.hasNode("jcr:content")) 087 { 088 // Already exists 089 resourceNode = fileNode.getNode("jcr:content"); 090 } 091 else 092 { 093 resourceNode = fileNode.addNode("jcr:content", "nt:resource"); 094 setCreator(author); 095 setCreationDate(new Date()); 096 } 097 098 GregorianCalendar gc = new GregorianCalendar(); 099 gc.setTime(lastModified); 100 resourceNode.setProperty("jcr:lastModified", gc); 101 102 resourceNode.setProperty("jcr:mimeType", mimeType); 103 104 Binary binary = resourceNode.getSession().getValueFactory().createBinary(stream); 105 resourceNode.setProperty("jcr:data", binary); 106 } 107 catch (RepositoryException e) 108 { 109 throw new AmetysRepositoryException(e); 110 } 111 } 112 113 @Override 114 public void setLastModified(Date lastModified) 115 { 116 Node fileNode = getNode(); 117 118 try 119 { 120 Node resourceNode = fileNode.getNode("jcr:content"); 121 122 GregorianCalendar gc = new GregorianCalendar(); 123 gc.setTime(lastModified); 124 resourceNode.setProperty("jcr:lastModified", gc); 125 } 126 catch (RepositoryException e) 127 { 128 throw new AmetysRepositoryException(e); 129 } 130 } 131 132 @Override 133 public void setKeywords(String keywords) 134 { 135 String[] words = StringUtils.stripAll(StringUtils.split(keywords, ',')); 136 137 String[] trimWords = new String[words.length]; 138 for (int i = 0; i < words.length; i++) 139 { 140 trimWords[i] = words[i].trim(); 141 } 142 143 Node fileNode = getNode(); 144 try 145 { 146 fileNode.setProperty("ametys:keywords", trimWords); 147 } 148 catch (RepositoryException e) 149 { 150 throw new AmetysRepositoryException(e); 151 } 152 } 153 154 @Override 155 public void setKeywords(String[] keywords) 156 { 157 Node fileNode = getNode(); 158 try 159 { 160 fileNode.setProperty("ametys:keywords", keywords); 161 } 162 catch (RepositoryException e) 163 { 164 throw new AmetysRepositoryException(e); 165 } 166 } 167 168 @Override 169 public void setMimeType(String mimeType) 170 { 171 Node fileNode = getNode(); 172 173 try 174 { 175 Node resourceNode = fileNode.getNode("jcr:content"); 176 177 resourceNode.setProperty("jcr:mimeType", mimeType); 178 } 179 catch (RepositoryException e) 180 { 181 throw new AmetysRepositoryException(e); 182 } 183 } 184 185 @Override 186 public void setCreator(UserIdentity author) 187 { 188 try 189 { 190 Node authorNode = null; 191 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME)) 192 { 193 authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME); 194 } 195 else 196 { 197 authorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME, RepositoryConstants.USER_NODETYPE); 198 } 199 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", author.getLogin()); 200 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", author.getPopulationId()); 201 } 202 catch (RepositoryException e) 203 { 204 throw new AmetysRepositoryException(e); 205 } 206 } 207 208 @Override 209 public InputStream getInputStream () throws AmetysRepositoryException 210 { 211 Node fileNode = getNode(); 212 try 213 { 214 Node resourceNode = null; 215 216 if (fileNode.hasNode("jcr:content")) 217 { 218 // Already exists 219 resourceNode = fileNode.getNode("jcr:content"); 220 return resourceNode.getProperty("jcr:data").getBinary().getStream(); 221 } 222 return null; 223 } 224 catch (RepositoryException e) 225 { 226 throw new AmetysRepositoryException(e); 227 } 228 } 229 230 @Override 231 public String getMimeType () throws AmetysRepositoryException 232 { 233 Node fileNode = getNode(); 234 try 235 { 236 Node resourceNode = null; 237 238 if (fileNode.hasNode("jcr:content")) 239 { 240 // Already exists 241 resourceNode = fileNode.getNode("jcr:content"); 242 return resourceNode.getProperty("jcr:mimeType").getString(); 243 } 244 245 return null; 246 } 247 catch (RepositoryException e) 248 { 249 throw new AmetysRepositoryException(e); 250 } 251 } 252 253 @Override 254 public Date getLastModified () throws AmetysRepositoryException 255 { 256 Node fileNode = getNode(); 257 try 258 { 259 Node resourceNode = null; 260 261 if (fileNode.hasNode("jcr:content")) 262 { 263 resourceNode = fileNode.getNode("jcr:content"); 264 return resourceNode.getProperty("jcr:lastModified").getDate().getTime(); 265 } 266 267 return null; 268 } 269 catch (RepositoryException e) 270 { 271 throw new AmetysRepositoryException(e); 272 } 273 } 274 275 @Override 276 public long getLength() throws AmetysRepositoryException 277 { 278 Node fileNode = getNode(); 279 try 280 { 281 Node resourceNode = null; 282 283 if (fileNode.hasNode("jcr:content")) 284 { 285 resourceNode = fileNode.getNode("jcr:content"); 286 return resourceNode.getProperty("jcr:data").getLength(); 287 } 288 289 return 0; 290 } 291 catch (RepositoryException e) 292 { 293 throw new AmetysRepositoryException(e); 294 } 295 } 296 297 @Override 298 public UserIdentity getCreator() throws AmetysRepositoryException 299 { 300 try 301 { 302 Node authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME); 303 return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString()); 304 } 305 catch (RepositoryException e) 306 { 307 throw new AmetysRepositoryException(e); 308 } 309 } 310 311 /** 312 * Get the author from old revision 313 * @param revision The revision 314 * @return The user identity of the author or <code>null</code> if not found 315 * @throws RepositoryException If an error occurred 316 */ 317 public UserIdentity getAuthorFromRevision (String revision) throws RepositoryException 318 { 319 try 320 { 321 switchToRevision(revision); 322 323 VersionHistory history = getVersionHistory(); 324 Node versionNode = history.getVersion(revision); 325 Node frozenNode = versionNode.getNode("jcr:frozenNode"); 326 327 if (frozenNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME)) 328 { 329 Node authorNode = frozenNode.getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME); 330 return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString()); 331 } 332 return null; 333 } 334 catch (RepositoryException e) 335 { 336 throw new AmetysRepositoryException("Unable to get author from revision: " + revision, e); 337 } 338 } 339 340 @Override 341 public String[] getKeywords() throws AmetysRepositoryException 342 { 343 Node fileNode = getNode(); 344 try 345 { 346 if (!fileNode.hasProperty("ametys:keywords")) 347 { 348 return new String[0]; 349 } 350 351 Value[] values = fileNode.getProperty("ametys:keywords").getValues(); 352 String[] result = new String[values.length]; 353 354 for (int i = 0; i < values.length; i++) 355 { 356 result[i] = values[i].getString(); 357 } 358 359 return result; 360 } 361 catch (RepositoryException e) 362 { 363 throw new AmetysRepositoryException(e); 364 } 365 } 366 367 @Override 368 public String getKeywordsAsString() throws AmetysRepositoryException 369 { 370 Node fileNode = getNode(); 371 try 372 { 373 if (!fileNode.hasProperty("ametys:keywords")) 374 { 375 return StringUtils.EMPTY; 376 } 377 378 StringBuilder sb = new StringBuilder(); 379 Value[] values = fileNode.getProperty("ametys:keywords").getValues(); 380 381 for (Value value : values) 382 { 383 if (sb.length() > 0) 384 { 385 sb.append(", "); 386 } 387 388 sb.append(value.getString()); 389 } 390 391 return sb.toString(); 392 } 393 catch (RepositoryException e) 394 { 395 throw new AmetysRepositoryException(e); 396 } 397 } 398 399 @Override 400 protected void restoreFromNode(Node node) throws RepositoryException 401 { 402 super.restoreFromNode(node); 403 404 // First remove node 405 NodeIterator nit = getBaseNode().getNodes("jcr:content"); 406 while (nit.hasNext()) 407 { 408 nit.nextNode().remove(); 409 } 410 411 NodeIterator new_nit = node.getNodes("jcr:content"); 412 while (new_nit.hasNext()) 413 { 414 copyNode(getBaseNode(), new_nit.nextNode()); 415 } 416 } 417 418 @Override 419 public String getResourcePath() throws AmetysRepositoryException 420 { 421 return ((ExplorerNode) getParent()).getExplorerPath() + "/" + getName(); 422 } 423 424 @Override 425 public void lock() throws AmetysRepositoryException 426 { 427 _getFactory().getLockComponent().lock(this); 428 } 429 430 @Override 431 public void unlock() throws AmetysRepositoryException 432 { 433 _getFactory().getLockComponent().unlock(this); 434 } 435 436 @Override 437 public boolean isLocked() throws AmetysRepositoryException 438 { 439 return _getFactory().getLockComponent().isLocked(this); 440 } 441 442 @Override 443 public UserIdentity getLockOwner() throws AmetysRepositoryException 444 { 445 return _getFactory().getLockComponent().getLockOwner(this); 446 } 447 448 @Override 449 public Thread getComments(boolean createThread) 450 { 451 try 452 { 453 Node node = getNode(); 454 if (!node.hasNode("ametys:comments")) 455 { 456 if (createThread) 457 { 458 Node commentsRootNode = node.addNode("ametys:comments", JCRThreadFactory.THREAD_NODETYPE); 459 460 JCRThread thread = _getFactory().getResolver().resolve(commentsRootNode, false); 461 thread.setTitle(getName()); 462 thread.setDescription(""); 463 thread.setAuthor(getCreator()); 464 thread.setCreationDate(new Date()); 465 466 return thread; 467 } 468 469 return null; 470 } 471 else 472 { 473 Node commentsRootNode = node.getNode("ametys:comments"); 474 return _getFactory().getResolver().resolve(commentsRootNode, false); 475 } 476 } 477 catch (RepositoryException e) 478 { 479 throw new AmetysRepositoryException(e); 480 } 481 } 482 483 @Override 484 public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 485 { 486 try 487 { 488 String nodeTypeName = NodeTypeHelper.getNodeTypeName(getNode()); 489 490 JCRResource copiedResource = parent.createChild(name, nodeTypeName); 491 492 copiedResource.setKeywords(getKeywords()); 493 copiedResource.setData(getInputStream(), getMimeType(), getLastModified(), getCreator()); 494 495 parent.saveChanges(); 496 497 return copiedResource; 498 } 499 catch (RepositoryException e) 500 { 501 throw new AmetysRepositoryException("Error copying the collection " + getId() + " into " + parent.getId(), e); 502 } 503 } 504 505 @Override 506 public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 507 { 508 return copyTo(parent, name); 509 } 510 511 // Dublin Core metadata. // 512 513 @Override 514 public String getDCTitle() throws AmetysRepositoryException 515 { 516 return DublinCoreHelper.getDCTitle(this, getName()); 517 } 518 519 @Override 520 public void setDCTitle(String title) throws AmetysRepositoryException 521 { 522 DublinCoreHelper.setDCTitle(this, title); 523 } 524 525 @Override 526 public String getDCCreator() throws AmetysRepositoryException 527 { 528 return DublinCoreHelper.getDCCreator(this); 529 } 530 531 @Override 532 public void setDCCreator(String creator) throws AmetysRepositoryException 533 { 534 DublinCoreHelper.setDCCreator(this, creator); 535 } 536 537 @Override 538 public String[] getDCSubject() throws AmetysRepositoryException 539 { 540 return DublinCoreHelper.getDCSubject(this, getKeywords()); 541 } 542 543 @Override 544 public void setDCSubject(String[] subject) throws AmetysRepositoryException 545 { 546 DublinCoreHelper.setDCSubject(this, subject); 547 } 548 549 @Override 550 public String getDCDescription() throws AmetysRepositoryException 551 { 552 return DublinCoreHelper.getDCDescription(this); 553 } 554 555 @Override 556 public void setDCDescription(String description) throws AmetysRepositoryException 557 { 558 DublinCoreHelper.setDCDescription(this, description); 559 } 560 561 @Override 562 public String getDCPublisher() throws AmetysRepositoryException 563 { 564 return DublinCoreHelper.getDCPublisher(this); 565 } 566 567 @Override 568 public void setDCPublisher(String publisher) throws AmetysRepositoryException 569 { 570 DublinCoreHelper.setDCPublisher(this, publisher); 571 } 572 573 @Override 574 public String getDCContributor() throws AmetysRepositoryException 575 { 576 return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getCreator())); 577 } 578 579 @Override 580 public void setDCContributor(String contributor) throws AmetysRepositoryException 581 { 582 DublinCoreHelper.setDCContributor(this, contributor); 583 } 584 585 @Override 586 public Date getDCDate() throws AmetysRepositoryException 587 { 588 return DublinCoreHelper.getDCDate(this, getLastModified()); 589 } 590 591 @Override 592 public void setDCDate(Date date) throws AmetysRepositoryException 593 { 594 DublinCoreHelper.setDCDate(this, date); 595 } 596 597 @Override 598 public String getDCType() throws AmetysRepositoryException 599 { 600 return DublinCoreHelper.getDCType(this, _getDefaultDCType()); 601 } 602 603 private String _getDefaultDCType () 604 { 605 String mimetype = getMimeType(); 606 607 if (mimetype == null) 608 { 609 return DCMITypes.TEXT; 610 } 611 else if (mimetype.startsWith("image")) 612 { 613 return DCMITypes.IMAGE; 614 } 615 else if (mimetype.startsWith("video") || "application/x-shockwave-flash".equals(mimetype)) 616 { 617 return DCMITypes.INTERACTIVERESOURCE; 618 } 619 else if (mimetype.startsWith("audio")) 620 { 621 return DCMITypes.SOUND; 622 } 623 624 return DCMITypes.TEXT; 625 } 626 627 628 @Override 629 public void setDCType(String type) throws AmetysRepositoryException 630 { 631 DublinCoreHelper.setDCType(this, type); 632 } 633 634 @Override 635 public String getDCFormat() throws AmetysRepositoryException 636 { 637 return DublinCoreHelper.getDCFormat(this, getMimeType()); 638 } 639 640 @Override 641 public void setDCFormat(String format) throws AmetysRepositoryException 642 { 643 DublinCoreHelper.setDCFormat(this, format); 644 } 645 646 @Override 647 public String getDCIdentifier() throws AmetysRepositoryException 648 { 649 return DublinCoreHelper.getDCIdentifier(this, getId()); 650 } 651 652 @Override 653 public void setDCIdentifier(String identifier) throws AmetysRepositoryException 654 { 655 DublinCoreHelper.setDCIdentifier(this, identifier); 656 } 657 658 @Override 659 public String getDCSource() throws AmetysRepositoryException 660 { 661 return DublinCoreHelper.getDCSource(this); 662 } 663 664 @Override 665 public void setDCSource(String source) throws AmetysRepositoryException 666 { 667 DublinCoreHelper.setDCSource(this, source); 668 } 669 670 @Override 671 public String getDCLanguage() throws AmetysRepositoryException 672 { 673 return DublinCoreHelper.getDCLanguage(this); 674 } 675 676 @Override 677 public void setDCLanguage(String language) throws AmetysRepositoryException 678 { 679 DublinCoreHelper.setDCLanguage(this, language); 680 } 681 682 @Override 683 public String getDCRelation() throws AmetysRepositoryException 684 { 685 return DublinCoreHelper.getDCRelation(this); 686 } 687 688 @Override 689 public void setDCRelation(String relation) throws AmetysRepositoryException 690 { 691 DublinCoreHelper.setDCRelation(this, relation); 692 } 693 694 @Override 695 public String getDCCoverage() throws AmetysRepositoryException 696 { 697 return DublinCoreHelper.getDCCoverage(this, getDCLanguage()); 698 } 699 700 @Override 701 public void setDCCoverage(String coverage) throws AmetysRepositoryException 702 { 703 DublinCoreHelper.setDCCoverage(this, coverage); 704 } 705 706 @Override 707 public String getDCRights() throws AmetysRepositoryException 708 { 709 return DublinCoreHelper.getDCRights(this); 710 } 711 712 @Override 713 public void setDCRights(String rights) throws AmetysRepositoryException 714 { 715 DublinCoreHelper.setDCRights(this, rights); 716 } 717 718 public Date getCreationDate() throws UnknownMetadataException, AmetysRepositoryException 719 { 720 Node fileNode = getNode(); 721 try 722 { 723 if (!fileNode.hasProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE)) 724 { 725 return null; 726 } 727 728 return fileNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE).getDate().getTime(); 729 } 730 catch (RepositoryException e) 731 { 732 throw new AmetysRepositoryException(e); 733 } 734 } 735 736 public void setCreationDate(Date creationDate) 737 { 738 Node fileNode = getNode(); 739 try 740 { 741 Calendar calendar = new GregorianCalendar(); 742 calendar.setTime(creationDate); 743 fileNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE, calendar); 744 } 745 catch (RepositoryException e) 746 { 747 throw new AmetysRepositoryException(e); 748 } 749 } 750 751 public UserIdentity getLastContributor() throws UnknownMetadataException, AmetysRepositoryException 752 { 753 try 754 { 755 Node authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME); 756 return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString()); 757 } 758 catch (RepositoryException e) 759 { 760 throw new AmetysRepositoryException(e); 761 } 762 } 763 764 public void setLastContributor(UserIdentity lastContributor) 765 { 766 try 767 { 768 Node lastContributorNode = null; 769 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME)) 770 { 771 lastContributorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME); 772 } 773 else 774 { 775 lastContributorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME, RepositoryConstants.USER_NODETYPE); 776 } 777 lastContributorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", lastContributor.getLogin()); 778 lastContributorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", lastContributor.getPopulationId()); 779 } 780 catch (RepositoryException e) 781 { 782 throw new AmetysRepositoryException(e); 783 } 784 } 785 786}