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