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.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Optional;
023import java.util.function.BiFunction;
024
025import org.apache.commons.lang3.StringUtils;
026
027import org.ametys.cms.repository.CommentableAmetysObject;
028import org.ametys.cms.repository.ReactionableObject;
029import org.ametys.cms.repository.ReactionableObjectHelper;
030import org.ametys.cms.repository.ReportableObject;
031import org.ametys.cms.repository.ReportableObjectHelper;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.repository.data.holder.DataHolder;
035import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
036import org.ametys.plugins.repository.data.holder.group.ModifiableComposite;
037import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
038import org.ametys.runtime.model.ModelItem;
039import org.ametys.runtime.model.type.ModelItemTypeConstants;
040/**
041 * A comment on a {@link CommentableAmetysObject}
042 */
043public abstract class AbstractComment implements ReactionableObject, ReportableObject
044{
045    /** Constants for comments Metadata not validted */
046    public static final String METADATA_COMMENTS_VALIDATED = "validated";
047    /** Constants for comments Metadata validated */
048    public static final String METADATA_COMMENTS_NOTVALIDATED = "not-validated";
049    /** Constants for sub comments data name */
050    public static final String SUB_COMMENTS_DATA_NAME = "comments";
051    /** Constants for referenced comment data name */
052    public static final String REFERENCED_COMMENT_DATA_NAME = "reference";
053    /** Constants for creation Metadata */
054    public static final String METADATA_COMMENT_CREATIONDATE = "creation";
055    /** Constants for last modification Metadata */
056    public static final String METADATA_COMMENT_LAST_MODIFICATION_DATE = "last-modification";
057    /** Constants for author Metadata */
058    public static final String METADATA_COMMENT_AUTHOR = "author";
059    /** Constants for author name Metadata */
060    public static final String METADATA_COMMENT_AUTHORNAME = "author-name";
061    /** Constants for author email Metadata */
062    public static final String METADATA_COMMENT_AUTHOREMAIL = "author-email";
063    /** Constants for author email hidden Metadata */
064    public static final String METADATA_COMMENT_AUTHOREMAIL_HIDDEN = "author-email-hidden";
065    /** Constants for author url Metadata */
066    public static final String METADATA_COMMENT_AUTHORURL = "author-url";
067    /** Constants for the content Metadata */
068    public static final String METADATA_COMMENT_CONTENT = "content";
069    /** Constants for the validated status Metadata */
070    public static final String METADATA_COMMENT_VALIDATED = "validated";
071    /** Constants for the is edited status Metadata */
072    public static final String METADATA_COMMENT_IS_EDITED = "is-edited";
073    /** Constants for the is deleted status Metadata */
074    public static final String METADATA_COMMENT_IS_DELETED = "is-deleted";
075    /** Constants for the is marked status Metadata */
076    public static final String METADATA_COMMENT_IS_ACCEPTED = "is-accepted";
077    /** Constant for the prefix of comments' name */
078    public static final String COMMENT_NAME_PREFIX = "comment-";
079    
080    /** Constants for the separator */
081    public static final String ID_SEPARATOR = "_";
082    
083    /** The root data holder where to store the first level of comments */
084    protected ModifiableDataHolder _rootDataHolder;
085    /** The node of the comment */
086    protected ModifiableComposite _commentComposite;
087    /** The id of the comment (unique in the ametys object) */
088    protected String _id;
089    
090    /**
091     * Retrieves a comment by its id
092     * @param rootDataHolder The root data holder hosting the first level of comments
093     * @param commentId The id of the comment to retrieve
094     * @throws AmetysRepositoryException if an error occurred
095     */
096    public AbstractComment(ModifiableDataHolder rootDataHolder, String commentId)
097    {
098        _rootDataHolder = rootDataHolder;
099        _id = commentId;
100        
101        String commentDataPath = getCommentDataPath(_id);
102        _commentComposite = _rootDataHolder.getComposite(commentDataPath);
103    }
104    
105    /**
106     * Creates a new comment
107     * @param dataHolder The data holder where to add the new comment
108     */
109    public AbstractComment(ModifiableDataHolder dataHolder)
110    {
111        this(dataHolder, Optional.empty(), Optional.empty());
112    }
113    
114    /**
115     * Creates a new comment, with the given id and creation date
116     * This method allows to create a comment from existing data (ex: data import from archive)
117     * The id is not generated here, the source is trusted. Be careful using this method
118     * @param dataHolder The data holder where to add the new comment
119     * @param commentId the comment's id
120     * @param creationDate the comment's creation date
121     */
122    public AbstractComment(ModifiableDataHolder dataHolder, String commentId, ZonedDateTime creationDate)
123    {
124        this(dataHolder, Optional.ofNullable(commentId), Optional.ofNullable(creationDate));
125    }
126    
127    /**
128     * Creates a new comment on the content, with the given id and creation date
129     * This method allow to create a comment from existing data (ex: data import from archive)
130     * The id is not generated here, the source is trusted. Be careful using this method
131     * @param dataHolder The data holder of the content where to add the new comment
132     * @param commentId the comment's id
133     * @param creationDate the comment's creation date
134     */
135    protected AbstractComment(ModifiableDataHolder dataHolder, Optional<String> commentId, Optional<ZonedDateTime> creationDate)
136    {
137        _rootDataHolder = dataHolder;
138        
139        _id = commentId.orElseGet(() -> _getNextCommentName(_rootDataHolder));
140        _commentComposite = _rootDataHolder.getComposite(_id, true);
141        _commentComposite.setValue(METADATA_COMMENT_CREATIONDATE, creationDate.orElseGet(ZonedDateTime::now));
142        
143        update();
144    }
145    
146    /**
147     * Creates a new sub comment of the comment
148     * @param comment The parent comment
149     */
150    public AbstractComment(AbstractComment comment)
151    {
152        this(comment, Optional.empty(), Optional.empty());
153    }
154    
155    /**
156     * Creates a new sub comment of the comment, with the given id and creation date
157     * This method allow to create a sub comment from existing data (ex: data import from archive)
158     * The id is not generated here, the source is trusted. Be careful using this method
159     * @param comment The parent comment
160     * @param commentId the sub comment's id
161     * @param creationDate the sub comment's creation date
162     */
163    public AbstractComment(AbstractComment comment, String commentId, ZonedDateTime creationDate)
164    {
165        this(comment, Optional.ofNullable(commentId), Optional.ofNullable(creationDate));
166    }
167    
168    /**
169     * Creates a new sub comment of the comment, with the given id and creation date
170     * This method allow to create a sub comment from existing data (ex: data import from archive)
171     * The id is not generated here, the source is trusted. Be careful using this method
172     * @param comment The parent comment
173     * @param commentId the sub comment's id
174     * @param creationDate the sub comment's creation date
175     */
176    protected AbstractComment(AbstractComment comment, Optional<String> commentId, Optional<ZonedDateTime> creationDate)
177    {
178        _rootDataHolder = comment._rootDataHolder;
179        ModifiableComposite subCommentsDataHolder = comment._commentComposite.getComposite(SUB_COMMENTS_DATA_NAME, true);
180        
181        String commentName = commentId.map(id -> StringUtils.substringAfterLast(id, ID_SEPARATOR))
182                .orElseGet(() -> _getNextCommentName(subCommentsDataHolder));
183        _id = commentId.orElseGet(() -> comment.getId() + ID_SEPARATOR + commentName);
184        
185        _commentComposite = subCommentsDataHolder.getComposite(commentName, true);
186        _commentComposite.setValue(METADATA_COMMENT_CREATIONDATE, creationDate.orElseGet(ZonedDateTime::now));
187        
188        update();
189    }
190    
191    /**
192     * Creates a reference comment of the comment, with the given comment
193     * @param parentComment The parent comment
194     * @param refComment the referenced comment
195     */
196    protected AbstractComment(AbstractComment parentComment, AbstractComment refComment)
197    {
198        _rootDataHolder = parentComment._rootDataHolder;
199        ModifiableComposite subCommentsDataHolder = parentComment._commentComposite.getComposite(REFERENCED_COMMENT_DATA_NAME, true);
200        
201        _id = refComment.getId();
202        _commentComposite = subCommentsDataHolder.getComposite(refComment.getId(), true);
203        refComment._commentComposite.copyTo(_commentComposite);
204    }
205    
206    private static String _getNextCommentName(DataHolder dataHolder)
207    {
208        String base = COMMENT_NAME_PREFIX;
209        int i = 0;
210        while (dataHolder.hasValueOrEmpty(base + i))
211        {
212            i++;
213        }
214        
215        return base + i;
216    }
217    
218    /**
219     * Retrieves the path of the comment with the given identifier
220     * @param commentId the comment identifier
221     * @return the path of the comment
222     */
223    protected String getCommentDataPath(String commentId)
224    {
225        String[] commentIdSegments = commentId.split(ID_SEPARATOR);
226        return StringUtils.join(commentIdSegments, ModelItem.ITEM_PATH_SEPARATOR + SUB_COMMENTS_DATA_NAME + ModelItem.ITEM_PATH_SEPARATOR);
227    }
228    
229    /**
230     * Retrieves the repository data of the {@link AbstractComment}
231     * @return the repository data of the {@link AbstractComment}
232     */
233    public ModifiableRepositoryData getRepositoryData()
234    {
235        return _commentComposite.getRepositoryData();
236    }
237    
238    /**
239     * The comment id (unique to the ametys object)
240     * @return The id. Cannot be null.
241     */
242    public String getId()
243    {
244        return _id;
245    }
246    
247    /**
248     * Get the date and time the comment was created
249     * @return The non null date of creation of the comment.
250     */
251    public ZonedDateTime getCreationDate()
252    {
253        return _commentComposite.getValue(METADATA_COMMENT_CREATIONDATE);
254    }
255    
256    /**
257     * Get the date and time the comment was last modified
258     * @return The non null date of last modification of the comment.
259     */
260    public ZonedDateTime getLastModificationDate()
261    {
262        return _commentComposite.getValue(METADATA_COMMENT_LAST_MODIFICATION_DATE);
263    }
264    
265    /**
266     * Set the date and time the comment was last modified
267     * @param lastModificationDate the last modification date
268     */
269    public void setLastModificationDate(ZonedDateTime lastModificationDate)
270    {
271        _commentComposite.setValue(METADATA_COMMENT_LAST_MODIFICATION_DATE, lastModificationDate);
272    }
273
274    /**
275     * Get the comment's author
276     * @return the comment's author
277     */
278    public UserIdentity getAuthor()
279    {
280        return _commentComposite.getValueOfType(METADATA_COMMENT_AUTHOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID);
281    }
282    
283    /**
284     * Set the comment's author.
285     * @param author the author. Can be null to remove the name.
286     */
287    public void setAuthor(UserIdentity author)
288    {
289        _commentComposite.setValue(METADATA_COMMENT_AUTHOR, author, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID);
290    }
291    
292    /**
293     * Get the readable name of the author.
294     * @return The full name. Can be null.
295     */
296    public String getAuthorName()
297    {
298        return _commentComposite.getValueOfType(METADATA_COMMENT_AUTHORNAME, ModelItemTypeConstants.STRING_TYPE_ID);
299    }
300    
301    /**
302     * Set the readable name of the author.
303     * @param name The full name. Can be null to remove the name.
304     */
305    public void setAuthorName(String name)
306    {
307        _commentComposite.setValue(METADATA_COMMENT_AUTHORNAME, name, ModelItemTypeConstants.STRING_TYPE_ID);
308    }
309    
310    /**
311     * Get the email of the author.
312     * @return The ameil. Can be null.
313     */
314    public String getAuthorEmail()
315    {
316        return _commentComposite.getValueOfType(METADATA_COMMENT_AUTHOREMAIL, ModelItemTypeConstants.STRING_TYPE_ID);
317    }
318    
319    /**
320     * Set the email of the author.
321     * @param email The email. Can be null to remove the email.
322     */
323    public void setAuthorEmail(String email)
324    {
325        _commentComposite.setValue(METADATA_COMMENT_AUTHOREMAIL, email, ModelItemTypeConstants.STRING_TYPE_ID);
326    }
327    
328    /**
329     * Get the url given by the author as its personal site url.
330     * @return The url. Can be null.
331     */
332    public String getAuthorURL()
333    {
334        return _commentComposite.getValueOfType(METADATA_COMMENT_AUTHORURL, ModelItemTypeConstants.STRING_TYPE_ID);
335    }
336    
337    /**
338     * Set the personal site url of the author
339     * @param url The url. Can be null to remove url.
340     */
341    public void setAuthorURL(String url)
342    {
343        _commentComposite.setValue(METADATA_COMMENT_AUTHORURL, url, ModelItemTypeConstants.STRING_TYPE_ID);
344    }
345    
346    /**
347     * Does the email of the authors have to be hidden ?
348     * @return true (default value) if the email does not have to appears to others users. Can still be used for administration.
349     */
350    public boolean isEmailHidden()
351    {
352        return _commentComposite.getValueOfTypeOrDefault(METADATA_COMMENT_AUTHOREMAIL_HIDDEN, ModelItemTypeConstants.BOOLEAN_TYPE_ID, true);
353    }
354    /**
355     * Set the email hidden status.
356     * @param hideEmail true to set the email as hidden.
357     */
358    public void setEmailHiddenStatus(boolean hideEmail)
359    {
360        _commentComposite.setValue(METADATA_COMMENT_AUTHOREMAIL_HIDDEN, hideEmail, ModelItemTypeConstants.BOOLEAN_TYPE_ID);
361    }
362    
363    /**
364     * Does the comment is edited
365     * @return true the email is edited
366     */
367    public boolean isEdited()
368    {
369        return _commentComposite.getValueOfTypeOrDefault(METADATA_COMMENT_IS_EDITED, ModelItemTypeConstants.BOOLEAN_TYPE_ID, false);
370    }
371    /**
372     * Set the comment to edited.
373     * @param isEdited true to set the comment to edited.
374     */
375    public void setEdited(boolean isEdited)
376    {
377        _commentComposite.setValue(METADATA_COMMENT_IS_EDITED, isEdited, ModelItemTypeConstants.BOOLEAN_TYPE_ID);
378    }
379    
380    /**
381     * Does the comment is deleted
382     * @return true the comment is deleted
383     */
384    public boolean isDeleted()
385    {
386        return _commentComposite.getValueOfTypeOrDefault(METADATA_COMMENT_IS_DELETED, ModelItemTypeConstants.BOOLEAN_TYPE_ID, false);
387    }
388    /**
389     * Set the comment to deleted.
390     * @param isEdited true to set the comment to deleted.
391     */
392    public void setDeleted(boolean isEdited)
393    {
394        _commentComposite.setValue(METADATA_COMMENT_IS_DELETED, isEdited, ModelItemTypeConstants.BOOLEAN_TYPE_ID);
395    }
396    
397    /**
398     * Get the content of the comment. A simple String (with \n or \t).
399     * @return The content. Can be null.
400     */
401    public String getContent()
402    {
403        return _commentComposite.getValueOfType(METADATA_COMMENT_CONTENT, ModelItemTypeConstants.STRING_TYPE_ID);
404    }
405    /**
406     * Set the content of the comment.
407     * @param content The content to set. Can be null to remove the content. Have to be a simple String (with \n or \t).
408     */
409    public void setContent(String content)
410    {
411        _commentComposite.setValue(METADATA_COMMENT_CONTENT, content, ModelItemTypeConstants.STRING_TYPE_ID);
412    }
413
414    /**
415     * Is the comment validated
416     * @return the status of validation of the comment
417     */
418    public boolean isValidated()
419    {
420        return _commentComposite.getValueOfTypeOrDefault(METADATA_COMMENT_VALIDATED, ModelItemTypeConstants.BOOLEAN_TYPE_ID, false);
421    }
422    
423    /**
424     * Set the validation status of the comment
425     * @param validated true the comment is validated
426     */
427    public void setValidated(boolean validated)
428    {
429        _commentComposite.setValue(METADATA_COMMENT_VALIDATED, validated, ModelItemTypeConstants.BOOLEAN_TYPE_ID);
430        update();
431    }
432    
433    public void addReport()
434    {
435        ReportableObjectHelper.addReport(_commentComposite);
436    }
437    
438    public void setReportsCount(long reportsCount)
439    {
440        ReportableObjectHelper.setReportsCount(_commentComposite, reportsCount);
441    }
442
443    public void clearReports()
444    {
445        ReportableObjectHelper.clearReports(_commentComposite);
446    }
447
448    public long getReportsCount()
449    {
450        return ReportableObjectHelper.getReportsCount(_commentComposite);
451    }
452
453    /**
454     * Remove the comment.
455     */
456    public void remove()
457    {
458        String commentDataPath = getCommentDataPath(_id);
459        _rootDataHolder.removeValue(commentDataPath);
460        update();
461    }
462    
463    /**
464     * Get sub comments of the comment
465     * @param <T> type of the value to retrieve
466     * @param includeNotValidatedComments True to include the comments that are not validated
467     * @param includeValidatedComments True to include the comments that are validated
468     * @return the list of comments
469     */
470    public abstract <T extends AbstractComment> List<T> getSubComment(boolean includeNotValidatedComments, boolean includeValidatedComments);
471    
472    /**
473     * Create sub comment from this comment
474     * @param <T> type of the value to retrieve
475     * @return the sub comment
476     */
477    public abstract <T extends AbstractComment> T createSubComment();
478    
479    /**
480     * Creates a sub comment from this comment, with the given id and creation date
481     * This method allow to create a sub comment from existing data (ex: data import from archive)
482     * The id is not generated here, the source is trusted. Be careful using this method
483     * @param <T> type of the value to retrieve
484     * @param commentId the comment's id
485     * @param creationDate the comment's creation date
486     * @return the new comment
487     */
488    public abstract <T extends AbstractComment> T createSubComment(String commentId, ZonedDateTime creationDate);
489    
490    /**
491     * Get the referenced comment of the comment
492     * @param <T> type of the value to retrieve
493     * @return the referenced comment
494     */
495    public abstract <T extends AbstractComment> T getReferencedComment();
496    
497    /**
498     * Creates a referenced comment from this comment, with the given comment
499     * @param <T> type of the value to retrieve
500     * @param comment the comment to reference
501     * @return the referenced comment
502     */
503    public abstract <T extends AbstractComment> T createReferencedComment(T comment);
504    
505    /**
506     * Update the comment tag statistics
507     */
508    protected abstract void update();
509    
510    /**
511     * Get the sub comments of the given comment
512     * @param <T> type of the value to retrieve
513     * @param parentComment The parent comment
514     * @param includeNotValidatedComments True to include the comments that are not validated
515     * @param includeValidatedComments True to include the comments that are validated
516     * @param commentCreator function to create new comment
517     * @return the list of sub comments
518     * @throws AmetysRepositoryException If an error occurred
519     */
520    public static <T extends AbstractComment> List<T> getComments(T parentComment, boolean includeNotValidatedComments, boolean includeValidatedComments, BiFunction<ModifiableDataHolder, String, T> commentCreator) throws AmetysRepositoryException
521    {
522        return getComments(parentComment, includeNotValidatedComments, includeValidatedComments, false, commentCreator);
523    }
524    
525    /**
526     * Get the sub comments of the given comment
527     * @param <T> type of the value to retrieve
528     * @param parentComment The parent comment
529     * @param includeNotValidatedComments True to include the comments that are not validated
530     * @param includeValidatedComments True to include the comments that are validated
531     * @param withSubComment true if we want to get all child comments
532     * @param commentCreator function to create new comment
533     * @return the list of comments
534     * @throws AmetysRepositoryException If an error occurred
535     */
536    public static <T extends AbstractComment> List<T> getComments(AbstractComment parentComment, boolean includeNotValidatedComments, boolean includeValidatedComments, boolean withSubComment, BiFunction<ModifiableDataHolder, String, T> commentCreator) throws AmetysRepositoryException
537    {
538        ModifiableComposite parentComposite = parentComment._commentComposite;
539        List<T> comments = new ArrayList<>();
540        
541        if (parentComposite.hasValue(SUB_COMMENTS_DATA_NAME, org.ametys.plugins.repository.data.type.ModelItemTypeConstants.COMPOSITE_TYPE_ID))
542        {
543            ModifiableComposite subCommentsComposite = parentComposite.getComposite(SUB_COMMENTS_DATA_NAME);
544            return _getComments(parentComment._rootDataHolder, subCommentsComposite, parentComment.getId() + ID_SEPARATOR, includeNotValidatedComments, includeValidatedComments, withSubComment, commentCreator);
545        }
546        
547        return comments;
548    }
549    
550    /**
551     * Get the comments in the given root data holder
552     * @param <T> type of the value to retrieve
553     * @param rootDataHolder The root data holder
554     * @param includeNotValidatedComments True to include the comments that are not validated
555     * @param includeValidatedComments True to include the comments that are validated
556     * @param commentCreator function to create new comment
557     * @return the list of comments
558     * @throws AmetysRepositoryException If an error occurred
559     */
560    public static <T extends AbstractComment> List<T> getComments(ModifiableDataHolder rootDataHolder, boolean includeNotValidatedComments, boolean includeValidatedComments, BiFunction<ModifiableDataHolder, String, T> commentCreator) throws AmetysRepositoryException
561    {
562        return getComments(rootDataHolder, includeNotValidatedComments, includeValidatedComments, false, commentCreator);
563    }
564    
565    /**
566     * Get the comments in the given root data holder
567     * @param <T> type of the value to retrieve
568     * @param rootDataHolder The root data holder
569     * @param includeNotValidatedComments True to include the comments that are not validated
570     * @param includeValidatedComments True to include the comments that are validated
571     * @param isRecursive true if we want to have sub comments
572     * @param commentCreator function to create new comment
573     * @return the list of comments
574     * @throws AmetysRepositoryException If an error occurred
575     */
576    public static <T extends AbstractComment> List<T> getComments(ModifiableDataHolder rootDataHolder, boolean includeNotValidatedComments, boolean includeValidatedComments, boolean isRecursive, BiFunction<ModifiableDataHolder, String, T> commentCreator) throws AmetysRepositoryException
577    {
578        return _getComments(rootDataHolder, rootDataHolder, StringUtils.EMPTY, includeNotValidatedComments, includeValidatedComments, isRecursive, commentCreator);
579    }
580    
581    private static <T extends AbstractComment> List<T> _getComments(ModifiableDataHolder rootDataHolder, ModifiableDataHolder dataHolder, String commentIdPrefix, boolean includeNotValidatedComments, boolean includeValidatedComments, boolean isRecursive, BiFunction<ModifiableDataHolder, String, T> commentCreator)
582    {
583        List<T> comments = new ArrayList<>();
584        
585        for (String name : dataHolder.getDataNames())
586        {
587            if (METADATA_COMMENTS_NOTVALIDATED.equals(name) || METADATA_COMMENTS_VALIDATED.equals(name))
588            {
589                continue;
590            }
591
592            String id = commentIdPrefix + name;
593            T c = commentCreator.apply(rootDataHolder, id);
594            if (includeNotValidatedComments && !c.isValidated() || includeValidatedComments && c.isValidated())
595            {
596                comments.add(c);
597                if (isRecursive)
598                {
599                    comments.addAll(getComments(c, includeNotValidatedComments, includeValidatedComments, isRecursive, commentCreator));
600                }
601            }
602        }
603        
604        return comments;
605    }
606
607    /**
608     * Get the referenced comment of the given comment
609     * @param <T> type of the value to retrieve
610     * @param parentComment The parent comment
611     * @param commentCreator function to create new comment
612     * @return the referenced comment
613     * @throws AmetysRepositoryException If an error occurred
614     */
615    public static <T extends AbstractComment> T getReferencedComment(AbstractComment parentComment, BiFunction<ModifiableDataHolder, String, T> commentCreator) throws AmetysRepositoryException
616    {
617        ModifiableComposite parentComposite = parentComment._commentComposite;
618        if (parentComposite.hasValue(REFERENCED_COMMENT_DATA_NAME, org.ametys.plugins.repository.data.type.ModelItemTypeConstants.COMPOSITE_TYPE_ID))
619        {
620            ModifiableComposite refComposite = parentComposite.getComposite(REFERENCED_COMMENT_DATA_NAME);
621            String refCommentId = refComposite.getDataNames().stream()
622                .findFirst()
623                .orElse(null);
624            return commentCreator.apply(refComposite, refCommentId);
625        }
626        
627        return null;
628    }
629    
630    @Override
631    public void addReaction(UserIdentity user, ReactionType reactionType)
632    {
633        ReactionableObjectHelper.addReaction(_commentComposite, user, reactionType);
634    }
635
636    @Override
637    public void removeReaction(UserIdentity user, ReactionType reactionType)
638    {
639        ReactionableObjectHelper.removeReaction(_commentComposite, user, reactionType);
640    }
641
642    @Override
643    public List<UserIdentity> getReactionUsers(ReactionType reactionType)
644    {
645        return ReactionableObjectHelper.getReactionUsers(_commentComposite, reactionType);
646    }
647    
648    /**
649     * Get parent of comment if exists
650     * @param <T> type of the value to retrieve
651     * @return the comment parent. null if the comment is not a sub comment
652     */
653    public abstract <T extends AbstractComment> T getCommentParent();
654
655    /**
656     * Check if the comment is a sub-comment from an other comment
657     * @return true if the comment is a sub-comment
658     */
659    public boolean isSubComment()
660    {
661        return this.getId().contains(ID_SEPARATOR);
662    }
663    
664    /**
665     * Check if the comment have any sub-comments
666     * @return true if the comment have any sub-comments
667     */
668    public boolean hasSubComments()
669    {
670        return !this.getSubComment(true, true).isEmpty();
671    }
672    
673    /**
674     * Is the comment the accepted answer
675     * @return true if the comment is the accepted answer
676     */
677    public boolean isAccepted()
678    {
679        return _commentComposite.getValueOfTypeOrDefault(METADATA_COMMENT_IS_ACCEPTED, ModelItemTypeConstants.BOOLEAN_TYPE_ID, false);
680    }
681    
682    /**
683     * Set the comment as accepted answer.
684     * @param isAccepted true to set the comment as accepted answer.
685     */
686    public void setAccepted(boolean isAccepted)
687    {
688        _commentComposite.setValue(METADATA_COMMENT_IS_ACCEPTED, isAccepted, ModelItemTypeConstants.BOOLEAN_TYPE_ID);
689    }
690    
691}