/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.event.comment.persistence;
import static org.opencastproject.util.persistencefn.Queries.persistOrUpdate;
import org.opencastproject.event.comment.EventComment;
import org.opencastproject.index.IndexProducer;
import org.opencastproject.message.broker.api.MessageReceiver;
import org.opencastproject.message.broker.api.MessageSender;
import org.opencastproject.message.broker.api.comments.CommentItem;
import org.opencastproject.message.broker.api.index.AbstractIndexProducer;
import org.opencastproject.message.broker.api.index.IndexRecreateObject;
import org.opencastproject.message.broker.api.index.IndexRecreateObject.Service;
import org.opencastproject.security.api.DefaultOrganization;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.OrganizationDirectoryService;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UserDirectoryService;
import org.opencastproject.security.util.SecurityUtil;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.data.Effect0;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Monadics;
import org.opencastproject.util.persistencefn.PersistenceEnv;
import org.opencastproject.util.persistencefn.PersistenceEnvs;
import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.Stream;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.osgi.framework.ServiceException;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;
/**
* Implements permanent storage for event comments.
*/
public class EventCommentDatabaseServiceImpl extends AbstractIndexProducer implements EventCommentDatabaseService {
/** Logging utilities */
private static final Logger logger = LoggerFactory.getLogger(EventCommentDatabaseServiceImpl.class);
public static final String PERSISTENCE_UNIT = "org.opencastproject.event.comment";
/** Factory used to create {@link EntityManager}s for transactions */
private EntityManagerFactory emf;
/** Persistence environment */
private PersistenceEnv env;
/** The security service used to retrieve organizations. */
private OrganizationDirectoryService organizationDirectoryService;
/** The security service used to run the security context with. */
private SecurityService securityService;
/** The user directory service */
private UserDirectoryService userDirectoryService;
/** The message broker sender service */
private MessageSender messageSender;
/** The message broker receiver service */
private MessageReceiver messageReceiver;
/** The component context this bundle is running in. */
private ComponentContext cc;
/** OSGi component activation callback */
public void activate(ComponentContext cc) {
logger.info("Activating persistence manager for event comments");
this.cc = cc;
super.activate();
}
/** OSGi DI */
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
this.env = PersistenceEnvs.mk(emf);
}
/**
* OSGi callback to set the security context to run with.
*
* @param securityService
* The security service
*/
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* OSGi callback to set the user directory service.
*
* @param userDirectoryService
* the user directory service
*/
public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
this.userDirectoryService = userDirectoryService;
}
/**
* OSGi callback to set the organization directory service.
*
* @param organizationDirectoryService
* the organization directory service
*/
public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) {
this.organizationDirectoryService = organizationDirectoryService;
}
/**
* OSGi callback to set the message sender.
*
* @param messageSender
* the message sender
*/
public void setMessageSender(MessageSender messageSender) {
this.messageSender = messageSender;
}
/**
* OSGi callback to set the message receiver.
*
* @param messageReceiver
* the message receiver
*/
public void setMessageReceiver(MessageReceiver messageReceiver) {
this.messageReceiver = messageReceiver;
}
@Override
@SuppressWarnings("unchecked")
public List<String> getReasons() throws EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
try {
Query q = em.createNamedQuery("EventComment.findReasons");
q.setParameter("org", securityService.getOrganization().getId());
return q.getResultList();
} catch (Exception e) {
logger.error("Could not get reasons: {}", ExceptionUtils.getStackTrace(e));
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
}
@Override
public EventComment getComment(long commentId) throws NotFoundException, EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
try {
EventCommentDto event = getEventComment(commentId, em);
if (event == null)
throw new NotFoundException("Event comment with ID " + commentId + " does not exist");
return event.toComment(userDirectoryService);
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Could not get event comment {}: {}", commentId, ExceptionUtils.getStackTrace(e));
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
}
@Override
public void deleteComment(long commentId) throws NotFoundException, EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
EventCommentDto event = getEventComment(commentId, em);
if (event == null)
throw new NotFoundException("Event comment with ID " + commentId + " does not exist");
em.remove(event);
tx.commit();
sendMessageUpdate(event.getEventId());
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Could not delete event comment: {}", ExceptionUtils.getStackTrace(e));
if (tx.isActive())
tx.rollback();
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
}
@Override
public void deleteComments(String eventId) throws NotFoundException, EventCommentDatabaseException {
// Similar to deleteComment but we want to avoid sending a message for each deletion
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
List<EventComment> comments = getComments(eventId);
for (EventComment comment : comments) {
long commentId = comment.getId().get().intValue();
EventCommentDto event = getEventComment(commentId, em);
if (event == null)
throw new NotFoundException("Event comment with ID " + commentId + " does not exist");
em.remove(event);
}
tx.commit();
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Could not delete event comments: {}", ExceptionUtils.getStackTrace(e));
if (tx.isActive())
tx.rollback();
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
sendMessageUpdate(eventId);
}
@Override
public EventComment updateComment(EventComment comment) throws EventCommentDatabaseException {
final EventCommentDto commentDto = EventCommentDto.from(comment);
final EventComment updatedComment = env.tx(persistOrUpdate(commentDto)).toComment(userDirectoryService);
sendMessageUpdate(updatedComment.getEventId());
return updatedComment;
}
/**
* Gets an event comment, using the current organizational context.
*
* @param commentId
* the comment identifier
* @param em
* an open entity manager
*
* @return the event comment entity, or null if not found
*/
private EventCommentDto getEventComment(long commentId, EntityManager em) {
Query q = em.createNamedQuery("EventComment.findByCommentId");
q.setParameter("commentId", commentId);
try {
return (EventCommentDto) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
@Override
@SuppressWarnings("unchecked")
public List<EventComment> getComments(String eventId) throws EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
try {
Query q = em.createNamedQuery("EventComment.findByEvent");
q.setParameter("eventId", eventId);
q.setParameter("org", securityService.getOrganization().getId());
List<EventComment> comments = Monadics.mlist(q.getResultList())
.map(new Function<EventCommentDto, EventComment>() {
@Override
public EventComment apply(EventCommentDto a) {
return a.toComment(userDirectoryService);
}
}).sort(new Comparator<EventComment>() {
@Override
public int compare(EventComment c1, EventComment c2) {
boolean v1 = c1.isResolvedStatus();
boolean v2 = c2.isResolvedStatus();
return (v1 ^ v2) ? ((v1 ^ false) ? 1 : -1) : 0;
}
}).value();
return new ArrayList<>(comments);
} catch (Exception e) {
logger.error("Could not retreive comments for event {}: {}", eventId, ExceptionUtils.getStackTrace(e));
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
}
@SuppressWarnings("unchecked")
public Iterator<EventCommentDto> getComments() throws EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
try {
Query q = em.createNamedQuery("EventComment.findAll");
return new ArrayList<EventCommentDto>(q.getResultList()).iterator();
} catch (Exception e) {
logger.error("Could not retreive event comments: {}", ExceptionUtils.getStackTrace(e));
throw new EventCommentDatabaseException(e);
} finally {
if (em != null)
em.close();
}
}
public int countComments() throws EventCommentDatabaseException {
EntityManager em = emf.createEntityManager();
Query query = em.createNamedQuery("EventComment.countAll");
try {
Number total = (Number) query.getSingleResult();
return total.intValue();
} catch (Exception e) {
logger.error("Could not find the number of comments.", e);
throw new EventCommentDatabaseException(e);
} finally {
em.close();
}
}
private void sendMessageUpdate(String eventId) throws EventCommentDatabaseException {
List<EventComment> comments = getComments(eventId);
boolean openComments = !Stream.$(comments).filter(filterOpenComments).toList().isEmpty();
boolean needsCutting = !Stream.$(comments).filter(filterNeedsCuttingComment).toList().isEmpty();
CommentItem update = CommentItem.update(eventId, !comments.isEmpty(), openComments, needsCutting);
messageSender.sendObjectMessage(CommentItem.COMMENT_QUEUE, MessageSender.DestinationType.Queue, update);
}
private static final Fn<EventComment, Boolean> filterOpenComments = new Fn<EventComment, Boolean>() {
@Override
public Boolean apply(EventComment comment) {
return !comment.isResolvedStatus();
}
};
private static final Fn<EventComment, Boolean> filterNeedsCuttingComment = new Fn<EventComment, Boolean>() {
@Override
public Boolean apply(EventComment comment) {
return EventComment.REASON_NEEDS_CUTTING.equals(comment.getReason()) && !comment.isResolvedStatus();
}
};
@Override
public void repopulate(final String indexName) throws Exception {
final String destinationId = CommentItem.COMMENT_QUEUE_PREFIX + WordUtils.capitalize(indexName);
try {
final int total = countComments();
final int[] current = new int[1];
current[0] = 1;
logger.info("Re-populating index '{}' with comments for events. There are {} events with comments to add",
indexName, total);
for (Iterator<EventCommentDto> i = getComments(); i.hasNext();) {
final EventCommentDto comment = i.next();
Organization organization = organizationDirectoryService.getOrganization(comment.getOrganization());
SecurityUtil.runAs(securityService, organization, SecurityUtil.createSystemUser(cc, organization),
new Effect0() {
@Override
protected void run() {
messageSender.sendObjectMessage(destinationId, MessageSender.DestinationType.Queue,
CommentItem.update(comment.getEventId(), true, !comment.isResolvedStatus(),
(!comment.isResolvedStatus()
&& EventComment.REASON_NEEDS_CUTTING.equals(comment.getReason()))));
messageSender.sendObjectMessage(IndexProducer.RESPONSE_QUEUE, MessageSender.DestinationType.Queue,
IndexRecreateObject.update(indexName, IndexRecreateObject.Service.Comments, total,
current[0]));
current[0] += 1;
}
});
}
} catch (Exception e) {
logger.warn("Unable to index event comments: {}", e);
throw new ServiceException(e.getMessage());
}
Organization organization = new DefaultOrganization();
SecurityUtil.runAs(securityService, organization, SecurityUtil.createSystemUser(cc, organization), new Effect0() {
@Override
protected void run() {
messageSender.sendObjectMessage(destinationId, MessageSender.DestinationType.Queue,
IndexRecreateObject.end(indexName, IndexRecreateObject.Service.Comments));
}
});
}
@Override
public MessageReceiver getMessageReceiver() {
return messageReceiver;
}
@Override
public Service getService() {
return Service.Comments;
}
@Override
public String getClassName() {
return EventCommentDatabaseServiceImpl.class.getName();
}
@Override
public MessageSender getMessageSender() {
return messageSender;
}
@Override
public SecurityService getSecurityService() {
return securityService;
}
@Override
public String getSystemUserName() {
return SecurityUtil.getSystemUserName(cc);
}
}