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