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