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}