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.explorer.threads.actions; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.logger.AbstractLogEnabled; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.commons.io.IOUtils; 033import org.apache.commons.lang.IllegalClassException; 034import org.apache.excalibur.xml.sax.SAXParser; 035import org.xml.sax.InputSource; 036import org.xml.sax.SAXException; 037 038import org.ametys.core.observation.Event; 039import org.ametys.core.observation.ObservationManager; 040import org.ametys.core.right.RightManager; 041import org.ametys.core.ui.Callable; 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.User; 044import org.ametys.core.user.UserIdentity; 045import org.ametys.core.user.UserManager; 046import org.ametys.plugins.core.user.UserHelper; 047import org.ametys.plugins.explorer.ExplorerNode; 048import org.ametys.plugins.explorer.ModifiableExplorerNode; 049import org.ametys.plugins.explorer.ObservationConstants; 050import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 051import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO; 052import org.ametys.plugins.explorer.threads.jcr.JCRPost; 053import org.ametys.plugins.explorer.threads.jcr.JCRPostFactory; 054import org.ametys.plugins.explorer.threads.jcr.JCRThread; 055import org.ametys.plugins.explorer.threads.jcr.JCRThreadFactory; 056import org.ametys.plugins.explorer.threads.jcr.PostRichTextHandler; 057import org.ametys.plugins.repository.AmetysObject; 058import org.ametys.plugins.repository.AmetysObjectIterable; 059import org.ametys.plugins.repository.AmetysObjectResolver; 060import org.ametys.plugins.repository.AmetysRepositoryException; 061import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 062import org.ametys.plugins.repository.metadata.ModifiableRichText; 063import org.ametys.runtime.parameter.ParameterHelper; 064 065/** 066 * Thread DAO 067 */ 068public class ThreadDAO extends AbstractLogEnabled implements Serviceable, Component 069{ 070 /** Avalon Role */ 071 public static final String ROLE = ThreadDAO.class.getName(); 072 073 /** Right to add a thread */ 074 public static final String __RIGHTS_THREAD_ADD = "Plugin_Explorer_Thread_Add"; 075 076 /** Right to edit a thread */ 077 public static final String __RIGHTS_THREAD_EDIT = "Plugin_Explorer_Thread_Edit"; 078 079 /** Right to delete a thread */ 080 public static final String __RIGHTS_THREAD_DELETE = "Plugin_Explorer_Thread_Delete"; 081 082 /** Right to add a post */ 083 public static final String __RIGHTS_POST_ADD = "Plugin_Explorer_Post_Add"; 084 085 /** Right to edit a post */ 086 public static final String __RIGHTS_POST_EDIT = "Plugin_Explorer_Post_Edit"; 087 088 /** Right to delete a post */ 089 public static final String __RIGHTS_POST_DELETE = "Plugin_Explorer_Post_Delete"; 090 091 /** Explorer resources DAO */ 092 protected ExplorerResourcesDAO _explorerResourcesDAO; 093 094 /** Ametys resolver */ 095 protected AmetysObjectResolver _resolver; 096 097 /** Observer manager. */ 098 protected ObservationManager _observationManager; 099 100 /** The current user provider. */ 101 protected CurrentUserProvider _currentUserProvider; 102 103 /** User manager */ 104 protected UserManager _userManager; 105 106 /** The rights manager */ 107 protected RightManager _rightManager; 108 /** The user helper */ 109 protected UserHelper _userHelper; 110 111 private SAXParser _parser; 112 113 public void service(ServiceManager manager) throws ServiceException 114 { 115 _explorerResourcesDAO = (ExplorerResourcesDAO) manager.lookup(ExplorerResourcesDAO.ROLE); 116 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 117 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 118 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 119 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 120 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 121 _parser = (SAXParser) manager.lookup(SAXParser.ROLE); 122 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 123 } 124 125 126 /** 127 * Get thread info 128 * @param id The thread id 129 * @param includeChildren True to also include children 130 * @return the thread data in a map 131 */ 132 @Callable 133 public Map<String, Object> getThreadData(String id, boolean includeChildren) 134 { 135 JCRThread thread = (JCRThread) _resolver.resolveById(id); 136 return getThreadData(thread, includeChildren); 137 } 138 139 /** 140 * Get thread info 141 * @param thread The thread 142 * @param includeChildren True to also include children 143 * @return the thread data in a map 144 */ 145 public Map<String, Object> getThreadData(JCRThread thread, boolean includeChildren) 146 { 147 Map<String, Object> result = new HashMap<>(); 148 UserIdentity author = thread.getAuthor(); 149 150 result.put("id", thread.getId()); 151 result.put("title", thread.getTitle()); 152 result.put("description", thread.getDescription()); 153 result.put("author", _formatAuthor(author)); 154 result.put("authorLogin", author.getLogin()); 155 result.put("authorPopulation", author.getPopulationId()); 156 UserIdentity currentUser = _currentUserProvider.getUser(); 157 result.put("isAuthor", author.equals(currentUser)); 158 result.put("creationDate", ParameterHelper.valueToString(thread.getCreationDate())); 159 result.put("unreadPosts", thread.getUnreadPosts(currentUser)); 160 161 Map<String, Object> rights = new HashMap<>(); 162 163 rights.put("threadEdit", _explorerResourcesDAO.getUserRight(currentUser, __RIGHTS_THREAD_EDIT, thread)); 164 rights.put("threadDelete", _explorerResourcesDAO.getUserRight(currentUser, __RIGHTS_THREAD_DELETE, thread)); 165 rights.put("postAdd", _explorerResourcesDAO.getUserRight(currentUser, __RIGHTS_POST_ADD, thread)); 166 result.put("rights", rights); 167 168 if (includeChildren) 169 { 170 List<Map<String, Object>> childrenData = new LinkedList<>(); 171 result.put("posts", childrenData); 172 173 AmetysObjectIterable<AmetysObject> children = thread.getChildren(); 174 for (AmetysObject child : children) 175 { 176 if (child instanceof JCRPost) 177 { 178 JCRPost jcrPost = (JCRPost) child; 179 childrenData.add(getPostData(jcrPost, false, false)); 180 } 181 } 182 } 183 184 return result; 185 } 186 187 /** 188 * Return the author of a thread in a formatted version ready to be displayed to the end user. 189 * @param userIdentity The author 190 * @return The formatted string 191 */ 192 protected String _formatAuthor(UserIdentity userIdentity) 193 { 194 User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 195 String name = user == null ? userIdentity.getLogin() : user.getFullName() + " (" + userIdentity.getLogin() + ")"; 196 return name; 197 } 198 199 /** 200 * Get post info 201 * @param ids The post ids 202 * @param fullInfo true to include full info (rights, parent id, etc...) 203 * @param isEdition true to get the content in edit mode 204 * @return the list of post data 205 */ 206 @Callable 207 public List<Map<String, Object>> getPostsDataByIds(List<String> ids, boolean fullInfo, boolean isEdition) 208 { 209 List<JCRPost> posts = new LinkedList<>(); 210 for (String id : ids) 211 { 212 posts.add((JCRPost) _resolver.resolveById(id)); 213 } 214 215 return getPostsData(posts, fullInfo, isEdition); 216 } 217 218 /** 219 * Get post info 220 * @param posts The posts 221 * @param fullInfo true to include full info (rights, parent id, etc...) 222 * @param isEdition true to get the content in edit mode 223 * @return the list of post data 224 */ 225 public List<Map<String, Object>> getPostsData(List<JCRPost> posts, boolean fullInfo, boolean isEdition) 226 { 227 List<Map<String, Object>> result = new LinkedList<>(); 228 229 for (JCRPost post : posts) 230 { 231 result.add(getPostData(post, fullInfo, isEdition)); 232 } 233 234 return result; 235 } 236 237 /** 238 * Get post info 239 * @param id The post id 240 * @param fullInfo true to include full info (rights, parent id, etc...) 241 * @param isEdition true to get the content in edit mode 242 * @return the post data in a map 243 */ 244 @Callable 245 public Map<String, Object> getPostDataById(String id, boolean fullInfo, boolean isEdition) 246 { 247 JCRPost post = (JCRPost) _resolver.resolveById(id); 248 return getPostData(post, fullInfo, isEdition); 249 } 250 251 /** 252 * Get post info 253 * @param post The post 254 * @param fullInfo true to include full info (rights, parent id, etc...) 255 * @param isEdition true to get the content in edit mode 256 * @return the post data in a map 257 */ 258 public Map<String, Object> getPostData(JCRPost post, boolean fullInfo, boolean isEdition) 259 { 260 Map<String, Object> result = new HashMap<>(); 261 262 UserIdentity author = post.getAuthor(); 263 boolean isOwner = author.equals(_currentUserProvider.getUser()); 264 265 result.put("id", post.getId()); 266 result.put("content", isEdition ? getPostContentForEditing(post) : getPostContent(post)); 267 result.put("author", _userHelper.user2json(author)); 268 result.put("isOwner", isOwner); 269 270 result.put("creationDate", ParameterHelper.valueToString(post.getCreationDate())); 271 result.put("lastModifiedDate", ParameterHelper.valueToString(post.getLastModified())); 272 273 result.put("canEdit", canEdit(post)); 274 result.put("canDelete", canDelete(post)); 275 276 if (fullInfo) 277 { 278 result.putAll(_getPostDataFullInfo(post)); 279 } 280 281 return result; 282 } 283 284 /** 285 * Determines if the post can be edited by current user 286 * @param post The post 287 * @return true if the post can be edited 288 */ 289 protected boolean canEdit(JCRPost post) 290 { 291 boolean isOwner = post.getAuthor().equals(_currentUserProvider.getUser()); 292 return isOwner || _explorerResourcesDAO.getUserRight(_currentUserProvider.getUser(), __RIGHTS_POST_EDIT, post); 293 } 294 295 /** 296 * Determines if the post can be deleted by current user 297 * @param post The post 298 * @return true if the post can be deleted 299 */ 300 protected boolean canDelete(JCRPost post) 301 { 302 boolean isOwner = post.getAuthor().equals(_currentUserProvider.getUser()); 303 return isOwner || _explorerResourcesDAO.getUserRight(_currentUserProvider.getUser(), __RIGHTS_POST_DELETE, post); 304 } 305 306 /** 307 * Convert the content of a post to a string (removing HTML tags) 308 * @param post The post 309 * @return the content of the post as string. 310 */ 311 public String convertPostToString (JCRPost post) 312 { 313 try 314 { 315 PostRichTextHandler txtHandler = new PostRichTextHandler(); 316 _parser.parse(new InputSource(post.getContent().getInputStream()), txtHandler); 317 return txtHandler.getValue().trim(); 318 } 319 catch (IOException | SAXException e) 320 { 321 getLogger().error("Cannot parse inputstream", e); 322 return null; 323 } 324 } 325 326 /** 327 * Retrieves the post additional info (rights, parent id, etc...) 328 * @param post The post 329 * @return the post additional info (rights, parent id, etc...) in a map 330 */ 331 protected Map<String, Object> _getPostDataFullInfo(JCRPost post) 332 { 333 Map<String, Object> result = new HashMap<>(); 334 335 ExplorerNode explorerNode = post.getParent(); 336 ExplorerNode root = explorerNode; 337 while (true) 338 { 339 if (root.getParent() instanceof ExplorerNode) 340 { 341 root = root.getParent(); 342 } 343 else 344 { 345 break; 346 } 347 } 348 result.put("rootId", root.getId()); 349 result.put("parentId", explorerNode.getId()); 350 result.put("name", post.getName()); 351 result.put("path", explorerNode.getExplorerPath()); 352 result.put("isModifiable", true); 353 354 result.put("rights", _getUserRights(explorerNode)); 355 356 return result; 357 } 358 359 /** 360 * Get the user rights on the resource collection 361 * @param node The explorer node 362 * @return The user's rights 363 */ 364 protected Set<String> _getUserRights(ExplorerNode node) 365 { 366 UserIdentity user = _currentUserProvider.getUser(); 367 return _rightManager.getUserRights(user, node); 368 } 369 370 /** 371 * Add a thread 372 * @param id The identifier of the parent in which the thread will be added 373 * @param inputName The desired name for the thread 374 * @param inputDescription The thread description 375 * @return The result map with id, parentId and name keys 376 * @throws IllegalAccessException If the user has no sufficient rights 377 */ 378 @Callable 379 public Map<String, Object> addThread(String id, String inputName, String inputDescription) throws IllegalAccessException 380 { 381 Map<String, Object> result = new HashMap<>(); 382 383 String originalName = inputName; 384 String description = inputDescription.replaceAll("\\r\\n|\\r|\\n", "<br />"); 385 assert id != null; 386 387 AmetysObject object = _resolver.resolveById(id); 388 if (!(object instanceof ModifiableResourceCollection || object instanceof JCRThread)) 389 { 390 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 391 } 392 393 ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) object; 394 395 // Check user right 396 _explorerResourcesDAO.checkUserRight(object, __RIGHTS_THREAD_ADD); 397 398 if (!_explorerResourcesDAO.checkLock(parent)) 399 { 400 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify thread '" + object.getName() + "' but it is locked by another user"); 401 result.put("message", "locked"); 402 return result; 403 } 404 405 int index = 2; 406 String name = originalName; 407 while (parent.hasChild(name)) 408 { 409 name = originalName + " (" + index + ")"; 410 index++; 411 } 412 413 JCRThread thread = parent.createChild(name, JCRThreadFactory.THREAD_NODETYPE); 414 thread.setTitle(originalName); 415 thread.setDescription(description); 416 thread.setAuthor(_currentUserProvider.getUser()); 417 Date now = new Date(); 418 thread.setCreationDate(now); 419 420 parent.saveChanges(); 421 422 result.put("id", thread.getId()); 423 result.put("parentId", id); 424 result.put("name", name); 425 426 // Notify listeners 427 Map<String, Object> eventParams = new HashMap<>(); 428 eventParams.put(ObservationConstants.ARGS_ID, thread.getId()); 429 eventParams.put(ObservationConstants.ARGS_THREAD, thread); 430 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_CREATED, _currentUserProvider.getUser(), eventParams)); 431 432 return result; 433 } 434 435 /** 436 * Edit a thread 437 * @param id The identifier of the thread 438 * @param inputName The new name 439 * @param inputDescription The new description 440 * @return The result map with id and name keys 441 * @throws IllegalAccessException If the user has no sufficient rights 442 */ 443 @Callable 444 public Map<String, Object> editThread(String id, String inputName, String inputDescription) throws IllegalAccessException 445 { 446 Map<String, Object> result = new HashMap<>(); 447 448 String description = inputDescription.replaceAll("\\r\\n|\\r|\\n", "<br />"); 449 assert id != null; 450 451 AmetysObject object = _resolver.resolveById(id); 452 if (!(object instanceof JCRThread)) 453 { 454 throw new IllegalClassException(JCRThread.class, object.getClass()); 455 } 456 457 JCRThread thread = (JCRThread) object; 458 459 // Check user right 460 if (!_currentUserProvider.getUser().equals(thread.getAuthor())) 461 { 462 _explorerResourcesDAO.checkUserRight(object, __RIGHTS_THREAD_EDIT); 463 } 464 465 if (!_explorerResourcesDAO.checkLock(thread)) 466 { 467 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify thread '" + object.getName() + "' but it is locked by another user"); 468 result.put("message", "locked"); 469 return result; 470 } 471 472 String originalName = inputName; 473 String name = originalName; 474 if (!name.equals(thread.getName())) 475 { 476 ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) thread.getParent(); 477 int index = 2; 478 while (parent.hasChild(name)) 479 { 480 name = originalName + " (" + index + ")"; 481 index++; 482 } 483 thread.rename(name); 484 } 485 486 487 thread.setTitle(name); 488 if (description != null) 489 { 490 thread.setDescription(description); 491 } 492 493 thread.saveChanges(); 494 495 result.put("id", thread.getId()); 496 result.put("title", name); 497 498 // Notify listeners 499 Map<String, Object> eventParams = new HashMap<>(); 500 eventParams.put(ObservationConstants.ARGS_ID, thread.getId()); 501 eventParams.put(ObservationConstants.ARGS_THREAD, thread); 502 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_UPDATED, _currentUserProvider.getUser(), eventParams)); 503 504 return result; 505 } 506 507 /** 508 * Rename a thread 509 * @param id The id of the thread 510 * @param name The thread name 511 * @return The result map with id, name and message keys 512 * @throws IllegalAccessException If the user has no sufficient rights 513 */ 514 @Callable 515 public Map<String, Object> renameThread(String id, String name) throws IllegalAccessException 516 { 517 Map<String, Object> result = new HashMap<>(); 518 519 assert id != null; 520 521 AmetysObject object = _resolver.resolveById(id); 522 if (!(object instanceof JCRThread)) 523 { 524 throw new IllegalClassException(JCRThread.class, object.getClass()); 525 } 526 527 JCRThread thread = (JCRThread) object; 528 529 // Check user right 530 if (!_currentUserProvider.getUser().equals(thread.getAuthor())) 531 { 532 _explorerResourcesDAO.checkUserRight(object, __RIGHTS_THREAD_EDIT); 533 } 534 535 if (!_explorerResourcesDAO.checkLock(thread)) 536 { 537 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify thread '" + object.getName() + "' but it is locked by another user"); 538 result.put("message", "locked"); 539 return result; 540 } 541 542 if (!name.equals(thread.getName())) 543 { 544 ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) thread.getParent(); 545 if (parent.hasChild(name)) 546 { 547 result.put("message", "already-exist"); 548 return result; 549 } 550 thread.rename(name); 551 } 552 553 thread.setTitle(name); 554 thread.saveChanges(); 555 556 result.put("id", thread.getId()); 557 result.put("name", name); 558 559 // Notify listeners 560 Map<String, Object> eventParams = new HashMap<>(); 561 eventParams.put(ObservationConstants.ARGS_ID, thread.getId()); 562 eventParams.put(ObservationConstants.ARGS_THREAD, thread); 563 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_RENAMED, _currentUserProvider.getUser(), eventParams)); 564 565 return result; 566 } 567 568 /** 569 * Delete a thread 570 * @param id The id of the thread 571 * @return The result map with id, parent id and message keys 572 * @throws IllegalAccessException If the user has no sufficient rights 573 */ 574 @Callable 575 public Map<String, Object> deleteThread(String id) throws IllegalAccessException 576 { 577 Map<String, Object> result = new HashMap<>(); 578 579 assert id != null; 580 581 AmetysObject object = _resolver.resolveById(id); 582 if (!(object instanceof JCRThread)) 583 { 584 throw new IllegalClassException(JCRThread.class, object.getClass()); 585 } 586 587 JCRThread thread = (JCRThread) object; 588 589 // Check user right 590 if (!_currentUserProvider.getUser().equals(thread.getAuthor())) 591 { 592 _explorerResourcesDAO.checkUserRight(object, __RIGHTS_THREAD_DELETE); 593 } 594 595 if (!_explorerResourcesDAO.checkLock(thread)) 596 { 597 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify thread '" + object.getName() + "' but it is locked by another user"); 598 result.put("message", "locked"); 599 return result; 600 } 601 602 ModifiableExplorerNode parent = thread.getParent(); 603 String parentId = parent.getId(); 604 String name = thread.getName(); 605 String path = thread.getPath(); 606 607 thread.remove(); 608 parent.saveChanges(); 609 610 result.put("id", id); 611 result.put("parentId", parentId); 612 613 // Notify listeners 614 Map<String, Object> eventParams = new HashMap<>(); 615 eventParams.put(ObservationConstants.ARGS_ID, id); 616 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId); 617 eventParams.put(ObservationConstants.ARGS_NAME, name); 618 eventParams.put(ObservationConstants.ARGS_PATH, path); 619 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_DELETED, _currentUserProvider.getUser(), eventParams)); 620 621 return result; 622 } 623 624 /** 625 * Add a post 626 * @param threadId The identifier of the thread in which the post will be added 627 * @param inputContent The post content 628 * @return The result map with id, parentId and message keys 629 * @throws IllegalAccessException If the user has no sufficient rights 630 * @throws IOException If an error occurs 631 */ 632 @Callable 633 public Map<String, Object> addPost(String threadId, String inputContent) throws IllegalAccessException, IOException 634 { 635 Map<String, Object> result = new HashMap<>(); 636 637 String name = JCRPostFactory.POST_NODENAME; 638 assert threadId != null; 639 640 AmetysObject object = _resolver.resolveById(threadId); 641 if (!(object instanceof JCRThread)) 642 { 643 throw new IllegalClassException(JCRThread.class, object.getClass()); 644 } 645 646 JCRThread parent = (JCRThread) object; 647 648 // Check user right 649 if (!_currentUserProvider.getUser().equals(parent.getAuthor())) 650 { 651 _explorerResourcesDAO.checkUserRight(object.getParent(), __RIGHTS_POST_ADD); 652 } 653 654 if (!_explorerResourcesDAO.checkLock(parent)) 655 { 656 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to add a post '" + parent.getName() + "' but thread is locked by another user"); 657 result.put("message", "locked"); 658 return result; 659 } 660 661 JCRPost post = parent.createChild(name, JCRPostFactory.POST_NODETYPE); 662 663 setPostContent(post, inputContent); 664 665 post.setAuthor(_currentUserProvider.getUser()); 666 Date now = new Date(); 667 post.setCreationDate(now); 668 post.setLastModified(now); 669 670 parent.markAsRead(_currentUserProvider.getUser()); 671 672 parent.saveChanges(); 673 674 result.put("id", post.getId()); 675 result.put("parentId", threadId); 676 result.put("name", name); 677 678 // Notify listeners 679 Map<String, Object> eventParams = new HashMap<>(); 680 eventParams.put(ObservationConstants.ARGS_ID, post.getId()); 681 eventParams.put(ObservationConstants.ARGS_THREAD, parent); 682 eventParams.put(ObservationConstants.ARGS_POST, post); 683 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_POST_CREATED, _currentUserProvider.getUser(), eventParams)); 684 685 return result; 686 } 687 688 /** 689 * Update the content of a post 690 * @param post The post to update 691 * @param content The content as string 692 */ 693 protected void setPostContent(JCRPost post, String content) 694 { 695 try 696 { 697 ModifiableRichText richText = post.getContent(); 698 699 richText.setMimeType("text/plain"); 700 richText.setLastModified(new Date()); 701 richText.setInputStream(new ByteArrayInputStream(content.getBytes("UTF-8"))); 702 } 703 catch (IOException e) 704 { 705 throw new AmetysRepositoryException("Failed to set post rich text", e); 706 } 707 } 708 709 /** 710 * Get the content of a post as a String 711 * @param post the post 712 * @return The content as String 713 * @throws AmetysRepositoryException if failed to parse content 714 */ 715 protected String getPostContent(JCRPost post) throws AmetysRepositoryException 716 { 717 try 718 { 719 ModifiableRichText richText = post.getContent(); 720 return IOUtils.toString(richText.getInputStream(), "UTF-8"); 721 } 722 catch (IOException e) 723 { 724 throw new AmetysRepositoryException("Failed to get post rich text", e); 725 } 726 } 727 728 /** 729 * Get the content of a post to edit as a String 730 * @param post the post 731 * @return The content as String 732 * @throws AmetysRepositoryException if failed to parse content 733 */ 734 protected String getPostContentForEditing(JCRPost post) throws AmetysRepositoryException 735 { 736 return getPostContent(post); 737 } 738 739 /** 740 * Edit a post 741 * @param id The identifier of the post 742 * @param inputContent The post content 743 * @return The result map with id, parentId and message keys 744 * @throws IllegalAccessException If the user has no sufficient rights 745 * @throws IOException If an error occurs 746 */ 747 @Callable 748 public Map<String, Object> editPost(String id, String inputContent) throws IllegalAccessException, IOException 749 { 750 Map<String, Object> result = new HashMap<>(); 751 752 assert id != null; 753 754 AmetysObject object = _resolver.resolveById(id); 755 if (!(object instanceof JCRPost)) 756 { 757 throw new IllegalClassException(JCRPost.class, object.getClass()); 758 } 759 760 JCRPost post = (JCRPost) object; 761 762 // Check user right 763 if (!_currentUserProvider.getUser().equals(post.getAuthor())) 764 { 765 _explorerResourcesDAO.checkUserRight(object.getParent(), __RIGHTS_POST_EDIT); 766 } 767 768 if (!_explorerResourcesDAO.checkLock(post)) 769 { 770 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify post '" + object.getName() + "' but it is locked by another user"); 771 result.put("message", "locked"); 772 return result; 773 } 774 775 setPostContent(post, inputContent); 776 777 Date now = new Date(); 778 post.setLastModified(now); 779 780 post.saveChanges(); 781 782 result.put("id", post.getId()); 783 result.put("content", getPostContent(post)); 784 785 // Notify listeners 786 Map<String, Object> eventParams = new HashMap<>(); 787 eventParams.put(ObservationConstants.ARGS_ID, post.getId()); 788 eventParams.put(ObservationConstants.ARGS_THREAD, post.getParent()); 789 eventParams.put(ObservationConstants.ARGS_POST, post); 790 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_POST_UPDATED, _currentUserProvider.getUser(), eventParams)); 791 792 return result; 793 } 794 795 /** 796 * Delete a post 797 * @param id The id of the post 798 * @return The result map with id, parent id and message keys 799 * @throws IllegalAccessException If the user has no sufficient rights 800 */ 801 @Callable 802 public Map<String, Object> deletePost(String id) throws IllegalAccessException 803 { 804 Map<String, Object> result = new HashMap<>(); 805 806 assert id != null; 807 808 AmetysObject object = _resolver.resolveById(id); 809 if (!(object instanceof JCRPost)) 810 { 811 throw new IllegalClassException(JCRPost.class, object.getClass()); 812 } 813 814 JCRPost post = (JCRPost) object; 815 816 // Check user right 817 if (!_currentUserProvider.getUser().equals(post.getAuthor())) 818 { 819 _explorerResourcesDAO.checkUserRight(object.getParent(), __RIGHTS_POST_DELETE); 820 } 821 822 if (!_explorerResourcesDAO.checkLock(post)) 823 { 824 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete thread '" + object.getName() + "' but it is locked by another user"); 825 result.put("message", "locked"); 826 return result; 827 } 828 829 ModifiableExplorerNode parent = post.getParent(); 830 String parentId = parent.getId(); 831 String name = post.getName(); 832 String path = post.getPath(); 833 834 post.remove(); 835 parent.saveChanges(); 836 837 result.put("id", id); 838 result.put("parentId", parentId); 839 840 // Notify listeners 841 Map<String, Object> eventParams = new HashMap<>(); 842 eventParams.put(ObservationConstants.ARGS_ID, id); 843 eventParams.put(ObservationConstants.ARGS_THREAD, parent); 844 eventParams.put(ObservationConstants.ARGS_NAME, name); 845 eventParams.put(ObservationConstants.ARGS_PATH, path); 846 _observationManager.notify(new Event(ObservationConstants.EVENT_THREAD_POST_DELETED, _currentUserProvider.getUser(), eventParams)); 847 848 return result; 849 } 850 851 /** 852 * Mark a thread as read by the current user 853 * @param id The thread id 854 * @return The result map with id, parent id and message keys 855 */ 856 @Callable 857 public Map<String, Object> markAsRead(String id) 858 { 859 Map<String, Object> result = new HashMap<>(); 860 861 assert id != null; 862 863 AmetysObject object = _resolver.resolveById(id); 864 if (!(object instanceof JCRThread)) 865 { 866 throw new IllegalClassException(JCRThread.class, object.getClass()); 867 } 868 869 JCRThread thread = (JCRThread) object; 870 871 if (!_explorerResourcesDAO.checkLock(thread)) 872 { 873 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify thread '" + object.getName() + "' but it is locked by another user"); 874 result.put("message", "locked"); 875 return result; 876 } 877 878 UserIdentity user = _currentUserProvider.getUser(); 879 thread.markAsRead(user); 880 881 thread.saveChanges(); 882 883 result.put("id", thread.getId()); 884 return result; 885 } 886 887}