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}