001/* 002 * Copyright 2015 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.workspaces.documents; 017 018import java.awt.image.BufferedImage; 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Optional; 031import java.util.function.Function; 032import java.util.stream.Collectors; 033 034import javax.jcr.RepositoryException; 035 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.cocoon.components.ContextHelper; 039import org.apache.cocoon.environment.Request; 040import org.apache.cocoon.servlet.multipart.Part; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.lang.IllegalClassException; 043import org.apache.commons.lang3.StringUtils; 044import org.apache.excalibur.source.Source; 045import org.apache.excalibur.source.SourceResolver; 046 047import org.ametys.cms.content.indexing.solr.SolrFieldNames; 048import org.ametys.cms.search.query.DocumentTypeQuery; 049import org.ametys.cms.search.query.FilenameQuery; 050import org.ametys.cms.search.query.FullTextQuery; 051import org.ametys.cms.search.query.MatchAllQuery; 052import org.ametys.cms.search.query.MimeTypeGroupQuery; 053import org.ametys.cms.search.query.OrQuery; 054import org.ametys.cms.search.query.Query; 055import org.ametys.cms.search.query.Query.Operator; 056import org.ametys.cms.search.query.StringQuery; 057import org.ametys.cms.search.solr.SearcherFactory; 058import org.ametys.cms.transformation.xslt.ResolveURIComponent; 059import org.ametys.core.right.RightManager.RightResult; 060import org.ametys.core.ui.Callable; 061import org.ametys.core.user.User; 062import org.ametys.core.user.UserIdentity; 063import org.ametys.core.user.UserManager; 064import org.ametys.core.util.DateUtils; 065import org.ametys.core.util.FilenameUtils; 066import org.ametys.core.util.URIUtils; 067import org.ametys.plugins.explorer.ExplorerNode; 068import org.ametys.plugins.explorer.ModifiableExplorerNode; 069import org.ametys.plugins.explorer.cmis.CMISRootResourcesCollection; 070import org.ametys.plugins.explorer.resources.ModifiableResource; 071import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 072import org.ametys.plugins.explorer.resources.Resource; 073import org.ametys.plugins.explorer.resources.ResourceCollection; 074import org.ametys.plugins.explorer.resources.actions.AddOrUpdateResourceHelper; 075import org.ametys.plugins.explorer.resources.actions.AddOrUpdateResourceHelper.ResourceOperationMode; 076import org.ametys.plugins.explorer.resources.actions.AddOrUpdateResourceHelper.ResourceOperationResult; 077import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO; 078import org.ametys.plugins.explorer.resources.jcr.JCRResource; 079import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollection; 080import org.ametys.plugins.explorer.threads.jcr.JCRPost; 081import org.ametys.plugins.repository.AmetysObject; 082import org.ametys.plugins.repository.AmetysObjectIterable; 083import org.ametys.plugins.repository.AmetysRepositoryException; 084import org.ametys.plugins.repository.ModifiableAmetysObject; 085import org.ametys.plugins.repository.RemovableAmetysObject; 086import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 087import org.ametys.plugins.repository.lock.LockableAmetysObject; 088import org.ametys.plugins.workspaces.WorkspacesHelper; 089import org.ametys.plugins.workspaces.html.HTMLTransformer; 090import org.ametys.plugins.workspaces.indexing.solr.SolrWorkspacesConstants; 091import org.ametys.plugins.workspaces.project.ProjectManager; 092import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 093import org.ametys.plugins.workspaces.project.objects.Project; 094import org.ametys.plugins.workspaces.search.query.KeywordQuery; 095import org.ametys.plugins.workspaces.search.query.ProjectQuery; 096import org.ametys.runtime.plugin.component.PluginAware; 097import org.ametys.web.WebConstants; 098 099/** 100 * DAO for resources of a project 101 * 102 */ 103public class WorkspaceExplorerResourceDAO extends ExplorerResourcesDAO implements PluginAware 104{ 105 /** Avalon Role */ 106 @SuppressWarnings("hiding") 107 public static final String ROLE = WorkspaceExplorerResourceDAO.class.getName(); 108 109 /** 110 * Enumeration for resource type 111 * 112 */ 113 public static enum ResourceType 114 { 115 /** Folder */ 116 FOLDER, 117 /** File */ 118 FILE 119 } 120 121 /** resource operation helper */ 122 protected AddOrUpdateResourceHelper _addOrUpdateResourceHelper; 123 124 /** The project manager */ 125 private ProjectManager _projectManager; 126 127 private HTMLTransformer _htmlTransformer; 128 private SourceResolver _sourceResolver; 129 private String _pluginName; 130 131 private SearcherFactory _searcherFactory; 132 133 private UserManager _userManager; 134 135 private WorkspaceModuleExtensionPoint _moduleEP; 136 137 private WorkspacesHelper _workspaceHelper; 138 139 @Override 140 public void service(ServiceManager manager) throws ServiceException 141 { 142 super.service(manager); 143 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 144 _htmlTransformer = (HTMLTransformer) manager.lookup(HTMLTransformer.ROLE); 145 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 146 _addOrUpdateResourceHelper = (AddOrUpdateResourceHelper) manager.lookup(AddOrUpdateResourceHelper.ROLE); 147 _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE); 148 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 149 _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 150 _workspaceHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE); 151 } 152 153 @Override 154 public void setPluginInfo(String pluginName, String featureName, String id) 155 { 156 _pluginName = pluginName; 157 } 158 159 /** 160 * Add a folder 161 * @param parentId Identifier of the parent collection. Can be null to add folder to root folder. 162 * @param inputName The desired name 163 * @param description The folder description 164 * @return The created folder or an error if a folder with same name already exists. 165 * @throws IllegalAccessException If the user has no sufficient rights 166 */ 167 @Callable 168 public Map<String, Object> addFolder(String parentId, String inputName, String description) throws IllegalAccessException 169 { 170 return addFolder(parentId, inputName, description, false); 171 } 172 173 /** 174 * Add a folder 175 * @param parentId Identifier of the parent collection. Can be null to add folder to root folder. 176 * @param inputName The desired name 177 * @param description The folder description 178 * @param renameIfExists True to rename if existing 179 * @return The result map with id, parentId and name keys 180 * @throws IllegalAccessException If the user has no sufficient rights 181 */ 182 @Callable 183 public Map<String, Object> addFolder(String parentId, String inputName, String description, Boolean renameIfExists) throws IllegalAccessException 184 { 185 ResourceCollection document = _getRootIfNull(parentId); 186 if (document == null) 187 { 188 throw new IllegalArgumentException("Unable to add folder: parent folder not found"); 189 } 190 191 // TODO catch IllegalAccessException -> error = has-right 192 193 if (!(document instanceof ModifiableResourceCollection)) 194 { 195 throw new IllegalClassException(ModifiableResourceCollection.class, document.getClass()); 196 } 197 198 List<String> errors = new LinkedList<>(); 199 ResourceCollection collection = addResourceCollection((ModifiableResourceCollection) document, inputName, renameIfExists, errors); 200 201 Map<String, Object> result = new HashMap<>(); 202 if (!errors.isEmpty()) 203 { 204 result.put("message", errors.get(0)); 205 result.put("error", true); 206 } 207 208 if (collection != null) 209 { 210 ((ModifiableResourceCollection) collection).setDescription(description); 211 ((ModifiableResourceCollection) collection).saveChanges(); 212 213 result.putAll(_extractFolderData(collection)); 214 } 215 216 return result; 217 } 218 219 /** 220 * Rename a folder 221 * @param id Identifier of the folder to edit 222 * @param name The new name 223 * @return The result map with id and name keys 224 * @throws IllegalAccessException If the user has no sufficient rights 225 */ 226 @Callable 227 public Map<String, Object> renameFolder(String id, String name) throws IllegalAccessException 228 { 229 Map<String, Object> result = new HashMap<>(); 230 231 JCRResourcesCollection folder = (JCRResourcesCollection) _resolver.resolveById(id); 232 233 // TODO catch IllegalAccessException -> error = has-right 234 235 List<String> errors = new LinkedList<>(); 236 JCRResourcesCollection newFolder = null; 237 try 238 { 239 newFolder = (JCRResourcesCollection) renameObject(folder, name, errors); 240 if (!errors.isEmpty()) 241 { 242 result.put("success", false); 243 result.put("message", errors.get(0)); 244 } 245 else 246 { 247 newFolder.saveChanges(); 248 249 result.put("success", true); 250 result.putAll(_extractFolderData(newFolder)); 251 } 252 } 253 catch (RepositoryException e) 254 { 255 getLogger().error("Repository exception during folder edition.", e); 256 errors.add("repository"); 257 } 258 259 return result; 260 } 261 262 /** 263 * Edit a folder 264 * @param id Identifier of the folder to edit 265 * @param inputName The desired name 266 * @param description The folder description 267 * @return The result map with id and name keys 268 * @throws IllegalAccessException If the user has no sufficient rights 269 */ 270 @Callable 271 public Map<String, Object> editFolder(String id, String inputName, String description) throws IllegalAccessException 272 { 273 JCRResourcesCollection folder = (JCRResourcesCollection) _resolver.resolveById(id); 274 275 // TODO catch IllegalAccessException -> error = has-right 276 277 List<String> errors = new LinkedList<>(); 278 JCRResourcesCollection newFolder = null; 279 try 280 { 281 newFolder = (JCRResourcesCollection) renameObject(folder, inputName, errors); 282 } 283 catch (RepositoryException e) 284 { 285 getLogger().error("Repository exception during folder edition.", e); 286 errors.add("repository"); 287 } 288 289 Map<String, Object> result = new HashMap<>(); 290 if (!errors.isEmpty()) 291 { 292 String error = errors.get(0); 293 294 // existing node is allowed, the description can still be changed. 295 if (!"already-exist".equals(error)) 296 { 297 result.put("message", error); 298 } 299 else 300 { 301 newFolder = folder; 302 } 303 } 304 305 if (newFolder != null) 306 { 307 newFolder.setDescription(description); 308 newFolder.saveChanges(); 309 310 result.putAll(_extractFolderData(newFolder)); 311 } 312 313 return result; 314 } 315 316 /** 317 * Delete a folder 318 * @param id Identifier of the folder to delete 319 * @return The result map with the parent id key 320 * @throws IllegalAccessException If the user has no sufficient rights 321 */ 322 @Callable 323 public Map<String, Object> deleteFolder(String id) throws IllegalAccessException 324 { 325 RemovableAmetysObject folder = (RemovableAmetysObject) _resolver.resolveById(id); 326 327 // TODO catch IllegalAccessException -> error = has-right 328 329 List<String> errors = new LinkedList<>(); 330 String parentId = deleteObject(folder, errors); 331 332 Map<String, Object> result = new HashMap<>(); 333 if (!errors.isEmpty()) 334 { 335 String error = errors.get(0); 336 result.put("message", error); 337 } 338 339 // include parent id except if it is the root 340 ResourceCollection rootCollection = _getRootFromRequest(); 341 boolean isRoot = rootCollection != null && rootCollection.getId().equals(parentId); 342 if (StringUtils.isNotEmpty(parentId) && !isRoot) 343 { 344 result.put("parentId", parentId); 345 } 346 347 return result; 348 } 349 350 /** 351 * Lock resources 352 * @param ids The id of resources to lock 353 * @return the result. 354 */ 355 @Callable 356 public Map<String, Object> lockResources(List<String> ids) 357 { 358 Map<String, Object> result = new HashMap<>(); 359 360 result.put("locked-resources", new ArrayList<String>()); 361 result.put("unlocked-resources", new ArrayList<Map<String, Object>>()); 362 363 for (String id : ids) 364 { 365 JCRResource resource = _resolver.resolveById(id); 366 367 if (!resource.isLocked()) 368 { 369 resource.lock(); 370 371 @SuppressWarnings("unchecked") 372 List<String> lockedResources = (List<String>) result.get("locked-resources"); 373 lockedResources.add(id); 374 } 375 else if (!resource.getLockOwner().equals(_currentUserProvider.getUser())) 376 { 377 UserIdentity lockOwner = resource.getLockOwner(); 378 379 getLogger().error("Unable to lock resource of id '" + id + "': the resource is already locked by user " + UserIdentity.userIdentityToString(lockOwner)); 380 381 Map<String, Object> info = new HashMap<>(); 382 info.put("id", id); 383 info.put("name", resource.getName()); 384 info.put("lockOwner", _userHelper.user2json(lockOwner)); 385 386 @SuppressWarnings("unchecked") 387 List<Map<String, Object>> unlockedResources = (List<Map<String, Object>>) result.get("unlocked-resources"); 388 unlockedResources.add(info); 389 } 390 } 391 392 return result; 393 } 394 395 /** 396 * Unlock resources 397 * @param ids The id of resources to lock 398 * @return the result. 399 */ 400 @Callable 401 public Map<String, Object> unlockResources(List<String> ids) 402 { 403 UserIdentity currentUser = _currentUserProvider.getUser(); 404 boolean canUnlockAll = _rightManager.hasRight(currentUser, RIGHTS_RESOURCE_UNLOCK_ALL, "/cms") == RightResult.RIGHT_ALLOW; 405 406 Map<String, Object> result = new HashMap<>(); 407 408 result.put("unlocked-resources", new ArrayList<String>()); 409 result.put("still-locked-resources", new ArrayList<Map<String, Object>>()); 410 411 for (String id : ids) 412 { 413 JCRResource resource = _resolver.resolveById(id); 414 415 if (resource.isLocked()) 416 { 417 if (canUnlockAll || resource.getLockOwner().equals(currentUser)) 418 { 419 resource.unlock(); 420 421 @SuppressWarnings("unchecked") 422 List<String> unlockedResources = (List<String>) result.get("unlocked-resources"); 423 unlockedResources.add(id); 424 } 425 else 426 { 427 UserIdentity lockOwner = resource.getLockOwner(); 428 429 getLogger().error("Unable to unlock resource of id '" + id + "': the resource is locked by user " + UserIdentity.userIdentityToString(lockOwner)); 430 431 Map<String, Object> info = new HashMap<>(); 432 info.put("id", id); 433 info.put("name", resource.getName()); 434 info.put("lockOwner", _userHelper.user2json(lockOwner)); 435 436 @SuppressWarnings("unchecked") 437 List<Map<String, Object>> stilllockedResources = (List<Map<String, Object>>) result.get("still-locked-resources"); 438 stilllockedResources.add(info); 439 } 440 } 441 else 442 { 443 @SuppressWarnings("unchecked") 444 List<String> unlockedResources = (List<String>) result.get("unlocked-resources"); 445 unlockedResources.add(id); 446 } 447 } 448 449 return result; 450 } 451 452 /** 453 * Determines if a resource with given name already exists 454 * @param parentId the id of parent collection. Can be null. 455 * @param name the name of resource 456 * @return true if a resource with same name exists 457 */ 458 @Callable 459 @Override 460 public boolean resourceExists(String parentId, String name) 461 { 462 ResourceCollection folder = _getRootIfNull(parentId); 463 return folder != null && resourceExists(folder, name); 464 } 465 466 /** 467 * Rename a folder 468 * @param id Identifier of the folder to edit 469 * @param name The new name 470 * @return The result map with id and name keys 471 * @throws IllegalAccessException If the user has no sufficient rights 472 */ 473 @Callable 474 public Map<String, Object> renameFile(String id, String name) throws IllegalAccessException 475 { 476 Map<String, Object> result = new HashMap<>(); 477 478 JCRResource file = (JCRResource) _resolver.resolveById(id); 479 if (StringUtils.isNotEmpty(name) && !StringUtils.equals(file.getName(), name)) 480 { 481 // TODO catch IllegalAccessException -> error = has-right 482 483 List<String> errors = new LinkedList<>(); 484 JCRResource renamedFile = null; 485 try 486 { 487 renamedFile = renameResource(file, name, errors); 488 if (!errors.isEmpty()) 489 { 490 result.put("success", false); 491 result.put("message", errors.get(0)); 492 } 493 else 494 { 495 renamedFile.saveChanges(); 496 497 result.put("success", true); 498 result.putAll(_extractFileData(renamedFile)); 499 } 500 } 501 catch (RepositoryException e) 502 { 503 getLogger().error("Repository exception during file edition.", e); 504 errors.add("repository"); 505 } 506 507 } 508 509 return result; 510 } 511 512 513 /** 514 * Edit a file 515 * @param id Identifier of the file to edit 516 * @param inputName The desired name 517 * @param description The file description 518 * @param tags The file tags 519 * @return The result map with id and name keys 520 * @throws IllegalAccessException If the user has no sufficient rights 521 */ 522 @Callable 523 public Map<String, Object> editFile(String id, String inputName, String description, Collection<String> tags) throws IllegalAccessException 524 { 525 Map<String, Object> result = new HashMap<>(); 526 JCRResource file = (JCRResource) _resolver.resolveById(id); 527 528 // Check lock on resource 529 if (!checkLock(file)) 530 { 531 getLogger().warn("User '{}' is trying to edit file '{}' but it is locked by another user", _currentUserProvider.getUser(), file.getName()); 532 result.put("message", "locked"); 533 return result; 534 } 535 536 // TODO catch IllegalAccessException -> error = has-right 537 538 List<String> errors = new LinkedList<>(); 539 540 // Rename 541 JCRResource renamedFile = null; 542 if (StringUtils.isNotEmpty(inputName) && !StringUtils.equals(file.getName(), inputName)) 543 { 544 try 545 { 546 renamedFile = renameResource(file, inputName, errors); 547 548 if (errors.isEmpty()) 549 { 550 file = renamedFile; 551 } 552 } 553 catch (RepositoryException e) 554 { 555 getLogger().error("Repository exception during folder edition.", e); 556 errors.add("repository-rename"); 557 } 558 } 559 560 if (!errors.isEmpty()) 561 { 562 String error = errors.get(0); 563 result.put("message", error); 564 return result; 565 } 566 567 // edit description + tags 568 List<String> fileTags = _sanitizeFileTags(tags); 569 570 Map<String, Object> editValues = new HashMap<>(); 571 editValues.put("dc_description", StringUtils.defaultIfEmpty(description, null)); 572 573 try 574 { 575 file.setKeywords(fileTags.toArray(new String[fileTags.size()])); 576 setDCMetadata(file, editValues); 577 578 // Add tags to the project 579 _projectManager.addTags(fileTags); 580 581 file.saveChanges(); 582 } 583 catch (AmetysRepositoryException e) 584 { 585 getLogger().error("Repository exception during folder edition.", e); 586 errors.add("repository-edit"); 587 } 588 589 if (!errors.isEmpty()) 590 { 591 String error = errors.get(0); 592 result.put("message", error); 593 } 594 else 595 { 596 result.putAll(_extractFileData(file)); 597 } 598 599 return result; 600 } 601 602 /** 603 * Set tags to resource 604 * @param resourceId the id of resources 605 * @param tagNames the tags (keywords) 606 */ 607 @Callable 608 public void setTags(String resourceId, List<String> tagNames) 609 { 610 JCRResource file = (JCRResource) _resolver.resolveById(resourceId); 611 612 List<String> tags = _sanitizeFileTags(tagNames); 613 614 // Add tags to projects' root 615 _projectManager.addTags(tags); 616 617 file.setKeywords(tags.toArray(new String[tags.size()])); 618 619 file.saveChanges(); 620 } 621 622 /** 623 * Copy file resources 624 * @param ids The list of identifiers for the resources to copy 625 * @param targetId The id of target to copy into. Can be null to copy into root folder. 626 * @return The result map with a message key in case of an error or with the list of uncopied/copied resources 627 * @throws RepositoryException If there is a repository error 628 */ 629 @Callable 630 public Map<String, Object> copyFiles(List<String> ids, String targetId) throws RepositoryException 631 { 632 ResourceCollection folder = _getRootIfNull(targetId); 633 if (folder == null) 634 { 635 throw new IllegalArgumentException("Unable to copy files: parent folder not found"); 636 } 637 638 if (!(folder instanceof ModifiableResourceCollection)) 639 { 640 throw new IllegalClassException(ModifiableResourceCollection.class, folder.getClass()); 641 } 642 643 return copyResource(ids, (ModifiableResourceCollection) folder); 644 } 645 646 /** 647 * Move documents (files or folders) 648 * @param ids The list of identifiers for the objects to move 649 * @param targetId The id of target to move into. Can be null to move documents to root folder. 650 * @return The result map with a message key in case of an error or with the list of unmoved/moved objects 651 * @throws RepositoryException If there is a repository error 652 */ 653 @Callable 654 public Map<String, Object> moveDocuments(List<String> ids, String targetId) throws RepositoryException 655 { 656 ResourceCollection folder = _getRootIfNull(targetId); 657 if (folder == null) 658 { 659 throw new IllegalArgumentException("Unable to move documents: parent folder not found"); 660 } 661 662 if (!(folder instanceof JCRTraversableAmetysObject)) 663 { 664 throw new IllegalClassException(JCRTraversableAmetysObject.class, folder.getClass()); 665 } 666 667 return moveObject(ids, (JCRTraversableAmetysObject) folder); 668 } 669 670 /** 671 * Search for files in the document module 672 * @param query The search query 673 * @param lang The search language 674 * @return A result map containing a <em>resources</em> entry which is a list of the files data 675 * @throws Exception if an exception occurs 676 */ 677 @Callable 678 public Map<String, Object> searchFiles(String query, String lang) throws Exception 679 { 680 // Handle result map 681 Map<String, Object> result = new HashMap<>(); 682 683 String escapedQuery = query.replace("\"", "\\\""); 684 685 Query solrQuery; 686 if (StringUtils.isEmpty(query)) 687 { 688 solrQuery = new MatchAllQuery(); 689 } 690 else 691 { 692 List<Query> queries = new ArrayList<>(); 693 queries.add(new FilenameQuery(query)); 694 queries.add(new StringQuery(SolrFieldNames.TITLE, Operator.LIKE, "*" + query + "*", null)); 695 queries.add(new FullTextQuery(escapedQuery, lang)); 696 queries.add(new KeywordQuery(escapedQuery.split(" "))); 697 solrQuery = new OrQuery(queries); 698 } 699 700 AmetysObjectIterable<Resource> results = _searcherFactory.create() 701 .withQuery(solrQuery) 702 .addFilterQuery(new DocumentTypeQuery(SolrWorkspacesConstants.TYPE_PROJECT_RESOURCE)) 703 .addFilterQuery(new ProjectQuery(_getProjectFromRequest().getId())) 704 .search(); 705 706 List<Map<String, Object>> resourceData = results.stream() 707 .map(this::_extractFileData) 708 .collect(Collectors.toList()); 709 710 result.put("resources", resourceData); 711 712 return result; 713 } 714 715 /** 716 * Search for files by their type 717 * @param type The file type 718 * @return A result map containing a <em>resources</em> entry which is a list of the files data 719 * @throws Exception if an exception occurs 720 */ 721 @Callable 722 public Map<String, Object> searchFilesByType(String type) throws Exception 723 { 724 Map<String, Object> result = new HashMap<>(); 725 726 AmetysObjectIterable<Resource> results = _searcherFactory.create() 727 .withQuery(new MimeTypeGroupQuery(type)) 728 .addFilterQuery(new DocumentTypeQuery(SolrWorkspacesConstants.TYPE_PROJECT_RESOURCE)) 729 .addFilterQuery(new ProjectQuery(_getProjectFromRequest().getId())) 730 .search(); 731 732 List<Map<String, Object>> resourceData = results.stream() 733 .map(this::_extractFileData) 734 .collect(Collectors.toList()); 735 736 result.put("resources", resourceData); 737 738 return result; 739 } 740 741 /** 742 * Get the root folder 743 * @return the root folder as JSON object 744 */ 745 @Callable 746 public Map<String, Object> getRootFolder() 747 { 748 return getFolder(null); 749 } 750 751 /** 752 * Get folder 753 * @param folderId the folder id 754 * @return the folder as JSON object 755 */ 756 @Callable 757 public Map<String, Object> getFolder(String folderId) 758 { 759 ResourceCollection collection = _getRootIfNull(folderId); 760 return _extractFolderData(collection); 761 } 762 763 /** 764 * Get file 765 * @param resourceId the resource id 766 * @return the file as JSON object 767 */ 768 @Callable 769 public Map<String, Object> getFile(String resourceId) 770 { 771 Resource resource = _resolver.resolveById(resourceId); 772 return _extractFileData(resource); 773 } 774 /** 775 * Get the child folders 776 * @param parentId the parent id. Can be null to get root folders 777 * @return the sub folders 778 */ 779 @Callable 780 public List<Map<String, Object>> getFolders(String parentId) 781 { 782 return getChildDocumentsData(parentId, false, true); 783 } 784 785 /** 786 * Get the child files 787 * @param parentId the parent id. Can be null to get root files 788 * @return the child files 789 */ 790 @Callable 791 public List<Map<String, Object>> getFiles(String parentId) 792 { 793 return getChildDocumentsData(parentId, true, false); 794 } 795 796 /** 797 * Get the child folders and files 798 * @param folderId the parent id. Can be null to get root folders and files 799 * @return the child folders and files 800 */ 801 @Callable 802 public Map<String, Object> getFoldersAndFiles(String folderId) 803 { 804 ResourceCollection collection = _getRootIfNull(folderId); 805 806 Map<String, Object> data = _extractFolderData(collection); 807 808 data.put("files", getChildDocumentsData(folderId, true, false)); 809 data.put("children", getChildDocumentsData(folderId, false, true)); 810 811 return data; 812 } 813 814 815 /** 816 * Retrieves the children of a document and extracts its data. 817 * @param parentId Identifier of the parent collection. Can be null to get children of root folder. 818 * @param excludeFolders Folders will be excluded if true 819 * @param excludeFiles Files will be excluded if true 820 * @return The map of information 821 */ 822 @Callable 823 public List<Map<String, Object>> getChildDocumentsData(String parentId, boolean excludeFolders, boolean excludeFiles) 824 { 825 ResourceCollection document = _getRootIfNull(parentId); 826 if (document == null) 827 { 828 throw new IllegalArgumentException("Unable to get child documents: parent folder not found"); 829 } 830 return getChildDocumentsData(document, excludeFolders, excludeFiles); 831 } 832 833 /** 834 * Retrieves the children of a document and extracts its data. 835 * @param document the document 836 * @param excludeFolders Folders will be excluded if true 837 * @param excludeFiles Files will be excluded if true 838 * @return The map of information 839 */ 840 public List<Map<String, Object>> getChildDocumentsData(ResourceCollection document, boolean excludeFolders, boolean excludeFiles) 841 { 842 ResourceCollection parent = _getRootIfNull(document); 843 if (parent == null) 844 { 845 throw new IllegalArgumentException("Unable to get child documents: parent folder not found"); 846 } 847 848 return parent.getChildren().stream() 849 .map(child -> _extractDocumentData(child, excludeFolders, excludeFiles)) 850 .filter(Objects::nonNull) 851 .collect(Collectors.toList()); 852 } 853 854 /** 855 * Retrieves the set of standard data for a document (folder or resource) 856 * @param id the document id or null to get root document 857 * @param excludeFilesInFolderHierarchy Should child files be taken into account when extracting data of a folder 858 * @return The map of data 859 */ 860 // TODO To remove not used 861 @Deprecated 862 @Callable 863 public Map<String, Object> getDocumentData(String id, boolean excludeFilesInFolderHierarchy) 864 { 865 AmetysObject document = id == null ? _getRootFromRequest() : _resolver.resolveById(id); 866 if (document == null) 867 { 868 throw new IllegalArgumentException("No project found in request to get root document data"); 869 } 870 871 Map<String, Object> data = _extractDocumentData(document, false, false); 872 return data != null ? data : new HashMap<>(); 873 } 874 875 /** 876 * Retrieves the set of standard data for a list of documents 877 * @param ids The list of document identifiers 878 * @param excludeFilesInFolderHierarchy Should child files be taken into account when extracting data of a folder 879 * @return The map of data 880 */ 881 // TODO To remove not used 882 @Callable 883 public List<Map<String, Object>> getDocumentsData(List<String> ids, boolean excludeFilesInFolderHierarchy) 884 { 885 return ids.stream() 886 .map(id -> getDocumentData(id, excludeFilesInFolderHierarchy)) 887 .collect(Collectors.toList()); 888 } 889 890 @Override 891 protected Map<String, Object> _comment2json(JCRPost comment, boolean isEdition) 892 { 893 Map<String, Object> comment2json = super._comment2json(comment, isEdition); 894 895 String lang = _getCurrentLanguage(); 896 String authorImgUrl = _workspaceHelper.getAvatar(comment.getAuthor(), lang, 30); 897 898 @SuppressWarnings("unchecked") 899 Map<String, Object> author = (Map<String, Object>) comment2json.get("author"); 900 author.put("imgUrl", authorImgUrl); 901 902 return comment2json; 903 } 904 905 @Override 906 protected Map<String, Object> _version2json(JCRResource resource, VersionInformation versionInformation) throws RepositoryException 907 { 908 Map<String, Object> version2json = super._version2json(resource, versionInformation); 909 910 String lang = _getCurrentLanguage(); 911 912 @SuppressWarnings("unchecked") 913 Map<String, Object> author = (Map<String, Object>) version2json.get("author"); 914 String authorImgUrl = _workspaceHelper.getAvatar(resource.getLastContributor(), lang, 30); 915 author.put("imgUrl", authorImgUrl); 916 917 return version2json; 918 919 } 920 921 /** 922 * Generates an uri to open a document through webdav 923 * @param documentId The document identifier 924 * @return The generated uri 925 */ 926 @Callable 927 public String generateWebdavUri(String documentId) 928 { 929 return ResolveURIComponent.resolve("webdav-project-resource", documentId, false, true); 930 } 931 932 private ResourceCollection _getRootIfNull(ResourceCollection document) 933 { 934 return document != null ? document : _getRootFromRequest(); 935 } 936 937 private ResourceCollection _getRootIfNull(String documentId) 938 { 939 return StringUtils.isNotEmpty(documentId) 940 ? (ResourceCollection) _resolver.resolveById(documentId) 941 : _getRootFromRequest(); 942 } 943 944 private ResourceCollection _getRootFromRequest() 945 { 946 Project project = _getProjectFromRequest(); 947 948 if (project != null) 949 { 950 return _getRootFromProject(project); 951 } 952 else 953 { 954 return null; 955 } 956 } 957 958 private ResourceCollection _getRootFromObject(AmetysObject ametysObject) 959 { 960 Project project = _getProjectFomObject(ametysObject); 961 if (project != null) 962 { 963 return _getRootFromProject(project); 964 } 965 return null; 966 } 967 968 private ResourceCollection _getRootFromProject(Project project) 969 { 970 if (project != null) 971 { 972 DocumentWorkspaceModule module = _moduleEP.getModule(DocumentWorkspaceModule.DODUMENT_MODULE_ID); 973 return module.getModuleRoot(project, false); 974 } 975 else 976 { 977 return null; 978 } 979 } 980 981 private Project _getProjectFromRequest() 982 { 983 Request request = ContextHelper.getRequest(_context); 984 985 String projectName = (String) request.getAttribute("projectName"); 986 if (projectName != null) 987 { 988 return _projectManager.getProject(projectName); 989 } 990 else 991 { 992 return null; 993 } 994 } 995 996 private Project _getProjectFomObject(AmetysObject ametysObject) 997 { 998 AmetysObject parent = ametysObject; 999 1000 while (parent != null && !(parent instanceof Project)) 1001 { 1002 parent = parent.getParent(); 1003 } 1004 1005 if (parent == null) 1006 { 1007 return null; 1008 } 1009 return (Project) parent; 1010 } 1011 1012 private String _getCurrentLanguage() 1013 { 1014 Request request = ContextHelper.getRequest(_context); 1015 return (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME); 1016 } 1017 1018 /** 1019 * Internal method to extract the valuable data of a document 1020 * @param document The document (file or folder) 1021 * @param excludeFolders Folders will be excluded if true 1022 * @param excludeFiles Files will be excluded if true 1023 * @return the valuable document data 1024 */ 1025 protected Map<String, Object> _extractDocumentData(AmetysObject document, boolean excludeFolders, boolean excludeFiles) 1026 { 1027 if (!excludeFiles && document instanceof Resource) 1028 { 1029 Resource file = (Resource) document; 1030 if (_canView(file)) 1031 { 1032 return _extractFileData(file); 1033 } 1034 } 1035 else if (!excludeFolders && document instanceof ResourceCollection) 1036 { 1037 ResourceCollection folder = (ResourceCollection) document; 1038 if (_canView(folder)) 1039 { 1040 return _extractFolderData(folder); 1041 } 1042 } 1043 1044 return null; 1045 } 1046 1047 /** 1048 * Internal method to extract the valuable data of a folder 1049 * @param folder The folder 1050 * @return the valuable folder data 1051 */ 1052 protected Map<String, Object> _extractFolderData(ResourceCollection folder) 1053 { 1054 Map<String, Object> data = new HashMap<>(); 1055 1056 data.put("id", folder.getId()); 1057 data.put("name", folder.getName()); 1058 data.put("path", _getFolderPath(folder)); 1059 data.put("type", ResourceType.FOLDER.name().toLowerCase()); 1060 data.put("description", StringUtils.defaultString(folder.getDescription())); 1061 1062 AmetysObject parent = folder.getParent(); 1063 if (parent != null && parent instanceof ResourceCollection) 1064 { 1065 data.put("location", ((ResourceCollection) parent).getName()); 1066 data.put("parentId", parent.getId()); 1067 } 1068 1069 boolean hasChildren = _hasChildren(folder, true); 1070 if (hasChildren) 1071 { 1072 data.put("children", Collections.EMPTY_LIST); 1073 } 1074 1075 data.put("modifiable", folder instanceof ModifiableAmetysObject); 1076 data.put("canCreateChild", folder instanceof ModifiableExplorerNode); 1077 data.put("rights", _extractFolderRightData(folder)); 1078 1079 data.put("notification", false); // TODO unread notification (not yet supported) 1080 1081 return data; 1082 } 1083 1084 private List<String> _getFolderPath(ResourceCollection folder) 1085 { 1086 List<String> paths = new ArrayList<>(); 1087 1088 ResourceCollection rootDocuments = _getRootFromObject(folder); 1089 1090 if (!rootDocuments.equals(folder)) 1091 { 1092 List<ExplorerNode> parents = new ArrayList<>(); 1093 1094 AmetysObject parent = folder.getParent(); 1095 while (parent instanceof ExplorerNode && !parent.equals(rootDocuments)) 1096 { 1097 parents.add((ExplorerNode) parent); 1098 parent = parent.getParent(); 1099 } 1100 1101 parents.add(rootDocuments); 1102 1103 Collections.reverse(parents); 1104 1105 parents.stream().forEach(p -> 1106 { 1107 paths.add(p.getId()); 1108 }); 1109 } 1110 1111 return paths; 1112 } 1113 1114 /** 1115 * Internal method to detect if a document has child 1116 * @param folder The folder 1117 * @param ignoreFiles Should child files be taken into account to compute the 'hasChildDocuments' data. 1118 * @return the valuable folder data 1119 */ 1120 protected boolean _hasChildren(ResourceCollection folder, boolean ignoreFiles) 1121 { 1122 for (AmetysObject child : folder.getChildren()) 1123 { 1124 if (child instanceof ResourceCollection && _canView((ResourceCollection) child) 1125 || !ignoreFiles && child instanceof Resource && _canView((Resource) child)) 1126 { 1127 return true; 1128 } 1129 } 1130 1131 return false; 1132 } 1133 1134 /** 1135 * Internal method to extract the data concerning the right of the current user for a folder 1136 * @param folder The folder 1137 * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not. 1138 */ 1139 protected Map<String, Object> _extractFolderRightData(ResourceCollection folder) 1140 { 1141 Map<String, Object> rightsData = new HashMap<>(); 1142 UserIdentity user = _currentUserProvider.getUser(); 1143 1144 // Add 1145 rightsData.put("add-file", _rightManager.hasRight(user, RIGHTS_RESOURCE_ADD, folder) == RightResult.RIGHT_ALLOW); 1146 rightsData.put("add-folder", _rightManager.hasRight(user, RIGHTS_COLLECTION_ADD, folder) == RightResult.RIGHT_ALLOW); 1147 rightsData.put("add-cmis-folder", _rightManager.hasRight(user, "Plugin_Explorer_CMIS_Add", folder) == RightResult.RIGHT_ALLOW); 1148 1149 // Rename - Edit 1150 rightsData.put("edit", _rightManager.hasRight(user, RIGHTS_COLLECTION_EDIT, folder) == RightResult.RIGHT_ALLOW); 1151 1152 // Delete 1153 rightsData.put("delete", _rightManager.hasRight(user, RIGHTS_COLLECTION_DELETE, folder) == RightResult.RIGHT_ALLOW); 1154 // FIXME Delete own? 1155 1156 return rightsData; 1157 } 1158 1159 /** 1160 * Add a file 1161 * @param part The uploaded part corresponding to the file 1162 * @param parentId Identifier of the parent collection 1163 * @param unarchive True if the file is an archive that should be unarchived (only available for ZIP file) 1164 * @param allowRename True if the file can be renamed if it already exists 1165 * @param allowUpdate True if the file can be updated if it already exists (and allowRename is false) 1166 * @return The result map with id, parentId and name keys 1167 * @throws IllegalAccessException If the user has no sufficient rights 1168 */ 1169 @Callable 1170 public Map<String, Object> addFile(Part part, String parentId, boolean unarchive, boolean allowRename, boolean allowUpdate) throws IllegalAccessException 1171 { 1172 // FIXME CMS-2297 1173 // checkUserRight(object, "Plugin_Explorer_File_Add"); 1174 // see AddOrUpdateResourceHelper 1175 // TODO catch IllegalAccessException -> error = has-right 1176 1177 ModifiableResourceCollection modifiableFolder = getModifiableResourceCollection(parentId); 1178 1179 ResourceOperationMode mode = getOperationMode(unarchive, allowRename, allowUpdate); 1180 1181 ResourceOperationResult operationResult = _addOrUpdateResourceHelper.performResourceOperation(part, modifiableFolder, mode); 1182 1183 // Handle result map 1184 return generateActionResult(modifiableFolder, operationResult); 1185 } 1186 /** 1187 * Add a file 1188 * @param inputStream The uploaded input stream 1189 * @param fileName desired file name 1190 * @param parentId Identifier of the parent collection 1191 * @param unarchive True if the file is an archive that should be unarchived (only available for ZIP file) 1192 * @param allowRename True if the file can be renamed if it already exists 1193 * @param allowUpdate True if the file can be updated if it already exists (and allowRename is false) 1194 * @return The result map with id, parentId and name keys 1195 * @throws IllegalAccessException If the user has no sufficient rights 1196 */ 1197 public Map<String, Object> addFile(InputStream inputStream, String fileName, String parentId, boolean unarchive, boolean allowRename, boolean allowUpdate) throws IllegalAccessException 1198 { 1199 // FIXME CMS-2297 1200 // checkUserRight(object, "Plugin_Explorer_File_Add"); 1201 // see AddOrUpdateResourceHelper 1202 // TODO catch IllegalAccessException -> error = has-right 1203 1204 ModifiableResourceCollection modifiableFolder = getModifiableResourceCollection(parentId); 1205 1206 ResourceOperationMode mode = getOperationMode(unarchive, allowRename, allowUpdate); 1207 1208 ResourceOperationResult operationResult = _addOrUpdateResourceHelper.performResourceOperation(inputStream, fileName, modifiableFolder, mode); 1209 1210 // Handle result map 1211 return generateActionResult(modifiableFolder, operationResult); 1212 } 1213 1214 /** 1215 * get a {@link ModifiableResourceCollection} for an ID, or the root folder; 1216 * @param ametysId id of the resource. Can be null to get root folder. 1217 * @return ModifiableResourceCollection 1218 * @throws IllegalClassException if id links to a node which is not a {@link ModifiableResourceCollection} 1219 */ 1220 private ModifiableResourceCollection getModifiableResourceCollection(String ametysId) 1221 { 1222 ResourceCollection folder = _getRootIfNull(ametysId); 1223 1224 if (folder == null) 1225 { 1226 throw new IllegalArgumentException("Root folder not found"); 1227 } 1228 1229 if (!(folder instanceof ModifiableResourceCollection)) 1230 { 1231 throw new IllegalClassException(ModifiableResourceCollection.class, folder.getClass()); 1232 } 1233 1234 return (ModifiableResourceCollection) folder; 1235 } 1236 /** 1237 * returns the {@link ResourceOperationMode} according to parameters 1238 * @param unarchive unarchive 1239 * @param allowRename allowRename 1240 * @param allowUpdate allowUpdate 1241 * @return ADD, ADD_UNZIP, ADD_RENAME, ADD_UPDATE 1242 */ 1243 private ResourceOperationMode getOperationMode(boolean unarchive, boolean allowRename, boolean allowUpdate) 1244 { 1245 ResourceOperationMode mode = ResourceOperationMode.ADD; 1246 if (unarchive) 1247 { 1248 mode = ResourceOperationMode.ADD_UNZIP; 1249 } 1250 else if (allowRename) 1251 { 1252 mode = ResourceOperationMode.ADD_RENAME; 1253 } 1254 else if (allowUpdate) 1255 { 1256 mode = ResourceOperationMode.UPDATE; 1257 } 1258 return mode; 1259 } 1260 private Map<String, Object> generateActionResult(ResourceCollection folder, ResourceOperationResult operationResult) 1261 { 1262 Map<String, Object> result = new HashMap<>(); 1263 1264 if (operationResult.isSuccess()) 1265 { 1266 List<Map<String, Object>> resourceData = operationResult.getResources() 1267 .stream() 1268 .filter(r -> r.getParent().equals(folder)) // limit to direct children 1269 .map(this::_extractFileData) 1270 .collect(Collectors.toList()); 1271 1272 result.put("resources", resourceData); 1273 } 1274 else 1275 { 1276 result.put("message", operationResult.getErrorMessage()); 1277 } 1278 1279 return result; 1280 } 1281 1282 /** 1283 * Internal method to extract the valuable data of a file 1284 * @param file The file 1285 * @return the valuable file data 1286 */ 1287 protected Map<String, Object> _extractFileData(Resource file) 1288 { 1289 Map<String, Object> data = new HashMap<>(); 1290 1291 data.put("id", file.getId()); 1292 data.put("name", file.getName()); 1293 data.put("path", _getFilePath(file)); 1294 1295 // Encode path without extension) 1296 String resourcePath = file.getResourcePath(); 1297 int i = resourcePath.lastIndexOf("."); 1298 resourcePath = i != -1 ? resourcePath.substring(0, i) : resourcePath; 1299 // Encode twice 1300 String encodedPath = FilenameUtils.encodePath(resourcePath); 1301 data.put("encodedPath", URIUtils.encodeURI(encodedPath, Map.of())); 1302 1303 data.put("type", ResourceType.FILE.name().toLowerCase()); 1304 data.put("fileType", _workspaceHelper.getFileType(file).name().toLowerCase()); 1305 data.put("fileExtension", StringUtils.substringAfterLast(file.getName(), ".")); 1306 1307 AmetysObject parent = file.getParent(); 1308 if (parent != null && parent instanceof ResourceCollection) 1309 { 1310 data.put("location", ((ResourceCollection) parent).getName()); 1311 data.put("parentId", parent.getId()); 1312 data.put("parentPath", ((ResourceCollection) parent).getExplorerPath()); 1313 } 1314 1315 data.put("modifiable", file instanceof ModifiableResource); 1316 data.put("canCreateChild", file instanceof ModifiableExplorerNode); 1317 data.put("uri", ResolveURIComponent.resolve("project-resource", file.getId(), false, true)); 1318 1319 data.put("description", file.getDCDescription()); 1320 data.put("tags", _tags2json(file)); 1321 data.put("mimetype", file.getMimeType()); 1322 data.put("length", String.valueOf(file.getLength())); 1323 data.putAll(_extractFileImageData(file)); 1324 1325 // TODO To remove: unused in 2.0.0 1326 UserIdentity userIdentity = file.getCreator(); 1327 User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 1328 String name = user == null ? userIdentity.getLogin() : user.getFullName(); 1329 data.put("lastContributor", name); 1330 if (user != null) 1331 { 1332 data.put("lastContributorLogin", userIdentity.getLogin()); 1333 data.put("lastContributorPopulationId", userIdentity.getPopulationId()); 1334 } 1335 // End remove 1336 1337 UserIdentity creatorIdentity = file.getCreator(); 1338 data.put("creator", _userHelper.user2json(creatorIdentity)); 1339 data.put("creationDate", DateUtils.dateToString(file.getCreationDate())); 1340 1341 UserIdentity contribIdentity = file.getLastContributor(); 1342 data.put("author", _userHelper.user2json(contribIdentity)); 1343 data.put("lastModified", DateUtils.dateToString(file.getLastModified())); 1344 1345 data.put("rights", _extractFileRightData(file)); 1346 1347 data.putAll(_extractFileLockData(file)); 1348 1349 return data; 1350 } 1351 1352 /** 1353 * Get tags 1354 * @return the tags 1355 */ 1356 @Callable 1357 public List<Map<String, Object>> getTags() 1358 { 1359 // FIXME get tag of project only ? get tag used on files only ? 1360 List<String> allTags = _projectManager.getTags(); 1361 1362 List<Map<String, Object>> tags = new ArrayList<>(); 1363 1364 allTags.forEach(t -> 1365 { 1366 Map<String, Object> tag = new HashMap<>(); 1367 tag.put("text", t); 1368 tag.put("color", null); // FIXME tag color is not supported yet 1369 tags.add(tag); 1370 }); 1371 1372 return tags; 1373 } 1374 1375 private List<Map<String, Object>> _tags2json(Resource file) 1376 { 1377 List<Map<String, Object>> tags = new ArrayList<>(); 1378 1379 Arrays.stream(file.getKeywords()).forEach(keyword -> 1380 { 1381 Map<String, Object> tag = new HashMap<>(); 1382 tag.put("text", keyword); 1383 tag.put("color", null); // FIXME tag color is not supported yet 1384 tags.add(tag); 1385 }); 1386 1387 return tags; 1388 } 1389 1390 private List<String> _getFilePath(Resource file) 1391 { 1392 return _getFolderPath(file.getParent()); 1393 } 1394 1395 /** 1396 * Internal method to extract image specific data of a file 1397 * @param file The file 1398 * @return The image specific data 1399 */ 1400 protected Map<String, Object> _extractFileImageData(Resource file) 1401 { 1402 Map<String, Object> imageData = new HashMap<>(); 1403 1404 BufferedImage image = _workspaceHelper.getImage(file); 1405 if (image != null) 1406 { 1407 imageData.put("imgWidth", image.getWidth()); 1408 imageData.put("imgHeight", image.getHeight()); 1409 } 1410 1411 imageData.put("image", image != null); 1412 1413 return imageData; 1414 } 1415 1416 /** 1417 * Internal method to extract the data concerning the right of the current user for file 1418 * @param file The file 1419 * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not. 1420 */ 1421 protected Map<String, Object> _extractFileRightData(Resource file) 1422 { 1423 Map<String, Object> rightsData = new HashMap<>(); 1424 UserIdentity user = _currentUserProvider.getUser(); 1425 ResourceCollection folder = file.getParent(); 1426 1427 // Rename - Edit 1428 rightsData.put("rename", _rightManager.hasRight(user, RIGHTS_RESOURCE_RENAME, folder) == RightResult.RIGHT_ALLOW); 1429 rightsData.put("edit", _rightManager.hasRight(user, RIGHTS_RESOURCE_EDIT_DC, folder) == RightResult.RIGHT_ALLOW); 1430 1431 // Delete 1432 rightsData.put("delete", _rightManager.hasRight(user, RIGHTS_RESOURCE_DELETE, folder) == RightResult.RIGHT_ALLOW); 1433 1434 // TODO Delete own - no ability to detect document creator currently 1435 // rightsData.put("delete-own", ...); 1436 1437 // Unlock 1438 rightsData.put("unlock", _rightManager.hasRight(user, RIGHTS_RESOURCE_UNLOCK_ALL, folder) == RightResult.RIGHT_ALLOW); 1439 1440 // Comments 1441 rightsData.put("comment", _rightManager.hasRight(user, RIGHTS_RESOURCE_COMMENT, folder) == RightResult.RIGHT_ALLOW); 1442 rightsData.put("moderate-comments", _rightManager.hasRight(user, RIGHTS_RESOURCE_MODERATE_COMMENT, folder) == RightResult.RIGHT_ALLOW); 1443 1444 return rightsData; 1445 } 1446 1447 /** 1448 * Internal method to extract the data relative to the lock state of a file 1449 * @param file The file 1450 * @return The image specific data 1451 */ 1452 protected Map<String, Object> _extractFileLockData(Resource file) 1453 { 1454 Map<String, Object> lockData = new HashMap<>(); 1455 1456 if (file instanceof LockableAmetysObject) 1457 { 1458 boolean isLocked = ((LockableAmetysObject) file).isLocked(); 1459 lockData.put("locked", isLocked); 1460 1461 if (isLocked) 1462 { 1463 UserIdentity lockOwner = ((LockableAmetysObject) file).getLockOwner(); 1464 1465 lockData.put("isLockOwner", lockOwner.equals(_currentUserProvider.getUser())); 1466 lockData.put("lockOwner", _userHelper.user2json(lockOwner)); 1467 } 1468 } 1469 1470 return lockData; 1471 } 1472 1473 private List<String> _sanitizeFileTags(Collection<String> tags) throws AmetysRepositoryException 1474 { 1475 // Enforce lowercase and remove possible duplicate tags 1476 return Optional.ofNullable(tags).orElseGet(ArrayList::new).stream() 1477 .map(String::trim) 1478 .map(String::toLowerCase) 1479 .distinct() 1480 .collect(Collectors.toList()); 1481 } 1482 1483 @Override 1484 protected void _setComment(JCRPost comment, String content) 1485 { 1486 try 1487 { 1488 _htmlTransformer.transform(content, comment.getContent()); 1489 } 1490 catch (IOException e) 1491 { 1492 throw new AmetysRepositoryException("Failed to transform comment into rich text", e); 1493 } 1494 } 1495 1496 @Override 1497 protected String _getComment(JCRPost post) throws AmetysRepositoryException 1498 { 1499 Source contentSource = null; 1500 try 1501 { 1502 Map<String, Object> parameters = new HashMap<>(); 1503 parameters.put("source", post.getContent().getInputStream()); 1504 contentSource = _sourceResolver.resolveURI("cocoon://_plugins/" + _pluginName + "/convert/html2html", null, parameters); 1505 return IOUtils.toString(contentSource.getInputStream(), "UTF-8"); 1506 } 1507 catch (IOException e) 1508 { 1509 throw new AmetysRepositoryException("Failed to transform rich text into string", e); 1510 } 1511 finally 1512 { 1513 _sourceResolver.release(contentSource); 1514 } 1515 } 1516 1517 @Override 1518 protected String _getCommentForEditing(JCRPost post) throws AmetysRepositoryException 1519 { 1520 try 1521 { 1522 StringBuilder sb = new StringBuilder(); 1523 _htmlTransformer.transformForEditing(post.getContent(), sb); 1524 return sb.toString(); 1525 } 1526 catch (IOException e) 1527 { 1528 throw new AmetysRepositoryException("Failed to transform rich text into string", e); 1529 } 1530 } 1531 1532 /** 1533 * Indicates if the current user can view the folder 1534 * @param folder The folder to test 1535 * @return true if the folder can be viewed 1536 */ 1537 protected boolean _canView(ResourceCollection folder) 1538 { 1539 return _rightManager.currentUserHasReadAccess(folder); 1540 } 1541 1542 /** 1543 * Indicates if the current user can view the file 1544 * @param file The file to test 1545 * @return true if the file can be viewed 1546 */ 1547 protected boolean _canView(Resource file) 1548 { 1549 return _rightManager.currentUserHasReadAccess(file.getParent()); 1550 } 1551 1552 @Override 1553 @Callable 1554 public Map<String, String> getCMISProperties(String id) 1555 { 1556 // override to allow calls from workspaces 1557 return super.getCMISProperties(id); 1558 } 1559 1560 @Override 1561 @Callable 1562 public Map<String, Object> addCMISCollection(String parentId, String originalName, String url, String login, String password, String repoId, boolean renameIfExists) 1563 { 1564 String rootId = parentId == null ? _getRootFromRequest().getId() : parentId; 1565 if (rootId == null) 1566 { 1567 throw new IllegalArgumentException("Unable to add CMIS collection: parent folder not found."); 1568 } 1569 return super.addCMISCollection(rootId, originalName, url, login, password, repoId, renameIfExists); 1570 } 1571 1572 /** 1573 * Edits a CMIS folder (see {@link CMISRootResourcesCollection}) 1574 * 1575 * @param id the id of CMIS folder 1576 * @param name The name of the CMIS folder 1577 * @param url The url of CMIS repository 1578 * @param login The user's login to access CMIS repository 1579 * @param password The user's password to access CMIS repository 1580 * @param repoId The id of CMIS repository 1581 * @return the result map with id of edited node 1582 * @throws RepositoryException If an error occurred 1583 */ 1584 @Callable 1585 public Map<String, Object> editCMISCollection(String id, String name, String url, String login, String password, String repoId) throws RepositoryException 1586 { 1587 List<String> errors = new LinkedList<>(); 1588 try 1589 { 1590 renameObject(id, name); 1591 } 1592 catch (RepositoryException e) 1593 { 1594 getLogger().error("Repository exception during CMIS folder edition.", e); 1595 errors.add("repository"); 1596 } 1597 1598 // override to allow calls from workspaces 1599 Map<String, Object> result = super.editCMISCollection(id, url, login, password, repoId); 1600 if (errors.size() > 0) 1601 { 1602 result.put("errors", errors); 1603 } 1604 return result; 1605 } 1606 1607 @Override 1608 @Callable 1609 public boolean isCMISCollection(String id) 1610 { 1611 // override to allow calls from workspaces 1612 return super.isCMISCollection(id); 1613 } 1614 1615 /** 1616 * Count the total of documents in the project 1617 * @param project The project 1618 * @return The total of documents, or null if the module is not activated 1619 */ 1620 public Long getDocumentsCount(Project project) 1621 { 1622 Function<Project, ResourceCollection> getModuleRoot = proj -> _moduleEP.getModule(DocumentWorkspaceModule.DODUMENT_MODULE_ID).getModuleRoot(proj, false); 1623 return Optional.ofNullable(project) 1624 .map(getModuleRoot) 1625 .map(root -> _getChildDocumentsCount(root)) 1626 .orElse(null); 1627 } 1628 1629 private Long _getChildDocumentsCount(ResourceCollection collection) 1630 { 1631 return collection.getChildren().stream() 1632 // Count the number of documents : a document counts as 1, a folder count has the sum of its children. Anything else is ignored (0) 1633 .map(ao -> ao instanceof Resource ? 1L : ao instanceof ResourceCollection ? _getChildDocumentsCount((ResourceCollection) ao) : 0L) 1634 .reduce(0L, Long::sum); 1635 } 1636}