001/*
002 *  Copyright 2010 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 */
016
017package org.ametys.cms.repository.comment;
018
019import java.util.ArrayList;
020import java.util.Date;
021import java.util.List;
022
023import org.ametys.cms.repository.ReactionableObjectHelper;
024import org.ametys.cms.repository.ReactionableObject;
025import org.ametys.core.user.UserIdentity;
026import org.ametys.plugins.repository.AmetysRepositoryException;
027import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
028
029/**
030 * A comment on a commentable content
031 */
032public class Comment implements ReactionableObject
033{
034    /** Constants for comments Metadat* */
035    public static final String METADATA_COMMENTS = "comments";
036    /** Constants for comments Metadata not validted */
037    public static final String METADATA_COMMENTS_VALIDATED = "validated";
038    /** Constants for comments Metadata validated */
039    public static final String METADATA_COMMENTS_NOTVALIDATED = "not-validated";
040
041    /** Constants for creation Metadata */
042    public static final String METADATA_COMMENT_CREATIONDATE = "creation";
043    /** Constants for author name Metadata */
044    public static final String METADATA_COMMENT_AUTHORNAME = "author-name";
045    /** Constants for author email Metadata */
046    public static final String METADATA_COMMENT_AUTHOREMAIL = "author-email";
047    /** Constants for author email hidden Metadata */
048    public static final String METADATA_COMMENT_AUTHOREMAIL_HIDDEN = "author-email-hidden";
049    /** Constants for author url Metadata */
050    public static final String METADATA_COMMENT_AUTHORURL = "author-url";
051    /** Constants for the content Metadata */
052    public static final String METADATA_COMMENT_CONTENT = "content";
053    /** Constants for the validated status Metadata */
054    public static final String METADATA_COMMENT_VALIDATED = "validated";
055    /** Constants for the number of reporters Metadata */
056    public static final String METADATA_COMMENT_NB_REPORTERS = "nb-reporters";
057
058    /** Constants for the separator */
059    public static final String ID_SEPARATOR = "_";
060    
061    /** The content to comment */
062    protected ModifiableCompositeMetadata _contentMetadataHolder;
063    /** The metadata node of the comment */
064    protected ModifiableCompositeMetadata _metadata;
065    /** The id of the comment (unique in the content) */
066    protected String _id;
067    
068    /**
069     * Retrieves a comment by its id
070     * @param contentUnversionnedMetadataHolder The unversionned metadate holder of the content hosting the comment
071     * @param commentId The id of the comment to retrieve
072     * @throws AmetysRepositoryException if an error occured
073     */
074    public Comment(ModifiableCompositeMetadata contentUnversionnedMetadataHolder, String commentId)
075    {
076        _contentMetadataHolder = contentUnversionnedMetadataHolder;
077        _id = commentId;
078        
079        String[] commentIdAsTab = commentId.split(ID_SEPARATOR);
080        int i = 0;
081        ModifiableCompositeMetadata currentMetadataHolder = _contentMetadataHolder;
082        while (i < commentIdAsTab.length)
083        {
084            ModifiableCompositeMetadata commentsComposite = currentMetadataHolder.getCompositeMetadata(Comment.METADATA_COMMENTS);
085            currentMetadataHolder = commentsComposite.getCompositeMetadata(commentIdAsTab[i]);
086            i++;
087        }
088        
089        _metadata = currentMetadataHolder;
090    }
091    
092    /**
093     * Creates a new comment on the content
094     * @param contentUnversionnedMetadataHolder The unversionned metadate holder of the content where to add the new comment 
095     */
096    public Comment(ModifiableCompositeMetadata contentUnversionnedMetadataHolder)
097    {
098        _contentMetadataHolder = contentUnversionnedMetadataHolder;
099
100        ModifiableCompositeMetadata parent = _contentMetadataHolder.getCompositeMetadata(METADATA_COMMENTS, true);
101
102        String base = "comment-";
103        int i = 0;
104        while (parent.hasMetadata(base + i))
105        {
106            i++;
107        }
108
109        _id = base + i;
110        _metadata = _contentMetadataHolder.getCompositeMetadata(METADATA_COMMENTS, true).getCompositeMetadata(_id, true);
111        _metadata.setMetadata(METADATA_COMMENT_CREATIONDATE, new Date());
112        
113        update();
114    }
115    
116    /**
117     * Creates a new sub comment of the comment
118     * @param comment The parent comment
119     */
120    public Comment(Comment comment)
121    {
122        _contentMetadataHolder = comment.getContentMetadata();
123
124        ModifiableCompositeMetadata parent = comment.getMetadata().getCompositeMetadata(METADATA_COMMENTS, true);
125
126        String base = "comment-";
127        int i = 0;
128        while (parent.hasMetadata(base + i))
129        {
130            i++;
131        }
132
133        String commentName = base + i;
134        _id = comment.getId() + ID_SEPARATOR + commentName;
135        _metadata = parent.getCompositeMetadata(commentName, true);
136        _metadata.setMetadata(METADATA_COMMENT_CREATIONDATE, new Date());
137        
138        update();
139    }
140    
141    /**
142     * Get the composite metadata object holding the comment
143     * @return the composite metadata
144     */
145    public ModifiableCompositeMetadata getMetadata()
146    {
147        return _metadata;
148    }
149    
150    /**
151     * Get the composite metadata object holding the content
152     * @return the content composite metadata
153     */
154    public ModifiableCompositeMetadata getContentMetadata()
155    {
156        return _contentMetadataHolder;
157    }
158    
159    /**
160     * The comment id (unique to the content)
161     * @return The id. Cannot be null.
162     */
163    public String getId()
164    {
165        return _id;
166    }
167    /**
168     * Get the date and time the comment was created
169     * @return The non null date of creation of the comment.
170     */
171    public Date getCreationDate()
172    {
173        return _metadata.getDate(METADATA_COMMENT_CREATIONDATE);
174    }
175    
176    /**
177     * Get the readable name of the author.
178     * @return The full name. Can be null.
179     */
180    public String getAuthorName()
181    {
182        if (_metadata.hasMetadata(METADATA_COMMENT_AUTHORNAME))
183        {
184            return _metadata.getString(METADATA_COMMENT_AUTHORNAME);
185        }
186        else
187        {
188            return null;
189        }
190    }
191    /**
192     * Set the readable name of the author.
193     * @param name The full name. Can be null to remove the name.
194     */
195    public void setAuthorName(String name)
196    {
197        if (name != null)
198        {
199            _metadata.setMetadata(METADATA_COMMENT_AUTHORNAME, name);
200        }
201        else if (_metadata.hasMetadata(METADATA_COMMENT_AUTHORNAME))
202        {
203            _metadata.removeMetadata(METADATA_COMMENT_AUTHORNAME);
204        }
205    }
206    
207    /**
208     * Get the email of the author.
209     * @return The ameil. Can be null.
210     */
211    public String getAuthorEmail()
212    {
213        if (_metadata.hasMetadata(METADATA_COMMENT_AUTHOREMAIL))
214        {
215            return _metadata.getString(METADATA_COMMENT_AUTHOREMAIL);
216        }
217        else
218        {
219            return null;
220        }
221    }
222    /**
223     * Set the email of the author.
224     * @param email The email. Can be null to remove the email.
225     */
226    public void setAuthorEmail(String email)
227    {
228        if (email != null)
229        {
230            _metadata.setMetadata(METADATA_COMMENT_AUTHOREMAIL, email);
231        }
232        else if (_metadata.hasMetadata(METADATA_COMMENT_AUTHOREMAIL))
233        {
234            _metadata.removeMetadata(METADATA_COMMENT_AUTHOREMAIL);
235        }
236    }
237    
238    /**
239     * Get the url given by the author as its personnal site url.
240     * @return The url. Can be null.
241     */
242    public String getAuthorURL()
243    {
244        if (_metadata.hasMetadata(METADATA_COMMENT_AUTHORURL))
245        {
246            return _metadata.getString(METADATA_COMMENT_AUTHORURL);
247        }
248        else
249        {
250            return null;
251        }
252    }
253    /**
254     * Set the personnal site url of the author
255     * @param url The url. Can be null to remove url.
256     */
257    public void setAuthorURL(String url)
258    {
259        if (url != null)
260        {
261            _metadata.setMetadata(METADATA_COMMENT_AUTHORURL, url);
262        }
263        else if (_metadata.hasMetadata(METADATA_COMMENT_AUTHORURL))
264        {
265            _metadata.removeMetadata(METADATA_COMMENT_AUTHORURL);
266        }
267    }
268    
269    /**
270     * Does the email of the authors have to be hidden ?
271     * @return true (default value) if the email does not have to appears to others users. Can still be used for administration.
272     */
273    public boolean isEmailHidden()
274    {
275        if (_metadata.hasMetadata(METADATA_COMMENT_AUTHOREMAIL_HIDDEN))
276        {
277            return _metadata.getBoolean(METADATA_COMMENT_AUTHOREMAIL_HIDDEN);
278        }
279        else
280        {
281            return true;
282        }
283    }
284    /**
285     * Set the email hidden status.
286     * @param hideEmail true to set the email as hidden.
287     */
288    public void setEmailHiddenStatus(boolean hideEmail)
289    {
290        _metadata.setMetadata(METADATA_COMMENT_AUTHOREMAIL_HIDDEN, hideEmail);
291    }
292    
293    /**
294     * Get the content of the comment. A simple String (with \n or \t).
295     * @return The content. Can be null.
296     */
297    public String getContent()
298    {
299        if (_metadata.hasMetadata(METADATA_COMMENT_CONTENT))
300        {
301            return _metadata.getString(METADATA_COMMENT_CONTENT);
302        }
303        else
304        {
305            return null;
306        }
307    }
308    /**
309     * Set the content of the comment.
310     * @param content The content to set. Can be null to remove the content. Have to be a simple String (with \n or \t).
311     */
312    public void setContent(String content)
313    {
314        if (content != null)
315        {
316            _metadata.setMetadata(METADATA_COMMENT_CONTENT, content);
317        }
318        else if (_metadata.hasMetadata(METADATA_COMMENT_CONTENT))
319        {
320            _metadata.removeMetadata(METADATA_COMMENT_CONTENT);
321        }
322    }
323    
324    /**
325     * Is the comment validated
326     * @return the status of validation of the comment
327     */
328    public boolean isValidated()
329    {
330        if (_metadata.hasMetadata(METADATA_COMMENT_VALIDATED))
331        {
332            return _metadata.getBoolean(METADATA_COMMENT_VALIDATED);
333        }
334        else
335        {
336            return false;
337        }
338    }
339    
340    /**
341     * Set the validation status of the comment
342     * @param validated true the comment is validated
343     */
344    public void setValidated(boolean validated)
345    {
346        _metadata.setMetadata(METADATA_COMMENT_VALIDATED, validated);
347        update();
348    }
349    
350    /**
351     * Reset to 0 the number of reporters
352     */
353    public void resetNbReporters()
354    {
355        _metadata.setMetadata(METADATA_COMMENT_NB_REPORTERS, 0);
356    }
357    /**
358     * Increase the number of reporters
359     * @return the increase number of reporters
360     */
361    public Long increaseNbReporters()
362    {
363        Long nbReporters = getNbReporters();
364        
365        Long nbReportersIncreased = nbReporters + 1;
366        _metadata.setMetadata(METADATA_COMMENT_NB_REPORTERS, nbReportersIncreased);
367        
368        return nbReportersIncreased;
369    }
370    
371    /**
372     * Get the number of reporters
373     * @return the number of reporters
374     */
375    public Long getNbReporters()
376    {
377        return _metadata.getLong(METADATA_COMMENT_NB_REPORTERS, 0L);
378    }
379    
380    /**
381     * Remove the comment.
382     */
383    public void remove()
384    {
385        String[] commentIdAsTab = _id.split(ID_SEPARATOR);
386        int i = 0;
387        ModifiableCompositeMetadata currentMetadataHolder = _contentMetadataHolder;
388        int lastIndex = commentIdAsTab.length - 1;
389        while (i < lastIndex)
390        {
391            ModifiableCompositeMetadata commentsComposite = currentMetadataHolder.getCompositeMetadata(Comment.METADATA_COMMENTS);
392            currentMetadataHolder = commentsComposite.getCompositeMetadata(commentIdAsTab[i]);
393            i++;
394        }
395        currentMetadataHolder.getCompositeMetadata(Comment.METADATA_COMMENTS).removeMetadata(commentIdAsTab[lastIndex]);
396        update();
397    }
398    
399    /**
400     * Get sub comments of the comment
401     * @param includeNotValidatedComments True to include the comments that are not validated
402     * @param includeValidatedComments True to include the comments that are validated
403     * @return the list of comments
404     */
405    public List<Comment> getSubComment(boolean includeNotValidatedComments, boolean includeValidatedComments)
406    {
407        return getComments(this, includeNotValidatedComments, includeValidatedComments);
408    }
409    
410    /**
411     * Create sub comment from this comment
412     * @return the sub comment
413     */
414    public Comment createSubComment()
415    {
416        return new Comment(this);
417    }
418    
419    /**
420     * Update the comment tag statistics
421     */
422    protected void update()
423    {
424        long validated = 0;
425        long notValidated = 0;
426        
427        List<Comment> comments = getComments(_contentMetadataHolder, true, true, false);
428        
429        for (Comment comment : comments)
430        {
431            if (comment.isValidated())
432            {
433                validated++;
434            }
435            else
436            {
437                notValidated++;
438            }
439        }
440        
441        _contentMetadataHolder.getCompositeMetadata(METADATA_COMMENTS).setMetadata(METADATA_COMMENTS_VALIDATED, validated);
442        _contentMetadataHolder.getCompositeMetadata(METADATA_COMMENTS).setMetadata(METADATA_COMMENTS_NOTVALIDATED, notValidated);
443    }
444
445    /**
446     * Get a comment
447     * @param contentUnversionnedMetadataHolder the content
448     * @param commentId The comment
449     * @return The comment
450     * @throws AmetysRepositoryException if the comment does not exist
451     */
452    public static Comment getComment(ModifiableCompositeMetadata contentUnversionnedMetadataHolder, String commentId) throws AmetysRepositoryException
453    {
454        return new Comment(contentUnversionnedMetadataHolder, commentId);
455    }
456    
457    /**
458     * Get the comments of a content 
459     * @param parentComment The parent comment
460     * @param includeNotValidatedComments True to include the comments that are not validated
461     * @param includeValidatedComments True to include the comments that are validated
462     * @return the list of comments
463     * @throws AmetysRepositoryException If an error occurred
464     */
465    public static List<Comment> getComments(Comment parentComment, boolean includeNotValidatedComments, boolean includeValidatedComments) throws AmetysRepositoryException
466    {
467        return getComments(parentComment, includeNotValidatedComments, includeValidatedComments, false);
468    }
469    
470    /**
471     * Get the comments of a content 
472     * @param parentComment The parent comment
473     * @param includeNotValidatedComments True to include the comments that are not validated
474     * @param includeValidatedComments True to include the comments that are validated
475     * @param withSubComment true if we want to get all child comments
476     * @return the list of comments
477     * @throws AmetysRepositoryException If an error occurred
478     */
479    public static List<Comment> getComments(Comment parentComment, boolean includeNotValidatedComments, boolean includeValidatedComments, boolean withSubComment) throws AmetysRepositoryException
480    {
481        ModifiableCompositeMetadata parentUnversionnedMetadataHolder = parentComment.getMetadata();
482        List<Comment> comments = new ArrayList<>();
483        
484        if (parentUnversionnedMetadataHolder.hasMetadata(METADATA_COMMENTS))
485        {
486            String[] names = parentUnversionnedMetadataHolder.getCompositeMetadata(METADATA_COMMENTS).getMetadataNames();
487            for (String name : names)
488            {
489                if (METADATA_COMMENTS_NOTVALIDATED.equals(name) || METADATA_COMMENTS_VALIDATED.equals(name))
490                {
491                    continue;
492                }
493                
494                String id = parentComment.getId() + ID_SEPARATOR + name;
495                Comment c = new Comment(parentComment.getContentMetadata(), id);
496                if (includeNotValidatedComments && !c.isValidated() || includeValidatedComments && c.isValidated())
497                {
498                    comments.add(c);
499                    if (withSubComment)
500                    {
501                        comments.addAll(getComments(c, includeNotValidatedComments, includeValidatedComments, withSubComment));
502                    }
503                }
504            }
505        }
506        
507        return comments;
508    }
509    
510    /**
511     * Get the comments of a content 
512     * @param contentUnversionnedMetadataHolder The content unversionned metadata holder
513     * @param includeNotValidatedComments True to include the comments that are not validated
514     * @param includeValidatedComments True to include the comments that are validated
515     * @return the list of comments
516     * @throws AmetysRepositoryException If an error occurred
517     */
518    public static List<Comment> getComments(ModifiableCompositeMetadata contentUnversionnedMetadataHolder, boolean includeNotValidatedComments, boolean includeValidatedComments) throws AmetysRepositoryException
519    {
520        return getComments(contentUnversionnedMetadataHolder, includeNotValidatedComments, includeValidatedComments, false);
521    }
522    
523    /**
524     * Get the comments of a content 
525     * @param contentUnversionnedMetadataHolder The content unversionned metadata holder
526     * @param includeNotValidatedComments True to include the comments that are not validated
527     * @param includeValidatedComments True to include the comments that are validated
528     * @param isRecursive true if we want to have sub comments
529     * @return the list of comments
530     * @throws AmetysRepositoryException If an error occurred
531     */
532    public static List<Comment> getComments(ModifiableCompositeMetadata contentUnversionnedMetadataHolder, boolean includeNotValidatedComments, boolean includeValidatedComments, boolean isRecursive) throws AmetysRepositoryException
533    {
534        List<Comment> comments = new ArrayList<>();
535        
536        if (contentUnversionnedMetadataHolder.hasMetadata(METADATA_COMMENTS))
537        {
538            String[] ids = contentUnversionnedMetadataHolder.getCompositeMetadata(METADATA_COMMENTS).getMetadataNames();
539            for (String id : ids)
540            {
541                if (METADATA_COMMENTS_NOTVALIDATED.equals(id) || METADATA_COMMENTS_VALIDATED.equals(id))
542                {
543                    continue;
544                }
545                
546                Comment c = new Comment(contentUnversionnedMetadataHolder, id);
547                if (includeNotValidatedComments && !c.isValidated() || includeValidatedComments && c.isValidated())
548                {
549                    comments.add(c);
550                    if (isRecursive)
551                    {
552                        comments.addAll(getComments(c, includeNotValidatedComments, includeValidatedComments, isRecursive));
553                    }
554                }
555            }
556        }
557        
558        return comments;
559    }
560
561    @Override
562    public void addReaction(UserIdentity user, ReactionType reactionType)
563    {
564        ReactionableObjectHelper.addReaction(getMetadata(), user, reactionType);
565    }
566
567    @Override
568    public void removeReaction(UserIdentity user, ReactionType reactionType)
569    {
570        ReactionableObjectHelper.removeReaction(getMetadata(), user, reactionType);
571    }
572
573    @Override
574    public List<UserIdentity> getReactionUsers(ReactionType reactionType)
575    {
576        return ReactionableObjectHelper.getReactionUsers(getMetadata(), reactionType);
577    }
578}