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