001/* 002 * Copyright 2016 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.actions; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.Reader; 022import java.nio.charset.StandardCharsets; 023import java.time.LocalDateTime; 024import java.time.format.DateTimeFormatter; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Comparator; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Map; 035import java.util.Objects; 036import java.util.Set; 037 038import javax.jcr.Node; 039import javax.jcr.RepositoryException; 040import javax.jcr.Session; 041import javax.jcr.lock.Lock; 042import javax.jcr.lock.LockManager; 043 044import org.apache.avalon.framework.component.Component; 045import org.apache.avalon.framework.context.ContextException; 046import org.apache.avalon.framework.context.Contextualizable; 047import org.apache.avalon.framework.service.ServiceException; 048import org.apache.avalon.framework.service.ServiceManager; 049import org.apache.avalon.framework.service.Serviceable; 050import org.apache.cocoon.Constants; 051import org.apache.cocoon.ProcessingException; 052import org.apache.cocoon.environment.Context; 053import org.apache.commons.io.IOUtils; 054import org.apache.commons.io.output.NullOutputStream; 055import org.apache.commons.lang.IllegalClassException; 056import org.apache.commons.lang.StringUtils; 057import org.apache.jackrabbit.util.Text; 058import org.apache.tika.metadata.Metadata; 059 060import org.ametys.core.observation.Event; 061import org.ametys.core.observation.ObservationManager; 062import org.ametys.core.right.RightManager; 063import org.ametys.core.right.RightManager.RightResult; 064import org.ametys.core.ui.Callable; 065import org.ametys.core.user.CurrentUserProvider; 066import org.ametys.core.user.UserIdentity; 067import org.ametys.core.util.DateUtils; 068import org.ametys.core.util.I18nUtils; 069import org.ametys.plugins.core.user.UserHelper; 070import org.ametys.plugins.explorer.ExplorerNode; 071import org.ametys.plugins.explorer.ModifiableExplorerNode; 072import org.ametys.plugins.explorer.ObservationConstants; 073import org.ametys.plugins.explorer.applications.ExplorerApplication; 074import org.ametys.plugins.explorer.applications.ExplorerApplicationExtensionPoint; 075import org.ametys.plugins.explorer.calendars.Calendar; 076import org.ametys.plugins.explorer.cmis.CMISRootResourcesCollection; 077import org.ametys.plugins.explorer.cmis.CMISTreeFactory; 078import org.ametys.plugins.explorer.resources.CommentableResource; 079import org.ametys.plugins.explorer.resources.ModifiableResource; 080import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 081import org.ametys.plugins.explorer.resources.Resource; 082import org.ametys.plugins.explorer.resources.ResourceCollection; 083import org.ametys.plugins.explorer.resources.generators.ResourcesExplorerGenerator; 084import org.ametys.plugins.explorer.resources.jcr.JCRResource; 085import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 086import org.ametys.plugins.explorer.resources.metadata.TikaProvider; 087import org.ametys.plugins.explorer.resources.metadata.populate.ResourceMetadataPopulator; 088import org.ametys.plugins.explorer.resources.metadata.populate.ResourceMetadataPopulatorExtensionPoint; 089import org.ametys.plugins.explorer.threads.actions.ThreadDAO; 090import org.ametys.plugins.explorer.threads.jcr.JCRPost; 091import org.ametys.plugins.explorer.threads.jcr.JCRPostFactory; 092import org.ametys.plugins.explorer.threads.jcr.JCRThread; 093import org.ametys.plugins.repository.AmetysObject; 094import org.ametys.plugins.repository.AmetysObjectIterable; 095import org.ametys.plugins.repository.AmetysObjectResolver; 096import org.ametys.plugins.repository.AmetysRepositoryException; 097import org.ametys.plugins.repository.ModifiableAmetysObject; 098import org.ametys.plugins.repository.RemovableAmetysObject; 099import org.ametys.plugins.repository.RepositoryConstants; 100import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 101import org.ametys.plugins.repository.TraversableAmetysObject; 102import org.ametys.plugins.repository.UnknownAmetysObjectException; 103import org.ametys.plugins.repository.dublincore.DublinCoreAwareAmetysObject; 104import org.ametys.plugins.repository.jcr.JCRAmetysObject; 105import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 106import org.ametys.plugins.repository.lock.LockHelper; 107import org.ametys.plugins.repository.lock.LockableAmetysObject; 108import org.ametys.plugins.repository.metadata.ModifiableRichText; 109import org.ametys.plugins.repository.version.VersionableAmetysObject; 110import org.ametys.runtime.i18n.I18nizableText; 111import org.ametys.runtime.plugin.component.AbstractLogEnabled; 112 113/** 114 * Explorer resources DAO 115 */ 116public class ExplorerResourcesDAO extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 117{ 118 /** Avalon Role */ 119 public static final String ROLE = ExplorerResourcesDAO.class.getName(); 120 121 /** Right id to unlock all resources */ 122 public static final String RIGHTS_RESOURCE_UNLOCK_ALL = "Plugin_Explorer_File_Unlock_All"; 123 124 /** Right id to add a resource */ 125 public static final String RIGHTS_RESOURCE_ADD = "Plugin_Explorer_File_Add"; 126 127 /** Right id to rename a resource */ 128 public static final String RIGHTS_RESOURCE_RENAME = "Plugin_Explorer_File_Rename"; 129 130 /** Right id to delete a resource */ 131 public static final String RIGHTS_RESOURCE_DELETE = "Plugin_Explorer_File_Delete"; 132 133 /** Right id to edit DC metadata of a resource */ 134 public static final String RIGHTS_RESOURCE_EDIT_DC = "Plugin_Explorer_File_Edit_DC_Metadata"; 135 136 /** Right id to comment a resource */ 137 public static final String RIGHTS_RESOURCE_COMMENT = "Plugin_Explorer_File_Comment"; 138 139 /** Right id to moderate comment a resource */ 140 public static final String RIGHTS_RESOURCE_MODERATE_COMMENT = "Plugin_Explorer_File_Moderate_Comments"; 141 142 /** Right id to add a folder */ 143 public static final String RIGHTS_COLLECTION_ADD = "Plugin_Explorer_Folder_Add"; 144 145 /** Right id to edit a folder */ 146 public static final String RIGHTS_COLLECTION_EDIT = "Plugin_Explorer_Folder_Edit"; 147 148 /** Right id to delete a folder */ 149 public static final String RIGHTS_COLLECTION_DELETE = "Plugin_Explorer_Folder_Delete"; 150 151 /** Ametys resolver */ 152 protected AmetysObjectResolver _resolver; 153 154 /** The rights manager */ 155 protected RightManager _rightManager; 156 157 /** Observer manager. */ 158 protected ObservationManager _observationManager; 159 160 /** The current user provider. */ 161 protected CurrentUserProvider _currentUserProvider; 162 163 /** I18n utils */ 164 protected I18nUtils _i18nUtils; 165 166 /** The avalon context */ 167 protected org.apache.avalon.framework.context.Context _context; 168 169 /** The cocoon context */ 170 protected Context _cocoonContext; 171 172 /** The tika provider. */ 173 protected TikaProvider _tikaProvider; 174 175 /** The metadata populator extension point. */ 176 protected ResourceMetadataPopulatorExtensionPoint _metadataPopulatorEP; 177 178 /** The users manager */ 179 protected UserHelper _userHelper; 180 181 /** The explorer application EP */ 182 protected ExplorerApplicationExtensionPoint _explorerApplicationEP; 183 184 /** The thread DAO */ 185 protected ThreadDAO _threadDAO; 186 187 @Override 188 public void service(ServiceManager manager) throws ServiceException 189 { 190 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 191 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 192 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 193 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 194 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 195 _tikaProvider = (TikaProvider) manager.lookup(TikaProvider.ROLE); 196 _metadataPopulatorEP = (ResourceMetadataPopulatorExtensionPoint) manager.lookup(ResourceMetadataPopulatorExtensionPoint.ROLE); 197 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 198 _explorerApplicationEP = (ExplorerApplicationExtensionPoint) manager.lookup(ExplorerApplicationExtensionPoint.ROLE); 199 _threadDAO = (ThreadDAO) manager.lookup(ThreadDAO.ROLE); 200 } 201 202 @Override 203 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 204 { 205 _context = context; 206 _cocoonContext = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 207 } 208 209 /** 210 * Get the root nodes for resources 211 * 212 * @return the root nodes 213 */ 214 public List<ExplorerNode> getResourcesRootNodes() 215 { 216 List<ExplorerNode> roots = new ArrayList<>(); 217 roots.add(_resolver.resolveByPath(RepositoryConstants.NAMESPACE_PREFIX + ":resources")); 218 return roots; 219 } 220 221 /** 222 * Retrieves the set of standard data for the explorer root node 223 * 224 * @return The data structured in a map 225 */ 226 @Callable 227 public List<Map<String, Object>> getRootNodesInfo() 228 { 229 List<Map<String, Object>> infos = new ArrayList<>(); 230 231 List<ExplorerNode> rootNodes = getResourcesRootNodes(); 232 for (ExplorerNode rootNode : rootNodes) 233 { 234 infos.add(getDefaultInfoAsRootNode(rootNode)); 235 } 236 return infos; 237 } 238 239 /** 240 * Get the necessary default info for a root node. Can be used to construct 241 * a root node for a tree (client side). 242 * 243 * @param id The root node id 244 * @return A map which contains a set of default info (such as id, 245 * applicationId, path, type etc...) 246 */ 247 @Callable 248 public Map<String, Object> getDefaultInfoAsRootNode(String id) 249 { 250 ExplorerNode node = _resolver.resolveById(id); 251 return getDefaultInfoAsRootNode(node); 252 } 253 254 /** 255 * Get the necessary default info for a root node. Can be used to construct 256 * a root node for a tree (client side). 257 * 258 * @param rootNode The root node 259 * @return A map which contains a set of default info (such as id, 260 * applicationId, path, type etc...) 261 */ 262 public Map<String, Object> getDefaultInfoAsRootNode(ExplorerNode rootNode) 263 { 264 Map<String, Object> result = new HashMap<>(); 265 266 result.put("id", rootNode.getId()); 267 result.put("applicationId", rootNode.getApplicationId()); 268 result.put("name", "resources"); 269 result.put("cls", "root"); 270 result.put("iconCls", "ametysicon-folder249"); 271 272 result.put("text", _i18nUtils.translate(new I18nizableText("plugin.explorer", "PLUGINS_EXPLORER_ROOT_NODE"))); 273 274 result.put("path", "/dummy/resources"); 275 result.put("type", ResourcesExplorerGenerator.RESOURCE_COLLECTION); 276 277 boolean hasResources = false; 278 if (rootNode instanceof ResourceCollection) 279 { 280 hasResources = ((ResourceCollection) rootNode).hasChildResources(); 281 } 282 boolean hasChildNodes = rootNode.hasChildExplorerNodes(); 283 284 if (hasChildNodes) 285 { 286 result.put("hasChildNodes", true); 287 } 288 289 if (hasResources) 290 { 291 result.put("hasResources", true); 292 } 293 294 result.put("isModifiable", false); 295 296 if (rootNode instanceof ModifiableExplorerNode) 297 { 298 result.put("canCreateChild", true); 299 } 300 301 return result; 302 } 303 304 /** 305 * Get the informations on given nodes (resources or collections) 306 * 307 * @param ids The ids of node 308 * @return the nodes information 309 */ 310 @Callable 311 public Map<String, Object> getNodesInfo(List<String> ids) 312 { 313 List<Map<String, Object>> objects = new ArrayList<>(); 314 List<String> objectsNotFound = new ArrayList<>(); 315 316 for (String id : ids) 317 { 318 try 319 { 320 AmetysObject ao = _resolver.resolveById(id); 321 322 if (ao instanceof ExplorerNode) 323 { 324 objects.add(getExplorerNodeProperties((ExplorerNode) ao)); 325 } 326 else if (ao instanceof Resource) 327 { 328 objects.add(getResourceProperties((Resource) ao)); 329 } 330 } 331 catch (UnknownAmetysObjectException e) 332 { 333 objectsNotFound.add(id); 334 } 335 } 336 337 Map<String, Object> result = new HashMap<>(); 338 result.put("objects", objects); 339 result.put("objectsNotFound", objectsNotFound); 340 341 return result; 342 } 343 344 /** 345 * Get the explorer node properties 346 * 347 * @param node The explorer node 348 * @return The properties 349 */ 350 public Map<String, Object> getExplorerNodeProperties(ExplorerNode node) 351 { 352 Map<String, Object> infos = new HashMap<>(); 353 354 ExplorerNode root = node; 355 AmetysObject parent = null; 356 357 while (true) 358 { 359 parent = root.getParent(); 360 if (parent instanceof ExplorerNode) 361 { 362 root = (ExplorerNode) parent; 363 } 364 else 365 { 366 break; 367 } 368 } 369 370 parent = node.getParent(); 371 372 infos.put("rootId", root.getId()); 373 infos.put("rootOwnerType", "explorer"); 374 infos.put("parentId", parent instanceof ExplorerNode ? parent.getId() : null); 375 infos.put("id", node.getId()); 376 infos.put("applicationId", node.getApplicationId()); 377 infos.put("name", node.getName()); 378 infos.put("path", node.getExplorerPath()); 379 infos.put("isModifiable", node instanceof ModifiableAmetysObject); 380 infos.put("canCreateChild", node instanceof ModifiableExplorerNode); 381 382 infos.put("rights", getUserRights(node)); 383 384 // Adding attributs specific to applications 385 String applicationId = node.getApplicationId(); 386 ExplorerApplication explorerApplication = _explorerApplicationEP.hasExtension(applicationId) ? _explorerApplicationEP.getExtension(applicationId) : null; 387 if (explorerApplication != null) 388 { 389 Map<String, String> additionalNodeParameters = explorerApplication.getAdditionalNodeParameters(node); 390 if (additionalNodeParameters != null) 391 { 392 additionalNodeParameters.entrySet().stream().forEach(entry -> infos.put(entry.getKey(), entry.getValue())); 393 } 394 } 395 396 return infos; 397 } 398 399 /** 400 * Get the resource properties 401 * 402 * @param resource The resources 403 * @return The properties 404 */ 405 public Map<String, Object> getResourceProperties(Resource resource) 406 { 407 Map<String, Object> infos = new HashMap<>(); 408 409 ResourceCollection parentAO = resource.getParent(); 410 ResourceCollection root = parentAO; 411 while (true) 412 { 413 if (root.getParent() instanceof ResourceCollection) 414 { 415 root = root.getParent(); 416 } 417 else 418 { 419 break; 420 } 421 } 422 infos.put("id", resource.getId()); 423 infos.put("rootId", root.getId()); 424 infos.put("rootOwnerType", "explorer"); 425 infos.put("parentId", parentAO.getId()); 426 infos.put("name", resource.getName()); 427 infos.put("path", resource.getResourcePath()); 428 infos.put("isModifiable", resource instanceof ModifiableAmetysObject); 429 430 infos.put("rights", getUserRights(parentAO)); 431 432 return infos; 433 } 434 435 /** 436 * Get the path of pages which match filter regexp 437 * @param id The id of explorer node to start search 438 * @param value the value to match 439 * @param allowedExtensions The allowed file extensions (lower-case). Can be null or empty to not filter on file extensions 440 * @return the matching paths 441 */ 442 @Callable 443 public List<String> filterResourcesByRegExp(String id, String value, List<String> allowedExtensions) 444 { 445 List<String> matchingPaths = new ArrayList<>(); 446 447 ExplorerNode root = _resolver.resolveById(id); 448 449 String toMatch = org.apache.commons.lang3.StringUtils.stripAccents(value.toLowerCase()).trim(); 450 451 if (root instanceof TraversableAmetysObject) 452 { 453 TraversableAmetysObject traversableObject = (TraversableAmetysObject) root; 454 AmetysObjectIterable< ? extends AmetysObject> children = traversableObject.getChildren(); 455 for (AmetysObject ao : children) 456 { 457 if (ao instanceof Resource) 458 { 459 _getMatchingResource((Resource) ao, toMatch, allowedExtensions, matchingPaths); 460 } 461 else if (ao instanceof ExplorerNode) 462 { 463 _getMatchingExplorerNode ((ExplorerNode) ao, toMatch, allowedExtensions, matchingPaths); 464 } 465 } 466 } 467 468 return matchingPaths; 469 } 470 471 private void _getMatchingExplorerNode(ExplorerNode explorerNode, String value, List<String> allowedExtensions, List<String> matchingPaths) 472 { 473 String title = org.apache.commons.lang3.StringUtils.stripAccents(explorerNode.getName().toLowerCase()); 474 475 if (title.contains(value)) 476 { 477 matchingPaths.add(explorerNode.getExplorerPath()); 478 } 479 480 if (explorerNode instanceof TraversableAmetysObject) 481 { 482 TraversableAmetysObject traversableObject = (TraversableAmetysObject) explorerNode; 483 484 AmetysObjectIterable< ? extends AmetysObject> children = traversableObject.getChildren(); 485 for (AmetysObject ao : children) 486 { 487 if (ao instanceof Resource) 488 { 489 _getMatchingResource((Resource) ao, value, allowedExtensions, matchingPaths); 490 } 491 else if (ao instanceof ExplorerNode) 492 { 493 _getMatchingExplorerNode ((ExplorerNode) ao, value, allowedExtensions, matchingPaths); 494 } 495 } 496 } 497 } 498 499 private void _getMatchingResource(Resource resource, String value, List<String> allowedExtensions, List<String> matchingPaths) 500 { 501 String filename = org.apache.commons.lang3.StringUtils.stripAccents(resource.getName().toLowerCase()); 502 String fileExtension = filename.lastIndexOf(".") > 0 ? filename.substring(filename.lastIndexOf(".") + 1) : ""; 503 if (filename.contains(value) && (allowedExtensions == null || allowedExtensions.size() == 0 || allowedExtensions.contains(fileExtension))) 504 { 505 matchingPaths.add(resource.getResourcePath()); 506 } 507 } 508 509 /** 510 * Get the user rights on the resource collection 511 * 512 * @param node The explorer node 513 * @return The user's rights 514 */ 515 protected Set<String> getUserRights(ExplorerNode node) 516 { 517 return _rightManager.getUserRights(_currentUserProvider.getUser(), node); 518 } 519 520 /** 521 * Check current user right on given explorer node 522 * 523 * @param id The id of the explorer node 524 * @param rightId The if of right to check 525 * @return true if user has right 526 */ 527 @Callable 528 public boolean hasRight(String id, String rightId) 529 { 530 UserIdentity user = _currentUserProvider.getUser(); 531 ExplorerNode node = _resolver.resolveById(id); 532 533 return _rightManager.hasRight(user, rightId, node) == RightResult.RIGHT_ALLOW; 534 } 535 536 /** 537 * Check lock on a Ametys object 538 * 539 * @param ao the Ametys object 540 * @return <code>false</code> if the Ametys object is locked and can not be 541 * edited or deleted 542 */ 543 public boolean checkLock(AmetysObject ao) 544 { 545 if (ao instanceof LockableAmetysObject) 546 { 547 LockableAmetysObject lockableAO = (LockableAmetysObject) ao; 548 if (lockableAO.isLocked()) 549 { 550 if (!LockHelper.isLockOwner(lockableAO, _currentUserProvider.getUser())) 551 { 552 return false; 553 } 554 555 if (ao instanceof JCRAmetysObject) 556 { 557 _addLockToken(((JCRAmetysObject) ao).getNode()); 558 } 559 } 560 561 } 562 563 return true; 564 } 565 566 private void _addLockToken(Node node) 567 { 568 try 569 { 570 if (node.isLocked()) 571 { 572 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 573 574 Lock lock = lockManager.getLock(node.getPath()); 575 Node lockHolder = lock.getNode(); 576 577 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 578 } 579 } 580 catch (RepositoryException e) 581 { 582 throw new AmetysRepositoryException("Unable to add lock token", e); 583 } 584 } 585 586 /** 587 * Check the user privilege on object 588 * 589 * @param object the object 590 * @param rightId the right id 591 * @throws IllegalAccessException if the user has no sufficient rights 592 */ 593 public void checkUserRight(AmetysObject object, String rightId) throws IllegalAccessException 594 { 595 ExplorerNode node; 596 if (object instanceof Resource) 597 { 598 node = object.getParent(); 599 } 600 else 601 { 602 node = (ExplorerNode) object; 603 } 604 605 if (_rightManager.hasRight(_currentUserProvider.getUser(), rightId, object) != RightResult.RIGHT_ALLOW) 606 { 607 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to access a privilege feature without convenient right [" + rightId 608 + ", /resources" + node.getExplorerPath() + "]"); 609 } 610 } 611 612 /** 613 * Retrieve the rights for the user 614 * 615 * @param user The user 616 * @param right The right 617 * @param object The object 618 * @return True if the user has the right 619 */ 620 public boolean getUserRight(UserIdentity user, String right, AmetysObject object) 621 { 622 AmetysObject explorerNode = object; 623 while (explorerNode != null && !(explorerNode instanceof ExplorerNode)) 624 { 625 explorerNode = explorerNode.getParent(); 626 } 627 if (explorerNode == null) 628 { 629 return false; 630 } 631 return _rightManager.hasRight(user, right, explorerNode) == RightResult.RIGHT_ALLOW; 632 } 633 634 /** 635 * Add a resource collection 636 * 637 * @param parentId The identifier of the parent in which the resource 638 * collection will be added 639 * @param desiredName The desired name for the resource collection 640 * @param renameIfExists If false, in case of existing name the resource 641 * collection will not be created, if true it will be created and 642 * renamed 643 * @return The result map with id, parentId, name and message keys 644 */ 645 @Callable 646 public Map<String, Object> addResourceCollection(String parentId, String desiredName, Boolean renameIfExists) 647 { 648 Map<String, Object> result = new HashMap<>(); 649 650 assert parentId != null; 651 652 AmetysObject object = _resolver.resolveById(parentId); 653 if (!(object instanceof ModifiableResourceCollection)) 654 { 655 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 656 } 657 658 List<String> errors = new LinkedList<>(); 659 ResourceCollection rc = addResourceCollection((ModifiableResourceCollection) object, desiredName, renameIfExists, errors); 660 661 if (!errors.isEmpty()) 662 { 663 result.put("message", errors.get(0)); 664 } 665 else 666 { 667 result.put("id", rc.getId()); 668 result.put("parentID", parentId); 669 result.put("name", rc.getName()); 670 } 671 672 return result; 673 } 674 675 /** 676 * Add a resource collection 677 * 678 * @param parent The parent collection in which the resource collection will 679 * be added 680 * @param desiredName The desired name for the resource collection 681 * @param renameIfExists If false, in case of existing name the resource 682 * collection will not be created, if true it will be created and 683 * renamed 684 * @param errors An optional list of possible error messages in case the 685 * creation failed. Possible values are: locked, already-exist. 686 * @return The created resource collection or null if creation failed 687 */ 688 public ResourceCollection addResourceCollection(ModifiableResourceCollection parent, String desiredName, Boolean renameIfExists, List<String> errors) 689 { 690 String originalName = desiredName; 691 692 // FIXME CMS-2297 693 // Check user right 694 // checkUserRight(object, "Plugin_Explorer_Folder_Add"); 695 696 if (!checkLock(parent)) 697 { 698 getLogger().warn("User '{}' try to modify collection '{}' but it is locked by another user", _currentUserProvider.getUser(), parent.getName()); 699 if (errors != null) 700 { 701 errors.add("locked"); 702 } 703 return null; 704 } 705 706 if (!renameIfExists && parent.hasChild(originalName)) 707 { 708 getLogger().warn("The object '{}' can not be renamed in '{}' : a object of same name already exists.", parent.getName(), originalName); 709 if (errors != null) 710 { 711 errors.add("already-exist"); 712 } 713 return null; 714 } 715 716 int index = 2; 717 String name = originalName; 718 while (parent.hasChild(name)) 719 { 720 name = originalName + " (" + index + ")"; 721 index++; 722 } 723 724 ResourceCollection child = parent.createChild(name, getResourceCollectionType()); 725 parent.saveChanges(); 726 727 // Notify listeners 728 Map<String, Object> eventParams = new HashMap<>(); 729 eventParams.put(ObservationConstants.ARGS_ID, child.getId()); 730 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 731 eventParams.put(ObservationConstants.ARGS_NAME, child.getName()); 732 eventParams.put(ObservationConstants.ARGS_PATH, child.getPath()); 733 734 _observationManager.notify(new Event(ObservationConstants.EVENT_COLLECTION_CREATED, _currentUserProvider.getUser(), eventParams)); 735 736 return child; 737 } 738 739 /** 740 * Get the type of child resource collection 741 * 742 * @return the type of child resource collection 743 */ 744 public String getResourceCollectionType() 745 { 746 return JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE; 747 } 748 749 /** 750 * Rename a resource, or resource collection 751 * 752 * @param id The id of the object to rename 753 * @param name The desired name to set to the object. 754 * @return The result map with id, name and message keys 755 * @throws RepositoryException If there is a repository error 756 */ 757 @Callable 758 public Map<String, Object> renameObject(String id, String name) throws RepositoryException 759 { 760 Map<String, Object> result = new HashMap<>(); 761 762 JCRAmetysObject object = _resolver.resolveById(id); 763 List<String> errors = new LinkedList<>(); 764 JCRAmetysObject newObject = renameObject(object, name, errors); 765 766 if (!errors.isEmpty()) 767 { 768 String error = errors.get(0); 769 result.put("message", error); 770 } 771 else 772 { 773 result.put("id", newObject.getId()); 774 result.put("name", name); 775 } 776 777 return result; 778 } 779 780 /** 781 * Rename a resource, or resource collection 782 * 783 * @param object The object to rename 784 * @param name The desired name to set to the object. 785 * @param errors An optional list of possible error messages in case the 786 * operation failed. Possible values are: locked, already-exist. 787 * @return The new object 788 * @throws RepositoryException If there is a repository error 789 */ 790 public JCRAmetysObject renameObject(JCRAmetysObject object, String name, List<String> errors) throws RepositoryException 791 { 792 assert object != null; 793 assert name != null; 794 795 String legalName = Text.escapeIllegalJcrChars(name); 796 797 // Check node is not the root node 798 if ("ametys-internal:resources".equals(object.getName())) 799 { 800 throw new IllegalStateException("The resources root node can not be renamed !"); 801 } 802 803 // FIXME CMS-2297 804 // Check user right 805 // checkUserRight (object, object instanceof Resource ? 806 // "Plugin_Explorer_File_Rename" : "Plugin_Explorer_Folder_Edit"); 807 808 Node node = object.getNode(); 809 String oldName = object.getName(); 810 String oldObjectPath = object.getPath(); 811 812 if (!checkLock(object)) 813 { 814 getLogger().warn("User '{}' is trying to rename object '{}' but it is locked by another user", _currentUserProvider.getUser(), object.getName()); 815 if (errors != null) 816 { 817 errors.add("locked"); 818 } 819 return null; 820 } 821 else if (node.getParent().hasNode(legalName)) 822 { 823 getLogger().warn("The object '{}' cannot be renamed in '{}' : an object with the same name already exists.", object.getName(), name); 824 if (errors != null) 825 { 826 errors.add("already-exist"); 827 } 828 return null; 829 } 830 else 831 { 832 String oldResourcePath = object instanceof Resource ? ((Resource) object).getResourcePath() : ((ResourceCollection) object).getExplorerPath(); 833 834 node.getSession().move(node.getPath(), node.getParent().getPath() + '/' + legalName); 835 node.getSession().save(); 836 837 // Resolve the new ametys object 838 JCRAmetysObject newObject = (JCRAmetysObject) _resolver.resolve(node, false); 839 840 // Notify listeners 841 Map<String, Object> eventParams = new HashMap<>(); 842 eventParams.put(ObservationConstants.ARGS_ID, newObject.getId()); 843 eventParams.put(ObservationConstants.ARGS_PARENT_ID, newObject.getParent().getId()); 844 eventParams.put(ObservationConstants.ARGS_NAME, newObject.getName()); 845 eventParams.put(ObservationConstants.ARGS_PATH, newObject.getPath()); 846 eventParams.put("object.old.name", oldName); 847 eventParams.put("object.old.path", oldObjectPath); 848 849 if (object instanceof Resource) 850 { 851 eventParams.put(ObservationConstants.ARGS_RESOURCE_PATH, ((Resource) object).getResourcePath()); 852 eventParams.put("resource.old.path", oldResourcePath); 853 } 854 else 855 { 856 eventParams.put(ObservationConstants.ARGS_EXPLORER_PATH, ((ResourceCollection) object).getExplorerPath()); 857 eventParams.put("explorer.old.path", oldResourcePath); 858 } 859 860 _observationManager.notify(new Event(object instanceof JCRResource ? ObservationConstants.EVENT_RESOURCE_RENAMED : ObservationConstants.EVENT_COLLECTION_RENAMED, 861 _currentUserProvider.getUser(), eventParams)); 862 863 return newObject; 864 } 865 } 866 867 /** 868 * Delete an object 869 * 870 * @param ids The list of identifiers for the objects to delete 871 * @return The result map with a message key in case of an error 872 */ 873 @Callable 874 public Map<String, Object> deleteObject(List<String> ids) 875 { 876 assert ids != null; 877 878 Map<String, Object> result = new HashMap<>(); 879 880 for (String id : ids) 881 { 882 RemovableAmetysObject object = (RemovableAmetysObject) _resolver.resolveById(id); 883 List<String> errors = new LinkedList<>(); 884 deleteObject(object, errors); 885 886 if (!errors.isEmpty()) 887 { 888 String error = errors.get(0); 889 result.put("message", error); 890 return result; 891 } 892 } 893 894 return result; 895 } 896 897 /** 898 * Delete an object 899 * 900 * @param object The object to delete 901 * @param errors An optional list of possible error messages in case the 902 * creation failed. Possible values are: locked. 903 * @return the parent id of the removed object 904 */ 905 public String deleteObject(RemovableAmetysObject object, List<String> errors) 906 { 907 // Check node is not the root node 908 if ("ametys-internal:resources".equals(object.getName())) 909 { 910 throw new IllegalStateException("The resources root node can not be deleted !"); 911 } 912 913 // Check user right 914 // FIXME CMS-2297 915 // checkUserRight(object, object instanceof Resource ? 916 // "Plugin_Explorer_File_Delete" : "Plugin_Explorer_Folder_Delete"); 917 918 if (!checkLock(object)) 919 { 920 getLogger().warn("User '{}' is trying to delete object '{}' but it is locked by another user", _currentUserProvider.getUser(), object.getName()); 921 if (errors != null) 922 { 923 errors.add("locked"); 924 } 925 return null; 926 } 927 928 ModifiableResourceCollection parent = object.getParent(); 929 String eventType = object instanceof Resource ? ObservationConstants.EVENT_RESOURCE_DELETED : ObservationConstants.EVENT_COLLECTION_DELETED; 930 String parentId = parent.getId(); 931 932 Map<String, Object> eventParams = new HashMap<>(); 933 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId); 934 eventParams.put(ObservationConstants.ARGS_ID, object.getId()); 935 eventParams.put(ObservationConstants.ARGS_NAME, object.getName()); 936 eventParams.put(ObservationConstants.ARGS_PATH, object.getPath()); 937 938 if (object instanceof Resource) 939 { 940 eventParams.put(ObservationConstants.ARGS_RESOURCE_PATH, ((Resource) object).getResourcePath()); 941 } 942 else 943 { 944 eventParams.put(ObservationConstants.ARGS_EXPLORER_PATH, ((ResourceCollection) object).getExplorerPath()); 945 } 946 947 object.remove(); 948 parent.saveChanges(); 949 950 _observationManager.notify(new Event(eventType, _currentUserProvider.getUser(), eventParams)); 951 952 return parentId; 953 } 954 955 /** 956 * Rename a resource 957 * 958 * @param id The id of the resource to rename 959 * @param name The desired name to set to the resource. 960 * @return The result map with id, name and message keys 961 * @throws RepositoryException If there is a repository error 962 */ 963 @Callable 964 public Map<String, Object> renameResource(String id, String name) throws RepositoryException 965 { 966 Map<String, Object> result = new HashMap<>(); 967 968 JCRResource resource = _resolver.resolveById(id); 969 List<String> errors = new LinkedList<>(); 970 971 Resource newResource = renameResource(resource, name, errors); 972 973 if (!errors.isEmpty()) 974 { 975 String error = errors.get(0); 976 result.put("message", error); 977 } 978 else 979 { 980 result.put("id", newResource.getId()); 981 result.put("name", name); 982 } 983 984 return result; 985 } 986 987 /** 988 * Rename a resource 989 * 990 * @param resource The resource to rename 991 * @param name The desired name to set to the resource. 992 * @param errors An optional list of possible error messages in case the 993 * operation failed. Possible values are: locked, already-exist. 994 * @return The new resource 995 * @throws RepositoryException If there is a repository error 996 */ 997 public JCRResource renameResource(JCRResource resource, String name, List<String> errors) throws RepositoryException 998 { 999 assert resource != null; 1000 assert name != null; 1001 1002 String legalName = Text.escapeIllegalJcrChars(name); 1003 1004 // FIXME API getNode should use the new rename method 1005 String oldName = resource.getName(); 1006 String oldPath = resource.getPath(); 1007 String oldResourcePath = resource.getResourcePath(); 1008 1009 Node node = resource.getNode(); 1010 1011 // FIXME CMS-2297 Check user right 1012 // checkUserRight (object, "Plugin_Explorer_File_Rename"); 1013 1014 // Check lock on resource 1015 if (!checkLock(resource)) 1016 { 1017 getLogger().warn("User '{}' is trying to rename resource '{}' but it is locked by another user", _currentUserProvider.getUser(), resource.getName()); 1018 if (errors != null) 1019 { 1020 errors.add("locked"); 1021 } 1022 return null; 1023 } 1024 1025 if (node.getParent().hasNode(legalName)) 1026 { 1027 getLogger().warn("The resource '{}' cannot be renamed in '{}' : an object with the same name already exists.", resource.getName(), name); 1028 if (errors != null) 1029 { 1030 errors.add("already-exist"); 1031 } 1032 return null; 1033 } 1034 1035 String mimeType = _cocoonContext.getMimeType(legalName.toLowerCase()); 1036 mimeType = mimeType == null ? "application/unknown" : mimeType; 1037 resource.setMimeType(mimeType); 1038 1039 node.getSession().move(node.getPath(), node.getParent().getPath() + '/' + legalName); 1040 1041 node.getSession().save(); 1042 1043 // Resolve the new ametys object 1044 JCRResource newObject = (JCRResource) _resolver.resolve(node, false); 1045 1046 // Notify listeners 1047 Map<String, Object> eventParams = new HashMap<>(); 1048 1049 eventParams.put(ObservationConstants.ARGS_ID, newObject.getId()); 1050 eventParams.put(ObservationConstants.ARGS_PARENT_ID, newObject.getParent().getId()); 1051 eventParams.put(ObservationConstants.ARGS_NAME, newObject.getName()); 1052 eventParams.put(ObservationConstants.ARGS_PATH, newObject.getPath()); 1053 eventParams.put("object.old.name", oldName); 1054 eventParams.put("object.old.path", oldPath); 1055 1056 eventParams.put(ObservationConstants.ARGS_RESOURCE_PATH, newObject.getResourcePath()); 1057 eventParams.put("resource.old.path", oldResourcePath); 1058 1059 _observationManager.notify(new Event(ObservationConstants.EVENT_RESOURCE_RENAMED, _currentUserProvider.getUser(), eventParams)); 1060 1061 return newObject; 1062 } 1063 1064 /** 1065 * Copy file resources 1066 * 1067 * @param ids The list of identifiers for the resources to copy 1068 * @param target The id of target to copy into 1069 * @return The result map with a message key in case of an error or with the 1070 * list of uncopied/copied resources 1071 * @throws RepositoryException If there is a repository error 1072 */ 1073 @Callable 1074 public Map<String, Object> copyResource(List<String> ids, String target) throws RepositoryException 1075 { 1076 assert ids != null; 1077 assert target != null; 1078 1079 AmetysObject object = _resolver.resolveById(target); 1080 if (!(object instanceof ModifiableResourceCollection)) 1081 { 1082 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 1083 } 1084 1085 return copyResource(ids, (ModifiableResourceCollection) object); 1086 } 1087 1088 /** 1089 * Copy file resources 1090 * 1091 * @param ids The list of identifiers for the resources to copy 1092 * @param target The target to copy into 1093 * @return The result map with a message key in case of an error or with the 1094 * list of uncopied/copied resources 1095 * @throws RepositoryException If there is a repository error 1096 */ 1097 public Map<String, Object> copyResource(List<String> ids, ModifiableResourceCollection target) throws RepositoryException 1098 { 1099 Map<String, Object> result = new HashMap<>(); 1100 List<String> uncopiedResources = new ArrayList<>(); 1101 List<String> copiedResourceIds = new ArrayList<>(); 1102 1103 assert ids != null; 1104 assert target != null; 1105 1106 if (!checkLock(target)) 1107 { 1108 getLogger().warn("User '{}' try to copy objet to '{}' but it is locked by another user", _currentUserProvider.getUser(), target.getName()); 1109 result.put("message", "locked"); 1110 return result; 1111 } 1112 1113 // FIXME CMS-2297 1114 // checkUserRight(object, "Plugin_Explorer_File_Add"); 1115 1116 for (String id : ids) 1117 { 1118 Resource resourceToCopy = _resolver.resolveById(id); 1119 String fileName = resourceToCopy.getName(); 1120 1121 if (target.hasChild(fileName)) 1122 { 1123 getLogger().warn("The resource '" + fileName + "' can not be copied : an object of same name already exists in the target collection"); 1124 result.put("message", "already-exist"); 1125 uncopiedResources.add(fileName); 1126 } 1127 else 1128 { 1129 ModifiableResource resource = createResource(target, fileName); 1130 1131 try (InputStream is = resourceToCopy.getInputStream()) 1132 { 1133 updateResource(resource, is, fileName); 1134 } 1135 catch (IOException e) 1136 { 1137 getLogger().warn("An error occurred while closing the ressource " + resource.getId(), e); 1138 } 1139 1140 copiedResourceIds.add(resource.getId()); 1141 } 1142 } 1143 1144 target.saveChanges(); 1145 1146 for (String id : copiedResourceIds) 1147 { 1148 ModifiableResource resource = _resolver.resolveById(id); 1149 1150 // Create version 1151 checkpoint(resource); 1152 1153 // Notify listeners 1154 Map<String, Object> eventParams = new HashMap<>(); 1155 Map<String, Resource> addedResource = new HashMap<>(); 1156 addedResource.put(resource.getId(), resource); 1157 eventParams.put(ObservationConstants.ARGS_RESOURCES, addedResource); 1158 eventParams.put(ObservationConstants.ARGS_PARENT_ID, target.getId()); 1159 eventParams.put(ObservationConstants.ARGS_PARENT_PATH, target.getPath()); 1160 1161 _observationManager.notify(new Event(ObservationConstants.EVENT_RESOURCE_CREATED, _currentUserProvider.getUser(), eventParams)); 1162 } 1163 1164 if (uncopiedResources.size() > 0) 1165 { 1166 result.put("uncopied-resources", uncopiedResources); 1167 } 1168 1169 if (copiedResourceIds.size() > 0) 1170 { 1171 result.put("copied-resources", copiedResourceIds); 1172 } 1173 1174 return result; 1175 } 1176 1177 /** 1178 * Move objects 1179 * 1180 * @param ids The list of identifiers for the objects to move 1181 * @param targetId The id of target to move into 1182 * @return The result map with a message key in case of an error or with the 1183 * list of unmoved/moved objects 1184 * @throws RepositoryException If there is a repository error 1185 */ 1186 @Callable 1187 public Map<String, Object> moveObject(List<String> ids, String targetId) throws RepositoryException 1188 { 1189 assert ids != null; 1190 assert targetId != null; 1191 1192 AmetysObject target = _resolver.resolveById(targetId); 1193 if (!(target instanceof JCRTraversableAmetysObject)) 1194 { 1195 throw new IllegalClassException(JCRTraversableAmetysObject.class, target.getClass()); 1196 } 1197 1198 return moveObject(ids, (JCRTraversableAmetysObject) target); 1199 } 1200 1201 /** 1202 * Move objects 1203 * 1204 * @param ids The list of identifiers for the objects to move 1205 * @param targetNode The target to move into 1206 * @return The result map with a message key in case of an error or with the 1207 * list of unmoved/moved objects 1208 * @throws RepositoryException If there is a repository error 1209 */ 1210 public Map<String, Object> moveObject(List<String> ids, JCRTraversableAmetysObject targetNode) throws RepositoryException 1211 { 1212 Map<String, Object> result = new HashMap<>(); 1213 List<String> unmovedObjects = new ArrayList<>(); 1214 List<String> movedObjectIds = new ArrayList<>(); 1215 1216 assert ids != null; 1217 assert targetNode != null; 1218 1219 if (!checkLock(targetNode)) 1220 { 1221 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to move objet to '" + targetNode.getName() + "' but it is locked by another user"); 1222 result.put("message", "locked"); 1223 return result; 1224 } 1225 1226 for (String id : ids) 1227 { 1228 JCRAmetysObject object = (JCRAmetysObject) _resolver.resolveById(id); 1229 1230 if (targetNode.hasChild(object.getName())) 1231 { 1232 getLogger().warn("The object '" + object.getName() + "' can not be moved : a object of same name already exists in the target collection"); 1233 result.put("message", "already-exist"); 1234 unmovedObjects.add(object.getName()); 1235 } 1236 else 1237 { 1238 String oldResourcePath = object instanceof Resource ? ((Resource) object).getResourcePath() : ((ExplorerNode) object).getExplorerPath(); 1239 1240 Map<String, Object> eventParams = new HashMap<>(); 1241 eventParams.put("object.old.path", object.getPath()); 1242 1243 Session session = object.getNode().getSession(); 1244 session.move(object.getNode().getPath(), targetNode.getNode().getPath() + "/" + object.getNode().getName()); 1245 1246 if (object instanceof Resource) 1247 { 1248 eventParams.put(ObservationConstants.ARGS_RESOURCE_PATH, ((Resource) object).getResourcePath()); 1249 eventParams.put("resource.old.path", oldResourcePath); 1250 } 1251 else 1252 { 1253 eventParams.put("explorer.old.path", oldResourcePath); 1254 } 1255 1256 eventParams.put(ObservationConstants.ARGS_PATH, ((ExplorerNode) targetNode).getExplorerPath() + "/" + object.getName()); 1257 eventParams.put(ObservationConstants.ARGS_PARENT_ID, ((ExplorerNode) targetNode).getId()); 1258 eventParams.put(ObservationConstants.ARGS_ID, id); 1259 eventParams.put(ObservationConstants.ARGS_NAME, object.getName()); 1260 1261 session.save(); 1262 movedObjectIds.add(object.getId()); 1263 1264 if (object instanceof Resource) 1265 { 1266 eventParams.put(ObservationConstants.ARGS_RESOURCE_PATH, ((Resource) object).getResourcePath()); 1267 eventParams.put("resource.old.path", oldResourcePath); 1268 _observationManager.notify(new Event(ObservationConstants.EVENT_RESOURCE_MOVED, _currentUserProvider.getUser(), eventParams)); 1269 } 1270 else if (object instanceof ResourceCollection) 1271 { 1272 _observationManager.notify(new Event(ObservationConstants.EVENT_COLLECTION_MOVED, _currentUserProvider.getUser(), eventParams)); 1273 } 1274 else if (object instanceof Calendar) 1275 { 1276 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_MOVED, _currentUserProvider.getUser(), eventParams)); 1277 } 1278 else if (object instanceof JCRThread) 1279 { 1280 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_MOVED, _currentUserProvider.getUser(), eventParams)); 1281 } 1282 else 1283 { 1284 getLogger().warn("Object " + object.getId() + " of class '" + object.getClass().getName() + "' was moved. This type is unknown."); 1285 } 1286 } 1287 } 1288 1289 if (!unmovedObjects.isEmpty()) 1290 { 1291 result.put("unmoved-objects", unmovedObjects); 1292 } 1293 1294 if (!movedObjectIds.isEmpty()) 1295 { 1296 result.put("moved-objects", movedObjectIds); 1297 } 1298 1299 return result; 1300 } 1301 1302 /** 1303 * Get the history versions of a resource 1304 * 1305 * @param id the id of resource 1306 * @return The versions 1307 * @throws RepositoryException if an error occurred 1308 */ 1309 @Callable 1310 public List<Map<String, Object>> resourceHistory(String id) throws RepositoryException 1311 { 1312 JCRResource resource = _resolver.resolveById(id); 1313 1314 List<Map<String, Object>> versions = new ArrayList<>(); 1315 1316 List<VersionInformation> versionsInfo = new ArrayList<>(); 1317 1318 for (String revision : resource.getAllRevisions()) 1319 { 1320 VersionInformation versionInformation = new VersionInformation(revision, resource.getRevisionTimestamp(revision)); 1321 1322 for (String label : resource.getLabels(revision)) 1323 { 1324 versionInformation.addLabel(label); 1325 } 1326 1327 versionsInfo.add(versionInformation); 1328 } 1329 1330 // Sort by date descendant 1331 Collections.sort(versionsInfo, new Comparator<VersionInformation>() 1332 { 1333 public int compare(VersionInformation o1, VersionInformation o2) 1334 { 1335 try 1336 { 1337 return -o1.getCreatedAt().compareTo(o2.getCreatedAt()); 1338 } 1339 catch (RepositoryException e) 1340 { 1341 throw new RuntimeException("Unable to retrieve a creation date", e); 1342 } 1343 } 1344 }); 1345 1346 for (VersionInformation versionInformation : versionsInfo) 1347 { 1348 Map<String, Object> version = new HashMap<>(); 1349 1350 for (String label : versionInformation.getLabels()) 1351 { 1352 version.put(label, label); 1353 } 1354 1355 version.put("rawName", versionInformation.getVersionRawName()); 1356 version.put("name", versionInformation.getVersionName()); 1357 version.put("createdAt", DateUtils.dateToString(versionInformation.getCreatedAt())); 1358 1359 try 1360 { 1361 resource.switchToRevision(versionInformation.getVersionRawName()); 1362 UserIdentity author = resource.getLastContributor(); 1363 version.put("author", _userHelper.user2json(author)); 1364 } 1365 catch (AmetysRepositoryException e) 1366 { 1367 // Do nothing. Can append with old version history (before 4.0) 1368 } 1369 1370 versions.add(version); 1371 } 1372 1373 return versions; 1374 } 1375 1376 /** 1377 * This action restores an old version of a {@link Resource}. 1378 * 1379 * @param id the id of resource 1380 * @param versionName the name of version to restore 1381 */ 1382 @Callable 1383 public void restoreResource(String id, String versionName) 1384 { 1385 JCRResource resource = _resolver.resolveById(id); 1386 resource.restoreFromRevision(versionName); 1387 1388 resource.setLastContributor(_currentUserProvider.getUser()); 1389 resource.setLastModified(new Date()); 1390 resource.saveChanges(); 1391 resource.checkpoint(); 1392 } 1393 1394 /** 1395 * Determines if a resource with given name already exists 1396 * 1397 * @param parentId the id of parent collection 1398 * @param name the name of resource 1399 * @return true if a resource with same name exists 1400 */ 1401 @Callable 1402 public boolean resourceExists(String parentId, String name) 1403 { 1404 return resourceExists((TraversableAmetysObject) _resolver.resolveById(parentId), name); 1405 } 1406 1407 /** 1408 * Determines if a resource with given name already exists 1409 * 1410 * @param parent the parent collection 1411 * @param name the name of resource 1412 * @return true if a resource with same name exists 1413 */ 1414 public boolean resourceExists(TraversableAmetysObject parent, String name) 1415 { 1416 return parent.hasChild(name); 1417 } 1418 1419 /** 1420 * Set the Dublin Core metadata of a {@link Resource}. 1421 * 1422 * @param resourceId the id of resource 1423 * @param values the DC values 1424 * @throws ProcessingException if an error occurred 1425 */ 1426 @Callable 1427 public void setDCMetadata(String resourceId, Map<String, Object> values) throws ProcessingException 1428 { 1429 setDCMetadata((ModifiableResource) _resolver.resolveById(resourceId), values); 1430 } 1431 1432 /** 1433 * Set the Dublin Core metadata of a {@link Resource}. 1434 * 1435 * @param resource the resource 1436 * @param values the DC values 1437 */ 1438 public void setDCMetadata(ModifiableResource resource, Map<String, Object> values) 1439 { 1440 String title = (String) values.get("dc_title"); 1441 String creator = (String) values.get("dc_creator"); 1442 Object subject = values.get("dc_subject"); 1443 @SuppressWarnings("unchecked") 1444 List<String> subjects = subject instanceof List ? (List<String>) subject : Collections.emptyList(); 1445 String description = (String) values.get("dc_description"); 1446 String publisher = (String) values.get("dc_publisher"); 1447 String contributor = (String) values.get("dc_contributor"); 1448 String dateStr = (String) values.get("dc_date"); 1449 String type = (String) values.get("dc_type"); 1450 String source = (String) values.get("dc_source"); 1451 String language = (String) values.get("dc_language"); 1452 String relation = (String) values.get("dc_relation"); 1453 String coverage = (String) values.get("dc_coverage"); 1454 String rights = (String) values.get("dc_rights"); 1455 1456 try 1457 { 1458 // FIXME CMS-2297 Check user right 1459 // checkUserRight (object, "Plugin_Explorer_File_Edit_DC_Metadata"); 1460 1461 _updateTitleIfNeeded(resource, title); 1462 _updateCreatorIfNeeded(resource, creator); 1463 _updateDateIfNeeded(resource, dateStr); 1464 _updateDCSubjectIfNeeded(resource, subjects); 1465 _updateDescriptionIfNeeded(resource, description); 1466 _updatePublisherIfNeeded(resource, publisher); 1467 _updateContributorIfNeeded(resource, contributor); 1468 _updateTypeIfNeeded(resource, type); 1469 _updateSourceIfNeeded(resource, source); 1470 _updateLanguageIfNeeded(resource, language); 1471 _updateRelationIfNeeded(resource, relation); 1472 _updateCoverageIfNeeded(resource, coverage); 1473 _updateRightsIfNeeded(resource, rights); 1474 1475 _saveChangesIfNeeded(resource); 1476 1477 } 1478 catch (AmetysRepositoryException e) 1479 { 1480 String errorMsg = String.format("Exception while trying to set Dublin Core metadata on resource %s", resource.getId()); 1481 getLogger().error(errorMsg, e); 1482 throw new AmetysRepositoryException(errorMsg, e); 1483 } 1484 } 1485 1486 private void _updateTitleIfNeeded(ModifiableResource resource, String title) 1487 { 1488 if (!Objects.equals(title, resource.getDCTitle())) 1489 { 1490 resource.setDCTitle(title); 1491 } 1492 } 1493 1494 private void _updateCreatorIfNeeded(ModifiableResource resource, String creator) 1495 { 1496 if (!Objects.equals(creator, resource.getDCCreator())) 1497 { 1498 resource.setDCCreator(creator); 1499 } 1500 } 1501 1502 private void _updateDateIfNeeded(ModifiableResource resource, String dateStr) 1503 { 1504 Date date = null; 1505 if (StringUtils.isNotEmpty(dateStr)) 1506 { 1507 LocalDateTime ldt = LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_DATE_TIME); 1508 date = DateUtils.asDate(ldt); 1509 } 1510 1511 if (!Objects.equals(date, resource.getDCDate())) 1512 { 1513 resource.setDCDate(date); 1514 } 1515 } 1516 1517 private void _updateRightsIfNeeded(ModifiableResource resource, String rights) 1518 { 1519 if (!Objects.equals(rights, resource.getDCRights())) 1520 { 1521 resource.setDCRights(rights); 1522 } 1523 } 1524 1525 private void _updateCoverageIfNeeded(ModifiableResource resource, String coverage) 1526 { 1527 if (!Objects.equals(coverage, resource.getDCCoverage())) 1528 { 1529 resource.setDCCoverage(coverage); 1530 } 1531 } 1532 1533 private void _updateRelationIfNeeded(ModifiableResource resource, String relation) 1534 { 1535 if (!Objects.equals(relation, resource.getDCRelation())) 1536 { 1537 resource.setDCRelation(relation); 1538 } 1539 } 1540 1541 private void _updateLanguageIfNeeded(ModifiableResource resource, String language) 1542 { 1543 if (!Objects.equals(language, resource.getDCLanguage())) 1544 { 1545 resource.setDCLanguage(language); 1546 } 1547 } 1548 1549 private void _updateSourceIfNeeded(ModifiableResource resource, String source) 1550 { 1551 if (!Objects.equals(source, resource.getDCSource())) 1552 { 1553 resource.setDCSource(source); 1554 } 1555 } 1556 1557 private void _updateTypeIfNeeded(ModifiableResource resource, String type) 1558 { 1559 if (!Objects.equals(type, resource.getDCType())) 1560 { 1561 resource.setDCType(type); 1562 } 1563 } 1564 1565 private void _updateContributorIfNeeded(ModifiableResource resource, String contributor) 1566 { 1567 if (!Objects.equals(contributor, resource.getDCContributor())) 1568 { 1569 resource.setDCContributor(contributor); 1570 } 1571 } 1572 1573 private void _updatePublisherIfNeeded(ModifiableResource resource, String publisher) 1574 { 1575 if (!Objects.equals(publisher, resource.getDCPublisher())) 1576 { 1577 resource.setDCPublisher(publisher); 1578 } 1579 } 1580 1581 private void _updateDescriptionIfNeeded(ModifiableResource resource, String description) 1582 { 1583 if (!Objects.equals(description, resource.getDCDescription())) 1584 { 1585 resource.setDCDescription(description); 1586 } 1587 } 1588 1589 private void _updateDCSubjectIfNeeded(ModifiableResource resource, List<String> subjects) 1590 { 1591 String[] trimSubject = subjects.stream().map(StringUtils::trim).toArray(String[]::new); 1592 if (!Objects.deepEquals(trimSubject, resource.getDCSubject())) 1593 { 1594 resource.setDCSubject(trimSubject); 1595 } 1596 } 1597 1598 private void _saveChangesIfNeeded(ModifiableResource resource) 1599 { 1600 if (resource.needsSave()) 1601 { 1602 resource.setLastContributor(_currentUserProvider.getUser()); 1603 resource.setLastModified(new Date()); 1604 1605 ModifiableResourceCollection parent = resource.getParent(); 1606 parent.saveChanges(); 1607 1608 if (resource instanceof VersionableAmetysObject) 1609 { 1610 // Create first version 1611 ((VersionableAmetysObject) resource).checkpoint(); 1612 } 1613 1614 // Notify listeners of resource update. 1615 Map<String, Object> eventParams = new HashMap<>(); 1616 eventParams.put(ObservationConstants.ARGS_ID, resource.getId()); 1617 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 1618 eventParams.put(ObservationConstants.ARGS_NAME, resource.getName()); 1619 eventParams.put(ObservationConstants.ARGS_PATH, resource.getPath()); 1620 1621 _observationManager.notify(new Event(ObservationConstants.EVENT_RESOURCE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1622 } 1623 } 1624 1625 /** 1626 * Get the DublinCore metadata of a {@link DublinCoreAwareAmetysObject} 1627 * 1628 * @param id the id of resource 1629 * @return the DC metadata 1630 */ 1631 @Callable 1632 public Map<String, Object> getDCMetadata(String id) 1633 { 1634 AmetysObject object = _resolver.resolveById(id); 1635 if (object instanceof DublinCoreAwareAmetysObject) 1636 { 1637 DublinCoreAwareAmetysObject dcObject = (DublinCoreAwareAmetysObject) object; 1638 1639 Map<String, Object> metadata = new HashMap<>(); 1640 1641 metadata.put("id", id); 1642 1643 Map<String, Object> values = new HashMap<>(); 1644 1645 values.put("dc_title", dcObject.getDCTitle()); 1646 values.put("dc_creator", dcObject.getDCCreator()); 1647 values.put("dc_subject", dcObject.getDCSubject()); 1648 values.put("dc_description", dcObject.getDCDescription()); 1649 values.put("dc_type", dcObject.getDCType()); 1650 values.put("dc_publisher", dcObject.getDCPublisher()); 1651 values.put("dc_contributor", dcObject.getDCContributor()); 1652 values.put("dc_date", DateUtils.dateToString(dcObject.getDCDate())); 1653 values.put("dc_format", dcObject.getDCFormat()); 1654 values.put("dc_identifier", dcObject.getDCIdentifier()); 1655 values.put("dc_source", dcObject.getDCSource()); 1656 values.put("dc_language", dcObject.getDCLanguage()); 1657 values.put("dc_relation", dcObject.getDCRelation()); 1658 values.put("dc_coverage", dcObject.getDCCoverage()); 1659 values.put("dc_rights", dcObject.getDCRights()); 1660 1661 metadata.put("values", values); 1662 return metadata; 1663 } 1664 else 1665 { 1666 throw new IllegalArgumentException("Object of id " + id + " is not Dublin Core aware."); 1667 } 1668 } 1669 1670 /** 1671 * Creates a new CMIS folder (see {@link CMISRootResourcesCollection}) 1672 * 1673 * @param parentId the id of parent folder 1674 * @param originalName the original name if CMIS folder 1675 * @param url The url of CMIS repository 1676 * @param login The user's login to access CMIS repository 1677 * @param password The user's password to access CMIS repository 1678 * @param repoId The id of CMIS repository 1679 * @param renameIfExists true to automatically renamed CMIS folder if 1680 * requested name already exists 1681 * @return the result map with id of created node 1682 */ 1683 @Callable 1684 public Map<String, Object> addCMISCollection(String parentId, String originalName, String url, String login, String password, String repoId, boolean renameIfExists) 1685 { 1686 Map<String, Object> result = new HashMap<>(); 1687 1688 AmetysObject object = _resolver.resolveById(parentId); 1689 if (!(object instanceof ModifiableResourceCollection)) 1690 { 1691 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 1692 } 1693 1694 ModifiableResourceCollection collection = (ModifiableResourceCollection) object; 1695 1696 // FIXME CMS-2297 1697 // checkUserRight(object, "Plugin_Explorer_CMIS_Add"); 1698 1699 if (!checkLock(collection)) 1700 { 1701 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify collection '" + object.getName() + "' but it is locked by another user"); 1702 result.put("message", "locked"); 1703 return result; 1704 } 1705 1706 if (!renameIfExists && collection.hasChild(originalName)) 1707 { 1708 getLogger().warn("The object '" + object.getName() + "' can not be renamed in '" + originalName + "' : a object of same name already exists."); 1709 result.put("message", "already-exist"); 1710 return result; 1711 } 1712 1713 int index = 1; 1714 String name = originalName; 1715 while (collection.hasChild(name)) 1716 { 1717 name = originalName + " (" + index + ")"; 1718 index++; 1719 } 1720 1721 CMISRootResourcesCollection child = collection.createChild(name, CMISTreeFactory.CMIS_ROOT_COLLECTION_NODETYPE); 1722 1723 child.setRepositoryUrl(url); 1724 child.setRepositoryId(repoId); 1725 child.setUser(login); 1726 child.setPassword(password); 1727 1728 collection.saveChanges(); 1729 1730 result.put("id", child.getId()); 1731 result.put("parentID", parentId); 1732 result.put("name", name); 1733 1734 // Notify listeners 1735 Map<String, Object> eventParams = new HashMap<>(); 1736 eventParams.put(ObservationConstants.ARGS_ID, child.getId()); 1737 eventParams.put(ObservationConstants.ARGS_PARENT_ID, object.getId()); 1738 eventParams.put(ObservationConstants.ARGS_NAME, child.getName()); 1739 eventParams.put(ObservationConstants.ARGS_PATH, child.getPath()); 1740 1741 _observationManager.notify(new Event(ObservationConstants.EVENT_COLLECTION_CREATED, _currentUserProvider.getUser(), eventParams)); 1742 1743 return result; 1744 } 1745 1746 /** 1747 * Edits a CMIS folder (see {@link CMISRootResourcesCollection}) 1748 * 1749 * @param id the id of CMIS folder 1750 * @param url The url of CMIS repository 1751 * @param login The user's login to access CMIS repository 1752 * @param password The user's password to access CMIS repository 1753 * @param repoId The id of CMIS repository 1754 * @return the result map with id of edited node 1755 */ 1756 @Callable 1757 public Map<String, Object> editCMISCollection(String id, String url, String login, String password, String repoId) 1758 { 1759 Map<String, Object> result = new HashMap<>(); 1760 1761 CMISRootResourcesCollection object = _resolver.resolveById(id); 1762 1763 // FIXME CMS-2297 1764 // checkUserRight(object, "Plugin_Explorer_CMIS_Add"); 1765 1766 object.setRepositoryUrl(url); 1767 object.setRepositoryId(repoId); 1768 object.setUser(login); 1769 object.setPassword(password); 1770 1771 object.saveChanges(); 1772 1773 // Notify listeners 1774 Map<String, Object> eventParams = new HashMap<>(); 1775 eventParams.put(ObservationConstants.ARGS_ID, object.getId()); 1776 1777 _observationManager.notify(new Event(ObservationConstants.EVENT_CMIS_COLLECTION_UPDATED, _currentUserProvider.getUser(), eventParams)); 1778 1779 result.put("id", object.getId()); 1780 1781 return result; 1782 } 1783 1784 /** 1785 * Determines if a object is a {@link CMISRootResourcesCollection} 1786 * 1787 * @param id The id of object 1788 * @return true if it is a CMIS root folder 1789 */ 1790 @Callable 1791 public boolean isCMISCollection(String id) 1792 { 1793 ResourceCollection collection = _resolver.resolveById(id); 1794 return collection instanceof CMISRootResourcesCollection; 1795 } 1796 1797 /** 1798 * Get the CMIS properties of collection 1799 * 1800 * @param id The id of CMIS collection 1801 * @return the CMIS properties to access CMIS repository 1802 */ 1803 @Callable 1804 public Map<String, String> getCMISProperties(String id) 1805 { 1806 Map<String, String> properties = new HashMap<>(); 1807 1808 CMISRootResourcesCollection cmis = _resolver.resolveById(id); 1809 1810 properties.put("id", cmis.getId()); 1811 properties.put("name", cmis.getName()); 1812 properties.put("url", cmis.getRepositoryUrl()); 1813 properties.put("login", cmis.getUser()); 1814 properties.put("password", cmis.getPassword()); 1815 properties.put("repoId", cmis.getRepositoryId()); 1816 1817 return properties; 1818 } 1819 1820 /** 1821 * Updates resource input stream and metadata 1822 * 1823 * @param resource The resource 1824 * @param is The resource input stream 1825 * @param fileName The file name 1826 */ 1827 public void updateResource(ModifiableResource resource, InputStream is, String fileName) 1828 { 1829 UserIdentity author = _currentUserProvider.getUser(); 1830 1831 String mimeType = _cocoonContext.getMimeType(fileName.toLowerCase()); 1832 mimeType = mimeType == null ? "application/unknown" : mimeType; 1833 1834 resource.setData(is, mimeType, new Date(), author); 1835 resource.setLastModified(new Date()); 1836 1837 extractResourceMetadata(resource, mimeType); 1838 } 1839 1840 /** 1841 * Extract the resource's metadata and populate the object accordingly. 1842 * 1843 * @param resource the resource to populate. 1844 * @param mimeType the resource MIME type. 1845 */ 1846 public void extractResourceMetadata(ModifiableResource resource, String mimeType) 1847 { 1848 try (InputStream is = resource.getInputStream()) 1849 { 1850 Metadata metadata = new Metadata(); 1851 1852 try (Reader reader = _tikaProvider.getTika().parse(is, metadata)) 1853 { 1854 IOUtils.copy(reader, NullOutputStream.NULL_OUTPUT_STREAM, StandardCharsets.UTF_8); 1855 1856 Collection<ResourceMetadataPopulator> populators = _metadataPopulatorEP.getPopulators(mimeType); 1857 for (ResourceMetadataPopulator populator : populators) 1858 { 1859 populator.populate(resource, metadata); 1860 } 1861 } 1862 } 1863 catch (Exception e) 1864 { 1865 getLogger().error("Error populating the metadata of resource " + resource.getId(), e); 1866 } 1867 } 1868 1869 /** 1870 * Creates a new version 1871 * 1872 * @param resource the resource 1873 */ 1874 public void checkpoint(ModifiableResource resource) 1875 { 1876 if (resource instanceof VersionableAmetysObject) 1877 { 1878 // Create first version 1879 ((VersionableAmetysObject) resource).checkpoint(); 1880 } 1881 } 1882 1883 /** 1884 * Creates a {@link ModifiableResource} under current 1885 * {@link ModifiableResourceCollection}. 1886 * 1887 * @param collection the parent {@link ModifiableResourceCollection} 1888 * @param name the name of the child resource 1889 * @return the new resource created. 1890 * @throws AmetysRepositoryException if an error occurs. 1891 * @throws RepositoryIntegrityViolationException if an object with the same 1892 * name already exists and same name siblings is not allowed. 1893 */ 1894 public ModifiableResource createResource(ModifiableResourceCollection collection, String name) 1895 { 1896 return collection.createChild(name, collection.getResourceType()); 1897 } 1898 1899 // -------------------------------- COMMENTS ------------------------------- 1900 /** 1901 * Add a comment to a resource 1902 * 1903 * @param resourceId The id of the resource 1904 * @param comment The comment 1905 * @return the result 1906 */ 1907 @Callable 1908 public Map<String, Object> addComment(String resourceId, String comment) 1909 { 1910 Map<String, Object> result = new HashMap<>(); 1911 1912 AmetysObject ao = _resolver.resolveById(resourceId); 1913 UserIdentity currentUser = _currentUserProvider.getUser(); 1914 1915 if (!(ao instanceof CommentableResource)) 1916 { 1917 throw new IllegalClassException(CommentableResource.class, ao.getClass()); 1918 } 1919 1920 if (!checkLock(ao)) 1921 { 1922 getLogger().warn("User '{}' is trying to add a comment on resource '{}' but it is locked by another user", currentUser, resourceId); 1923 result.put("message", "locked"); 1924 return result; 1925 } 1926 1927 // Check comment rights 1928 CommentableResource resource = (CommentableResource) ao; 1929 1930 if (!_canAddComment(currentUser, resource)) 1931 { 1932 getLogger().warn("User '{}' is trying to add a comment on resource '{}' without the convient right.", currentUser, resourceId); 1933 result.put("message", "rights"); 1934 return result; 1935 } 1936 1937 org.ametys.plugins.explorer.threads.Thread comments = resource.getComments(true); 1938 1939 if (!(comments instanceof JCRThread)) 1940 { 1941 throw new IllegalClassException(JCRThread.class, comments.getClass()); 1942 } 1943 1944 JCRThread jcrComments = (JCRThread) comments; 1945 1946 JCRPost post = jcrComments.createChild(JCRPostFactory.POST_NODENAME, JCRPostFactory.POST_NODETYPE); 1947 1948 _setComment(post, comment); 1949 1950 post.setAuthor(currentUser); 1951 Date now = new Date(); 1952 post.setCreationDate(now); 1953 post.setLastModified(now); 1954 1955 jcrComments.markAsRead(currentUser); 1956 jcrComments.saveChanges(); 1957 1958 // Notify listeners 1959 Map<String, Object> eventParams = new HashMap<>(); 1960 eventParams.put(ObservationConstants.ARGS_ID, resourceId); 1961 eventParams.put(ObservationConstants.ARGS_POST, post); 1962 _observationManager.notify(new Event(ObservationConstants.EVENT_RESOURCE_COMMENTED, currentUser, eventParams)); 1963 1964 result.put("id", resourceId); 1965 result.put("commentId", post.getId()); 1966 1967 return result; 1968 } 1969 1970 /** 1971 * Indicates if an user can add a comment of a given resource 1972 * 1973 * @param user An user identity 1974 * @param resource The resource to comment 1975 * @return True if the user can add a comment 1976 */ 1977 protected boolean _canAddComment(UserIdentity user, CommentableResource resource) 1978 { 1979 ExplorerNode node = resource.getParent(); 1980 return _rightManager.hasRight(user, "Plugin_Explorer_File_Comment", node) == RightResult.RIGHT_ALLOW 1981 || _rightManager.hasRight(user, "Plugin_Explorer_File_Moderate_Comments", node) == RightResult.RIGHT_ALLOW; 1982 } 1983 1984 /** 1985 * Edit the comment of a resource 1986 * 1987 * @param resourceId The id of the resource 1988 * @param commentId The id of comment to edit 1989 * @param comment The comment 1990 * @return the result 1991 */ 1992 @Callable 1993 public Map<String, Object> editComment(String resourceId, String commentId, String comment) 1994 { 1995 Map<String, Object> result = new HashMap<>(); 1996 1997 AmetysObject ao = _resolver.resolveById(resourceId); 1998 UserIdentity currentUser = _currentUserProvider.getUser(); 1999 2000 if (!(ao instanceof CommentableResource)) 2001 { 2002 throw new IllegalClassException(CommentableResource.class, ao.getClass()); 2003 } 2004 2005 if (!checkLock(ao)) 2006 { 2007 getLogger().warn("User '{}' is trying to edit comment '{}' on resource '{}' but it is locked by another user", currentUser, commentId, resourceId); 2008 result.put("message", "locked"); 2009 return result; 2010 } 2011 2012 CommentableResource resource = (CommentableResource) ao; 2013 JCRPost post = _resolver.resolveById(commentId); 2014 2015 // Check comment rights 2016 if (!_canEditComment(currentUser, resource, post)) 2017 { 2018 getLogger().warn("User '{}' is trying to add a comment on resource '{}' without the convient right.", currentUser, resourceId); 2019 result.put("message", "rights"); 2020 return result; 2021 } 2022 2023 _setComment(post, comment); 2024 post.setLastModified(new Date()); 2025 post.saveChanges(); 2026 2027 result.put("id", resourceId); 2028 result.put("commentId", commentId); 2029 2030 return result; 2031 } 2032 2033 /** 2034 * Indicates if an user can edit a comment of a given resource 2035 * 2036 * @param user An user identity 2037 * @param resource The resource 2038 * @param comment The comment to edit 2039 * @return True if the user can edit the comment 2040 */ 2041 protected boolean _canEditComment(UserIdentity user, CommentableResource resource, JCRPost comment) 2042 { 2043 ExplorerNode node = resource.getParent(); 2044 // Comment right + owner OR Moderate right 2045 return (_rightManager.hasRight(user, "Plugin_Explorer_File_Comment", node) == RightResult.RIGHT_ALLOW && comment.getAuthor().equals(user)) 2046 || _rightManager.hasRight(user, "Plugin_Explorer_File_Moderate_Comments", node) == RightResult.RIGHT_ALLOW; 2047 } 2048 2049 /** 2050 * Update the content of a comment 2051 * 2052 * @param comment The comment to update 2053 * @param content The content as string 2054 */ 2055 protected void _setComment(JCRPost comment, String content) 2056 { 2057 try 2058 { 2059 ModifiableRichText richText = comment.getContent(); 2060 2061 richText.setMimeType("text/plain"); 2062 richText.setLastModified(new Date()); 2063 richText.setInputStream(new ByteArrayInputStream(content.getBytes("UTF-8"))); 2064 } 2065 catch (IOException e) 2066 { 2067 throw new AmetysRepositoryException("Failed to set post rich text", e); 2068 } 2069 } 2070 2071 /** 2072 * Get the content of a comment as a String 2073 * 2074 * @param post the post 2075 * @return The content as String 2076 * @throws AmetysRepositoryException if failed to parse comment 2077 */ 2078 protected String _getComment(JCRPost post) throws AmetysRepositoryException 2079 { 2080 try 2081 { 2082 ModifiableRichText richText = post.getContent(); 2083 return IOUtils.toString(richText.getInputStream(), "UTF-8"); 2084 } 2085 catch (IOException e) 2086 { 2087 throw new AmetysRepositoryException("Failed to get post rich text", e); 2088 } 2089 } 2090 2091 /** 2092 * Get the content of a comment to edit as a String 2093 * 2094 * @param post the post 2095 * @return The content as String 2096 * @throws AmetysRepositoryException if failed to parse comment 2097 */ 2098 protected String _getCommentForEditing(JCRPost post) throws AmetysRepositoryException 2099 { 2100 return _getComment(post); 2101 } 2102 2103 /** 2104 * Delete the comment of a resource 2105 * 2106 * @param resourceId The id of the resource 2107 * @param commentId The id of comment to delete 2108 * @return the result 2109 */ 2110 @Callable 2111 public Map<String, Object> deleteComment(String resourceId, String commentId) 2112 { 2113 Map<String, Object> result = new HashMap<>(); 2114 2115 AmetysObject ao = _resolver.resolveById(resourceId); 2116 UserIdentity currentUser = _currentUserProvider.getUser(); 2117 2118 if (!(ao instanceof CommentableResource)) 2119 { 2120 throw new IllegalClassException(CommentableResource.class, ao.getClass()); 2121 } 2122 2123 if (!checkLock(ao)) 2124 { 2125 getLogger().warn("User '{}' is trying to delete comment '{}' on resource '{}' but it is locked by another user", currentUser, commentId, resourceId); 2126 result.put("message", "locked"); 2127 return result; 2128 } 2129 2130 CommentableResource resource = (CommentableResource) ao; 2131 JCRPost post = _resolver.resolveById(commentId); 2132 2133 // Check comment rights 2134 if (!_canDeleteComment(currentUser, resource, post)) 2135 { 2136 getLogger().warn("User '{}' is trying to add a comment on resource '{}' without the convient right.", currentUser, resourceId); 2137 result.put("message", "rights"); 2138 return result; 2139 } 2140 2141 org.ametys.plugins.explorer.threads.Thread comments = resource.getComments(false); 2142 2143 if (!(comments instanceof JCRThread)) 2144 { 2145 throw new IllegalClassException(JCRThread.class, comments.getClass()); 2146 } 2147 2148 JCRThread jcrComments = (JCRThread) comments; 2149 2150 post.remove(); 2151 jcrComments.saveChanges(); 2152 2153 result.put("id", resourceId); 2154 result.put("commentId", commentId); 2155 2156 return result; 2157 } 2158 2159 /** 2160 * Indicates if an user can delete a comment of a given resource 2161 * 2162 * @param user An user identity 2163 * @param resource The resource 2164 * @param comment The comment to edit 2165 * @return True if the user can edit the comment 2166 */ 2167 protected boolean _canDeleteComment(UserIdentity user, CommentableResource resource, JCRPost comment) 2168 { 2169 ExplorerNode node = resource.getParent(); 2170 // Comment right + owner OR Moderate right 2171 return (_rightManager.hasRight(user, "Plugin_Explorer_File_Comment", node) == RightResult.RIGHT_ALLOW && comment.getAuthor().equals(user)) 2172 || _rightManager.hasRight(user, "Plugin_Explorer_File_Moderate_Comments", node) == RightResult.RIGHT_ALLOW; 2173 } 2174 2175 /** 2176 * Get the comments of a resource 2177 * 2178 * @param resourceId The id of the resource 2179 * @param isEdition true to get the comments in edit mode 2180 * @return The comments 2181 */ 2182 @Callable 2183 public List<Map<String, Object>> getComments(String resourceId, boolean isEdition) 2184 { 2185 List<Map<String, Object>> comments = new ArrayList<>(); 2186 2187 AmetysObject ao = _resolver.resolveById(resourceId); 2188 2189 if (!(ao instanceof CommentableResource)) 2190 { 2191 throw new IllegalClassException(CommentableResource.class, ao.getClass()); 2192 } 2193 2194 org.ametys.plugins.explorer.threads.Thread thread = ((CommentableResource) ao).getComments(false); 2195 if (thread != null) 2196 { 2197 if (!(thread instanceof JCRThread)) 2198 { 2199 throw new IllegalClassException(JCRThread.class, thread.getClass()); 2200 } 2201 2202 JCRThread jcrComments = (JCRThread) thread; 2203 2204 AmetysObjectIterable<AmetysObject> children = jcrComments.getChildren(); 2205 for (AmetysObject child : children) 2206 { 2207 if (child instanceof JCRPost) 2208 { 2209 JCRPost comment = (JCRPost) child; 2210 comments.add(_comment2json(comment, isEdition)); 2211 } 2212 } 2213 } 2214 2215 return comments; 2216 } 2217 2218 /** 2219 * Get a comment of a resource 2220 * 2221 * @param commentId The id of the comment 2222 * @param isEdition true to get the comment in edit mode 2223 * @return The comments 2224 */ 2225 @Callable 2226 public Map<String, Object> getComment(String commentId, boolean isEdition) 2227 { 2228 // FIXME check read access on resource? 2229 JCRPost comment = _resolver.resolveById(commentId); 2230 return _comment2json(comment, isEdition); 2231 } 2232 2233 private Map<String, Object> _comment2json(JCRPost comment, boolean isEdition) 2234 { 2235 Map<String, Object> result = new HashMap<>(); 2236 2237 result.put("id", comment.getId()); 2238 result.put("content", isEdition ? _getCommentForEditing(comment) : _getComment(comment)); 2239 result.put("author", _userHelper.user2json(comment.getAuthor())); 2240 result.put("creationDate", DateUtils.dateToString(comment.getCreationDate())); 2241 result.put("lastModifiedDate", DateUtils.dateToString(comment.getLastModified())); 2242 2243 UserIdentity author = comment.getAuthor(); 2244 boolean isOwner = author.equals(_currentUserProvider.getUser()); 2245 result.put("isOwner", isOwner); 2246 2247 return result; 2248 } 2249 2250 private static class VersionInformation 2251 { 2252 private String _rawName; 2253 2254 private String _name; 2255 2256 private Date _creationDate; 2257 2258 private Set<String> _labels = new HashSet<>(); 2259 2260 /** 2261 * Creates a {@link VersionInformation}. 2262 * 2263 * @param rawName the revision name. 2264 * @param creationDate the revision creation date. 2265 * @throws RepositoryException if an error occurs. 2266 */ 2267 public VersionInformation(String rawName, Date creationDate) throws RepositoryException 2268 { 2269 _creationDate = creationDate; 2270 _rawName = rawName; 2271 // 1.0 > v0, 1.1 > v1, ... 2272 _name = String.valueOf(Integer.parseInt(_rawName.substring(2)) + 1); 2273 } 2274 2275 /** 2276 * Retrieves the version name. 2277 * 2278 * @return the version name. 2279 */ 2280 public String getVersionName() 2281 { 2282 return _name; 2283 } 2284 2285 /** 2286 * Retrieves the version raw name. 2287 * 2288 * @return the version raw name. 2289 */ 2290 public String getVersionRawName() 2291 { 2292 return _rawName; 2293 } 2294 2295 /** 2296 * Retrieves the creation date. 2297 * 2298 * @return the creation date. 2299 * @throws RepositoryException if an error occurs. 2300 */ 2301 public Date getCreatedAt() throws RepositoryException 2302 { 2303 return _creationDate; 2304 } 2305 2306 /** 2307 * Retrieves the labels associated with this version. 2308 * 2309 * @return the labels. 2310 */ 2311 public Set<String> getLabels() 2312 { 2313 return _labels; 2314 } 2315 2316 /** 2317 * Add a label to this version. 2318 * 2319 * @param label the label. 2320 */ 2321 public void addLabel(String label) 2322 { 2323 _labels.add(label); 2324 } 2325 } 2326}