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