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