/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.core.commons.services.commentAndRating.manager; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.commentAndRating.CommentAndRatingLoggingAction; import org.olat.core.commons.services.commentAndRating.UserCommentsDelegate; import org.olat.core.commons.services.commentAndRating.model.UserComment; import org.olat.core.commons.services.commentAndRating.model.UserCommentImpl; import org.olat.core.commons.services.commentAndRating.model.UserCommentsCount; import org.olat.core.commons.services.commentAndRating.model.UserCommentsCountImpl; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.CoreLoggingResourceable; import org.olat.core.logging.activity.OlatResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.resource.OresHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Initial date: 13.03.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ @Service("userCommentsDAO") public class UserCommentsDAO { private static final OLog log = Tracing.createLoggerFor(UserCommentsDAO.class); @Autowired private DB dbInstance; private List<UserCommentsDelegate> delegates = new ArrayList<>(); public void addDelegate(UserCommentsDelegate delegate) { delegates.add(delegate); } public UserComment createComment(Identity creator, OLATResourceable ores, String resSubPath, String commentText) { UserComment comment = new UserCommentImpl(ores, resSubPath, creator, commentText); dbInstance.getCurrentEntityManager().persist(comment); updateDelegateRatings(ores, resSubPath); // do Logging ThreadLocalUserActivityLogger.log(CommentAndRatingLoggingAction.COMMENT_CREATED, getClass(), CoreLoggingResourceable.wrap(ores, OlatResourceableType.feedItem)); return comment; } public UserComment reloadComment(UserComment comment) { try { String q = "select comment from usercomment as comment where comment.key=:commentKey"; List<UserComment> comments = dbInstance.getCurrentEntityManager() .createQuery(q, UserComment.class) .setParameter("commentKey", comment.getKey()) .getResultList(); return comments.isEmpty() ? null : comments.get(0); } catch (Exception e) { // Huh, most likely the given object does not exist anymore on the // db, probably deleted by someone else log.warn("Tried to reload a user comment but got an exception. Probably deleted in the meantime", e); return null; } } public UserComment replyTo(UserComment originalComment, Identity creator, String replyCommentText) { // First reload parent from cache to prevent stale object or cache issues originalComment = reloadComment(originalComment); if (originalComment == null) { // Original comment has been deleted in the meantime. Don't create a reply return null; } OLATResourceable ores = OresHelper.createOLATResourceableInstance(originalComment.getResName(), originalComment.getResId()); UserCommentImpl reply = new UserCommentImpl(ores, originalComment.getResSubPath(), creator, replyCommentText); reply.setParent(originalComment); dbInstance.getCurrentEntityManager().persist(reply); updateDelegateRatings(ores, originalComment.getResSubPath()); return reply; } public List<UserComment> getComments(OLATResourceable ores, String resSubPath) { TypedQuery<UserComment> query; if (resSubPath == null) { // special query when sub path is null query = DBFactory.getInstance().getCurrentEntityManager() .createQuery("select comment from usercomment as comment where resName=:resname AND resId=:resId AND resSubPath is NULL", UserComment.class); } else { query = DBFactory.getInstance().getCurrentEntityManager() .createQuery("select comment from usercomment as comment where resName=:resname AND resId=:resId AND resSubPath=:resSubPath", UserComment.class) .setParameter("resSubPath", resSubPath); } return query.setParameter("resname", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .getResultList(); } public UserComment updateComment(UserComment comment, String newCommentText) { // First reload parent from cache to prevent stale object or cache issues comment = reloadComment(comment); if (comment == null) { // Original comment has been deleted in the meantime. Don't update it return null; } // Update DB entry comment.setComment(newCommentText); return dbInstance.getCurrentEntityManager().merge(comment); } public int deleteComment(UserComment comment, boolean deleteReplies) { int counter = 0; // First reload parent from cache to prevent stale object or cache issues comment = reloadComment(comment); if (comment == null) { // Original comment has been deleted in the meantime. Don't delete it again. return 0; } // First deal with all direct replies List<UserComment> replies = dbInstance.getCurrentEntityManager() .createQuery("select comment from usercomment as comment where parent=:parent", UserComment.class) .setParameter("parent", comment) .getResultList(); if (deleteReplies) { // Since we have a many-to-one we first have to recursively delete // the replies to prevent foreign key constraints for (UserComment reply : replies) { counter += deleteComment(reply, true); } } else { // To not delete the replies we have to set the parent to the parent // of the original comment for each reply for (UserComment reply : replies) { reply.setParent(comment.getParent()); dbInstance.getCurrentEntityManager().merge(reply); } } // Now delete this comment and finish dbInstance.getCurrentEntityManager().remove(comment); // do Logging OLATResourceable ores = OresHelper.createOLATResourceableInstance(comment.getResName(), comment.getResId()); updateDelegateRatings(ores, comment.getResSubPath()); ThreadLocalUserActivityLogger.log(CommentAndRatingLoggingAction.COMMENT_DELETED, getClass(), CoreLoggingResourceable.wrap(ores, OlatResourceableType.feedItem)); return counter+1; } public List<UserCommentsCount> countCommentsWithSubPath(OLATResourceable ores, String resSubPath) { if (resSubPath != null) { UserCommentsCount count = new UserCommentsCountImpl(ores, resSubPath, countComments(ores, resSubPath)); return Collections.singletonList(count); } StringBuilder sb = new StringBuilder(); sb.append("select comment.resSubPath, count(comment.key) from usercomment as comment ") .append(" where comment.resName=:resname AND comment.resId=:resId") .append(" group by comment.resSubPath"); List<Object[]> counts = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Object[].class) .setParameter("resname", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .getResultList(); Set<String> countMap = new HashSet<String>(); List<UserCommentsCount> countList = new ArrayList<UserCommentsCount>(); for(Object[] count:counts) { Object subPath = count[0] == null ? "" : count[0]; if(!countMap.contains(subPath)) { UserCommentsCount c = new UserCommentsCountImpl(ores, (String)count[0], (Long)count[1]); countList.add(c); } } return countList; } public long countComments(OLATResourceable ores, String resSubPath) { TypedQuery<Number> query; if (resSubPath == null) { // special query when sub path is null query = DBFactory.getInstance().getCurrentEntityManager() .createQuery("select count(*) from usercomment where resName=:resname AND resId=:resId AND resSubPath is NULL", Number.class); } else { query = DBFactory.getInstance().getCurrentEntityManager() .createQuery("select count(*) from usercomment where resName=:resname AND resId=:resId AND resSubPath=:resSubPath", Number.class) .setParameter("resSubPath", resSubPath); } return query.setParameter("resname", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .getSingleResult().longValue(); } /** * @see org.olat.core.commons.services.commentAndRating.UserCommentsManager#deleteAllComments() */ public int deleteAllComments(OLATResourceable ores, String resSubPath) { EntityManager em = dbInstance.getCurrentEntityManager(); // special query when sub path is null List<UserCommentImpl> comments; if (resSubPath == null) { StringBuilder sb = new StringBuilder(); sb.append("select comment from usercomment comment") .append(" where resName=:resName and resId=:resId and resSubPath is null") .append(" order by creationDate desc"); comments = em.createQuery(sb.toString(), UserCommentImpl.class) .setParameter("resName", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .getResultList(); } else { StringBuilder sb = new StringBuilder(); sb.append("select comment from usercomment comment") .append(" where resName=:resName and resId=:resId and resSubPath=:resSubPath") .append(" order by creationDate desc"); comments = em.createQuery(sb.toString(), UserCommentImpl.class) .setParameter("resName", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .setParameter("resSubPath", resSubPath) .getResultList(); } if(comments != null && !comments.isEmpty()) { for(UserCommentImpl comment:comments) { em.remove(comment); } } updateDelegateRatings(ores, resSubPath); return comments == null ? 0 : comments.size(); } /** * Don't limit to subpath. Ignore if null or not, just delete on the resource * @see org.olat.core.commons.services.commentAndRating.UserCommentsManager#deleteAllCommentsIgnoringSubPath() */ public int deleteAllCommentsIgnoringSubPath(OLATResourceable ores) { EntityManager em = dbInstance.getCurrentEntityManager(); StringBuilder sb = new StringBuilder(); sb.append("select comment from usercomment comment") .append(" where resName=:resName and resId=:resId") .append(" order by creationDate desc"); List<UserCommentImpl> comments = em.createQuery(sb.toString(), UserCommentImpl.class) .setParameter("resName", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()) .getResultList(); for(UserCommentImpl comment:comments) { em.remove(comment); } updateDelegateRatings(ores, null); return comments.size(); } private void updateDelegateRatings(OLATResourceable ores, String resSubPath) { if(delegates == null || delegates.isEmpty()) return; for(UserCommentsDelegate delegate:delegates) { if(delegate.accept(ores, resSubPath)) { StringBuilder sb = new StringBuilder(); sb.append("select count(comment.key) from usercomment as comment") .append(" where comment.resName=:resname and comment.resId=:resId"); if(resSubPath == null) { sb.append(" and comment.resSubPath is null"); } else { sb.append(" and comment.resSubPath=:resSubPath"); } TypedQuery<Number> query = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Number.class) .setParameter("resname", ores.getResourceableTypeName()) .setParameter("resId", ores.getResourceableId()); if(resSubPath!= null) { query.setParameter("resSubPath", resSubPath); } Number count = query.getSingleResult(); if(count == null) { delegate.update(ores, resSubPath, 1); } else { delegate.update(ores, resSubPath, count.intValue()); } } } } }