/** * 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.workflow.handler.comments; import org.opencastproject.event.comment.EventComment; import org.opencastproject.event.comment.EventCommentException; import org.opencastproject.event.comment.EventCommentService; import org.opencastproject.job.api.JobContext; import org.opencastproject.security.api.SecurityService; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.data.Option; import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler; import org.opencastproject.workflow.api.WorkflowInstance; import org.opencastproject.workflow.api.WorkflowOperationException; import org.opencastproject.workflow.api.WorkflowOperationInstance; import org.opencastproject.workflow.api.WorkflowOperationResult; import org.opencastproject.workflow.api.WorkflowOperationResult.Action; import com.entwinemedia.fn.data.Opt; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * A workflow operation handler for creating, resolving and deleting comments automatically during the workflow process. */ public class CommentWorkflowOperationHandler extends AbstractWorkflowOperationHandler { protected static final String ACTION = "action"; protected static final String DESCRIPTION = "description"; protected static final String REASON = "reason"; /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(CommentWorkflowOperationHandler.class); /* service references */ private EventCommentService eventCommentService; private SecurityService securityService; /** The configuration options for this handler */ private static final SortedMap<String, String> CONFIG_OPTIONS; public enum Operation { create, resolve, delete }; static { CONFIG_OPTIONS = new TreeMap<String, String>(); CONFIG_OPTIONS.put(REASON, "The optional comment reason's i18n id. You can find the id in etc/listproviders/event.comment.reasons.properties"); CONFIG_OPTIONS.put(DESCRIPTION, "The description text to add to the comment."); CONFIG_OPTIONS.put(ACTION, "Options are " + StringUtils.join(Operation.values(), ",") + ". Creates a new comment, marks a comment as resolved or deletes a comment that matches the same description and reason. By default creates."); } @Override public SortedMap<String, String> getConfigurationOptions() { return CONFIG_OPTIONS; } /** * {@inheritDoc} */ @Override public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { logger.debug("Running comment workflow operation on workflow {}", workflowInstance.getId()); try { return handleCommentOperation(workflowInstance); } catch (Exception e) { throw new WorkflowOperationException(e); } } /** * Determine the type of operation to do on a comment and execute it. * * @param workflowInstance * The {@link WorkflowInstance} to be handled. * @return The result of handling the {@link WorkflowInstance} * @throws EventCommentException * Thrown if there is an issue creating, resolving or deleting a comment * @throws NotFoundException * Thrown if the comment cannot be found to delete. */ private WorkflowOperationResult handleCommentOperation(WorkflowInstance workflowInstance) throws EventCommentException, NotFoundException { Date date = new Date(); WorkflowOperationInstance operation = workflowInstance.getCurrentOperation(); Operation action; String inputAction = StringUtils.trimToNull(operation.getConfiguration(ACTION)); if (inputAction == null) { action = Operation.create; } else { action = Operation.valueOf(inputAction.toLowerCase()); } String reason = StringUtils.trimToNull(operation.getConfiguration(REASON)); String description = StringUtils.trimToNull(operation.getConfiguration(DESCRIPTION)); switch (action) { case create: createComment(workflowInstance, reason, description); break; case resolve: resolveComment(workflowInstance, reason, description); break; case delete: deleteComment(workflowInstance, reason, description); break; default: logger.warn( "Unknown action '{}' for comment with description '{}' and reason '{}'. It should be one of the following: ", new Object[] { inputAction, description, reason, StringUtils.join(Operation.values(), ",") }); } WorkflowOperationResult result = createResult(workflowInstance.getMediaPackage(), Action.CONTINUE, (new Date().getTime()) - date.getTime()); return result; } /** * Create a new comment if one doesn't already exist with the reason and description. * * @param workflowInstance * The {@link WorkflowInstance} to handle. * @param reason * The reason for the comment. * @param description * The descriptive text of the comment. * @throws EventCommentException * Thrown if unable to create the comment. */ private void createComment(WorkflowInstance workflowInstance, String reason, String description) throws EventCommentException { Opt<EventComment> optComment = findComment(workflowInstance.getMediaPackage().getIdentifier().toString(), reason, description); if (optComment.isNone()) { EventComment comment = EventComment.create(Option.<Long> none(), workflowInstance.getMediaPackage().getIdentifier().toString(), securityService.getOrganization().getId(), description, workflowInstance.getCreator(), reason, false); eventCommentService.updateComment(comment); } else { logger.warn("Not creating comment with '{}' text and '{}' reason as it already exists for this event.", description, reason); } } /** * Resolve a comment with matching reason and description. * * @param workflowInstance * The {@link WorkflowInstance} to handle. * @param reason * The reason for the comment. * @param description * The comment's descriptive text. * @throws EventCommentException * Thrown if unable to update the comment. */ private void resolveComment(WorkflowInstance workflowInstance, String reason, String description) throws EventCommentException { Opt<EventComment> optComment = findComment(workflowInstance.getMediaPackage().getIdentifier().toString(), reason, description); if (optComment.isSome()) { EventComment comment = EventComment.create(optComment.get().getId(), workflowInstance.getMediaPackage().getIdentifier().toString(), securityService.getOrganization().getId(), optComment.get().getText(), optComment.get().getAuthor(), optComment.get().getReason(), true, optComment.get().getCreationDate(), optComment.get().getModificationDate(), optComment.get().getReplies()); eventCommentService.updateComment(comment); } else { logger.warn("Not resolving comment with '{}' text and '{}' reason as it doesn't exist.", description, reason); } } /** * Delete a comment with matching reason and description. * * @param workflowInstance * The {@link WorkflowInstance} to handle. * @param reason * The reason for the comment. * @param description * The comment's descriptive text. * @throws EventCommentException * Thrown if unable to delete the comment. * @throws NotFoundException * Thrown if unable to find the comment. */ private void deleteComment(WorkflowInstance workflowInstance, String reason, String description) throws EventCommentException, NotFoundException { Opt<EventComment> optComment = findComment(workflowInstance.getMediaPackage().getIdentifier().toString(), reason, description); if (optComment.isSome()) { try { eventCommentService.deleteComment(optComment.get().getId().get()); } catch (NotFoundException e) { logger.warn("Not deleting comment with '{}' text and '{}' reason and id '{}' as it doesn't exist.", new Object[] { description, reason, optComment.get().getId() }); } } else { logger.warn("Not deleting comment with '{}' text and '{}' reason as it doesn't exist.", description, reason); } } /** * Find a comment by its reason and description. * * @param eventId * The event id to search the comments for. * @param reason * The reason for the comment. * @param description * The description for the comment. * @return The comment if one is found matching the reason and description. * @throws EventCommentException * Thrown if there was a problem finding the comment. */ private Opt<EventComment> findComment(String eventId, String reason, String description) throws EventCommentException { Opt<EventComment> comment = Opt.none(); List<EventComment> eventComments = eventCommentService.getComments(eventId); for (EventComment existingComment : eventComments) { if (isSameComment(existingComment, reason, description)) { comment = Opt.some(existingComment); break; } } return comment; } /** * Determines if a comment has a given reason and description. * * @param comment * The comment to compare. * @param reason * The reason for the comment. * @param description * The description for the comment. * @return True if the two properties match. */ private boolean isSameComment(EventComment comment, String reason, String description) { return description == null ? comment.getText() == null : description.equals(comment.getText()) && (reason == null ? comment.getReason() == null : reason.equals(comment.getReason())); } /** * Callback from the OSGi environment that will pass a reference to the workflow service upon component activation. * * @param eventCommentService * the workflow service */ public void setEventCommentService(EventCommentService eventCommentService) { this.eventCommentService = eventCommentService; } /** OSGi DI */ void setSecurityService(SecurityService service) { this.securityService = service; } }