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