View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.workflow.handler.comments;
23  
24  import org.opencastproject.event.comment.EventComment;
25  import org.opencastproject.event.comment.EventCommentException;
26  import org.opencastproject.event.comment.EventCommentService;
27  import org.opencastproject.job.api.JobContext;
28  import org.opencastproject.security.api.SecurityService;
29  import org.opencastproject.security.api.User;
30  import org.opencastproject.security.api.UserDirectoryService;
31  import org.opencastproject.serviceregistry.api.ServiceRegistry;
32  import org.opencastproject.util.NotFoundException;
33  import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
34  import org.opencastproject.workflow.api.WorkflowInstance;
35  import org.opencastproject.workflow.api.WorkflowOperationException;
36  import org.opencastproject.workflow.api.WorkflowOperationHandler;
37  import org.opencastproject.workflow.api.WorkflowOperationInstance;
38  import org.opencastproject.workflow.api.WorkflowOperationResult;
39  import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
40  
41  import org.apache.commons.lang3.StringUtils;
42  import org.osgi.service.component.annotations.Component;
43  import org.osgi.service.component.annotations.Reference;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  import java.util.Date;
48  import java.util.List;
49  import java.util.Optional;
50  
51  /**
52   * A workflow operation handler for creating, resolving and deleting comments
53   * automatically during the workflow process.
54   */
55  @Component(
56      immediate = true,
57      service = WorkflowOperationHandler.class,
58      property = {
59          "service.description=Comment Workflow Operation Handler",
60          "workflow.operation=comment"
61      }
62  )
63  public class CommentWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
64    protected static final String ACTION = "action";
65    protected static final String DESCRIPTION = "description";
66    protected static final String REASON = "reason";
67  
68    /** The logging facility */
69    private static final Logger logger = LoggerFactory.getLogger(CommentWorkflowOperationHandler.class);
70  
71    /* service references */
72    private EventCommentService eventCommentService;
73    private SecurityService securityService;
74    private UserDirectoryService userDirectoryService;
75  
76    public enum Operation {
77      create, resolve, delete
78    };
79  
80    /**
81     * {@inheritDoc}
82     */
83    @Override
84    public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context)
85            throws WorkflowOperationException {
86      logger.debug("Running comment workflow operation on workflow {}", workflowInstance.getId());
87      try {
88        return handleCommentOperation(workflowInstance);
89      } catch (Exception e) {
90        throw new WorkflowOperationException(e);
91      }
92    }
93  
94    /**
95     * Determine the type of operation to do on a comment and execute it.
96     *
97     * @param workflowInstance
98     *          The {@link WorkflowInstance} to be handled.
99     * @return The result of handling the {@link WorkflowInstance}
100    * @throws EventCommentException
101    *           Thrown if there is an issue creating, resolving or deleting a comment
102    * @throws NotFoundException
103    *           Thrown if the comment cannot be found to delete.
104    */
105   private WorkflowOperationResult handleCommentOperation(WorkflowInstance workflowInstance)
106           throws EventCommentException, NotFoundException {
107     Date date = new Date();
108     WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
109     Operation action;
110     String inputAction = StringUtils.trimToNull(operation.getConfiguration(ACTION));
111     if (inputAction == null) {
112       action = Operation.create;
113     } else {
114       action = Operation.valueOf(inputAction.toLowerCase());
115     }
116     String reason = StringUtils.trimToNull(operation.getConfiguration(REASON));
117     String description = StringUtils.trimToNull(operation.getConfiguration(DESCRIPTION));
118     switch (action) {
119       case create:
120         createComment(workflowInstance, reason, description);
121         break;
122       case resolve:
123         resolveComment(workflowInstance, reason, description);
124         break;
125       case delete:
126         deleteComment(workflowInstance, reason, description);
127         break;
128       default:
129         logger.warn(
130             "Unknown action '{}' for comment with description '{}' and reason '{}'. It should be "
131                 + "one of the following: {}",
132             inputAction, description, reason, StringUtils.join(Operation.values(), ","));
133     }
134     WorkflowOperationResult result = createResult(workflowInstance.getMediaPackage(), Action.CONTINUE,
135             (new Date().getTime()) - date.getTime());
136     return result;
137   }
138 
139   /**
140    * Create a new comment if one doesn't already exist with the reason and description.
141    *
142    * @param workflowInstance
143    *          The {@link WorkflowInstance} to handle.
144    * @param reason
145    *          The reason for the comment.
146    * @param description
147    *          The descriptive text of the comment.
148    * @throws EventCommentException
149    *           Thrown if unable to create the comment.
150    */
151   private void createComment(WorkflowInstance workflowInstance, String reason, String description)
152           throws EventCommentException {
153     String mpId = workflowInstance.getMediaPackage().getIdentifier().toString();
154     Optional<EventComment> optComment = findComment(mpId, reason, description);
155     if (optComment.isEmpty()) {
156       final User user = userDirectoryService.loadUser(workflowInstance.getCreatorName());
157       EventComment comment = EventComment.create(
158           Optional.empty(), mpId,
159           securityService.getOrganization().getId(), description, user, reason, false);
160       eventCommentService.updateComment(comment);
161     } else {
162       logger.debug("Not creating comment with '{}' text and '{}' reason as it already exists for this event.",
163               description, reason);
164     }
165   }
166 
167   /**
168    * Resolve a comment with matching reason and description.
169    *
170    * @param workflowInstance
171    *          The {@link WorkflowInstance} to handle.
172    * @param reason
173    *          The reason for the comment.
174    * @param description
175    *          The comment's descriptive text.
176    * @throws EventCommentException
177    *           Thrown if unable to update the comment.
178    */
179   private void resolveComment(WorkflowInstance workflowInstance, String reason, String description)
180           throws EventCommentException {
181     String mpId = workflowInstance.getMediaPackage().getIdentifier().toString();
182     Optional<EventComment> optComment = findComment(mpId, reason, description);
183     if (optComment.isPresent()) {
184       EventComment comment = EventComment.create(
185           optComment.get().getId(),
186           mpId,
187           securityService.getOrganization().getId(), optComment.get().getText(),
188           optComment.get().getAuthor(), optComment.get().getReason(), true, optComment.get().getCreationDate(),
189           optComment.get().getModificationDate(), optComment.get().getReplies());
190       eventCommentService.updateComment(comment);
191     } else {
192       logger.debug("Not resolving comment with '{}' text and/or '{}' reason as it doesn't exist.", description, reason);
193     }
194   }
195 
196   /**
197    * Delete a comment with matching reason and description.
198    *
199    * @param workflowInstance
200    *          The {@link WorkflowInstance} to handle.
201    * @param reason
202    *          The reason for the comment.
203    * @param description
204    *          The comment's descriptive text.
205    * @throws EventCommentException
206    *           Thrown if unable to delete the comment.
207    * @throws NotFoundException
208    *           Thrown if unable to find the comment.
209    */
210   private void deleteComment(WorkflowInstance workflowInstance, String reason, String description)
211           throws EventCommentException, NotFoundException {
212     String mpId = workflowInstance.getMediaPackage().getIdentifier().toString();
213     Optional<EventComment> optComment = findComment(mpId, reason, description);
214     if (optComment.isPresent()) {
215       try {
216         eventCommentService.deleteComment(optComment.get().getId().get());
217       } catch (NotFoundException e) {
218         logger.debug("Not deleting comment with '{}' text and '{}' reason and id '{}' as it doesn't exist.",
219                 description, reason, optComment.get().getId());
220       }
221     } else {
222       logger.debug("Not deleting comment with '{}' text and/or '{}' reason as it doesn't exist.", description, reason);
223     }
224   }
225 
226   /**
227    * Find a comment by its reason, description or both
228    *
229    * @param eventId
230    *          The event id to search the comments for.
231    * @param reason
232    *          The reason for the comment (optional)
233    * @param description
234    *          The description for the comment (optional)
235    * @return The comment if one is found matching the reason and description.
236    * @throws EventCommentException
237    *           Thrown if there was a problem finding the comment.
238    */
239   private Optional<EventComment> findComment(String eventId, String reason, String description)
240           throws EventCommentException {
241     Optional<EventComment> comment = Optional.empty();
242     List<EventComment> eventComments = eventCommentService.getComments(eventId);
243 
244     for (EventComment existingComment : eventComments) {
245       // Match on reason and description
246       if (reason != null && description != null
247           && reason.equals(existingComment.getReason()) && description.equals(existingComment.getText())) {
248         comment = Optional.of(existingComment);
249         break;
250       }
251       // Match on reason only
252       if (reason != null && description == null && reason.equals(existingComment.getReason())) {
253         comment = Optional.of(existingComment);
254         break;
255       }
256       // Match on description only
257       if (reason == null && description != null && description.equals(existingComment.getText())) {
258         comment = Optional.of(existingComment);
259         break;
260       }
261     }
262     return comment;
263   }
264 
265   /**
266    * Callback from the OSGi environment that will pass a reference to the workflow service upon component activation.
267    *
268    * @param eventCommentService
269    *          the workflow service
270    */
271   @Reference
272   public void setEventCommentService(EventCommentService eventCommentService) {
273     this.eventCommentService = eventCommentService;
274   }
275 
276   /** OSGi DI */
277   @Reference
278   void setSecurityService(SecurityService service) {
279     this.securityService = service;
280   }
281 
282   @Reference
283   public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
284     this.userDirectoryService = userDirectoryService;
285   }
286 
287   @Reference
288   @Override
289   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
290     super.setServiceRegistry(serviceRegistry);
291   }
292 
293 }