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