View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.workflow.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.time.StopWatch;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.exception.RiceRuntimeException;
22  import org.kuali.rice.core.api.util.RiceKeyConstants;
23  import org.kuali.rice.kew.api.KewApiServiceLocator;
24  import org.kuali.rice.kew.api.WorkflowDocument;
25  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
26  import org.kuali.rice.kew.api.action.ActionRequestType;
27  import org.kuali.rice.kew.api.action.ActionType;
28  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
29  import org.kuali.rice.kew.api.exception.WorkflowException;
30  import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
31  import org.kuali.rice.kew.api.KewApiConstants;
32  import org.kuali.rice.kim.api.group.Group;
33  import org.kuali.rice.kim.api.identity.Person;
34  import org.kuali.rice.kim.api.identity.principal.Principal;
35  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
36  import org.kuali.rice.krad.bo.AdHocRoutePerson;
37  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
38  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
39  import org.kuali.rice.krad.exception.UnknownDocumentIdException;
40  import org.kuali.rice.krad.service.KRADServiceLocator;
41  import org.kuali.rice.krad.util.GlobalVariables;
42  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
43  import org.springframework.transaction.annotation.Transactional;
44  
45  import java.text.MessageFormat;
46  import java.util.ArrayList;
47  import java.util.HashSet;
48  import java.util.List;
49  import java.util.Set;
50  
51  
52  /**
53   * Implementation of the WorkflowDocumentService, which makes use of Workflow
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  @Transactional
58  public class WorkflowDocumentServiceImpl implements WorkflowDocumentService {
59      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(WorkflowDocumentServiceImpl.class);
60  
61      @Override
62      public boolean workflowDocumentExists(String documentId) {
63          boolean exists = false;
64  
65          if (StringUtils.isBlank(documentId)) {
66              throw new IllegalArgumentException("invalid (blank) documentId");
67          }
68  
69          exists = KewApiServiceLocator.getWorkflowDocumentService().doesDocumentExist(documentId);
70  
71          return exists;
72      }
73  
74      @Override
75      public WorkflowDocument createWorkflowDocument(String documentTypeName, Person person) {
76          String watchName = "createWorkflowDocument";
77          StopWatch watch = new StopWatch();
78          watch.start();
79          if (LOG.isDebugEnabled()) {
80              LOG.debug(watchName + ": started");
81          }
82          if (StringUtils.isBlank(documentTypeName)) {
83              throw new IllegalArgumentException("invalid (blank) documentTypeName");
84          }
85          if (person == null) {
86              throw new IllegalArgumentException("invalid (null) person");
87          }
88  
89          if (StringUtils.isBlank(person.getPrincipalName())) {
90              throw new IllegalArgumentException("invalid (empty) PrincipalName");
91          }
92  
93          if (LOG.isDebugEnabled()) {
94              LOG.debug("creating workflowDoc(" + documentTypeName + "," + person.getPrincipalName() + ")");
95          }
96  
97          WorkflowDocument document = WorkflowDocumentFactory.createDocument(person.getPrincipalId(), documentTypeName);
98          watch.stop();
99          if (LOG.isDebugEnabled()) {
100             LOG.debug(watchName + ": " + watch.toString());
101         }
102 
103         return document;
104     }
105 
106     @Override
107     public WorkflowDocument loadWorkflowDocument(String documentId, Person user) {
108         if (documentId == null) {
109             throw new IllegalArgumentException("invalid (null) documentHeaderId");
110         }
111         if (user == null) {
112             throw new IllegalArgumentException("invalid (null) workflowUser");
113         }
114         else if (StringUtils.isEmpty(user.getPrincipalName())) {
115             throw new IllegalArgumentException("invalid (empty) workflowUser");
116         }
117 
118         if (LOG.isDebugEnabled()) {
119             LOG.debug("retrieving document(" + documentId + "," + user.getPrincipalName() + ")");
120         }
121 
122         try {
123             return WorkflowDocumentFactory.loadDocument(user.getPrincipalId(), documentId);
124         } catch (IllegalArgumentException e) {
125             // TODO do we really need to do this or just let the IllegalArgument propogate?
126             throw new UnknownDocumentIdException("unable to locate document with documentHeaderId '" + documentId + "'");
127         }
128     }
129 
130     @Override
131     public void acknowledge(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
132         if (LOG.isDebugEnabled()) {
133             LOG.debug("acknowleding document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
134         }
135 
136         handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ }));
137         workflowDocument.acknowledge(annotation);
138     }
139 
140     @Override
141     public void approve(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
142         if (LOG.isDebugEnabled()) {
143             LOG.debug("approving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
144         }
145 
146         handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ }));
147         workflowDocument.approve(annotation);
148     }
149 
150 
151     @Override
152     public void superUserApprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
153         if ( LOG.isInfoEnabled() ) {
154             LOG.info("super user approve document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
155         }
156         workflowDocument.superUserBlanketApprove(annotation);
157     }
158 
159     @Override
160     public void superUserCancel(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
161         LOG.info("super user cancel document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
162         workflowDocument.superUserCancel(annotation);
163     }
164 
165     @Override
166     public void superUserDisapprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
167         if ( LOG.isInfoEnabled() ) {
168             LOG.info("super user disapprove document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
169         }
170         workflowDocument.superUserDisapprove(annotation);
171     }
172 
173     @Override
174     public void blanketApprove(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
175         if (LOG.isDebugEnabled()) {
176             LOG.debug("blanket approving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
177         }
178 
179         handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ }));
180         workflowDocument.blanketApprove(annotation);
181     }
182 
183     @Override
184     public void cancel(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
185         if (LOG.isDebugEnabled()) {
186             LOG.debug("canceling document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
187         }
188 
189         workflowDocument.cancel(annotation);
190     }
191 
192     @Override
193     public void recall(WorkflowDocument workflowDocument, String annotation, boolean cancel) throws WorkflowException {
194         if (LOG.isDebugEnabled()) {
195             LOG.debug("recalling document(" + workflowDocument.getDocumentId() + ",'" + annotation + "', '" + cancel + "')");
196         }
197 
198         workflowDocument.recall(annotation, cancel);
199     }
200 
201     @Override
202     public void clearFyi(WorkflowDocument workflowDocument, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
203         if (LOG.isDebugEnabled()) {
204             LOG.debug("clearing FYI for document(" + workflowDocument.getDocumentId() + ")");
205         }
206 
207         handleAdHocRouteRequests(workflowDocument, "", filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_FYI_REQ }));
208         workflowDocument.fyi();
209     }
210 
211     @Override
212     public void sendWorkflowNotification(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
213         sendWorkflowNotification(workflowDocument, annotation, adHocRecipients, null);
214     }
215 
216     @Override
217     public void sendWorkflowNotification(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients, String notificationLabel) throws WorkflowException {
218         if (LOG.isDebugEnabled()) {
219             LOG.debug("sending FYI for document(" + workflowDocument.getDocumentId() + ")");
220         }
221 
222         handleAdHocRouteRequests(workflowDocument, annotation, adHocRecipients, notificationLabel);
223     }
224 
225     @Override
226     public void disapprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
227         if (LOG.isDebugEnabled()) {
228             LOG.debug("disapproving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
229         }
230 
231         workflowDocument.disapprove(annotation);
232     }
233 
234     @Override
235     public void route(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
236         if (LOG.isDebugEnabled()) {
237             LOG.debug("routing document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
238         }
239 
240         handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ }));
241         workflowDocument.route(annotation);
242     }
243 
244     @Override
245     public void save(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
246         if (workflowDocument.isValidAction(ActionType.SAVE)) {
247             if (LOG.isDebugEnabled()) {
248                 LOG.debug("saving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
249             }
250 
251             workflowDocument.saveDocument(annotation);
252         }
253         else {
254             this.saveRoutingData(workflowDocument);
255         }
256     }
257 
258     @Override
259     public void saveRoutingData(WorkflowDocument workflowDocument) throws WorkflowException {
260         if (LOG.isDebugEnabled()) {
261             LOG.debug("saving document(" + workflowDocument.getDocumentId() + ")");
262         }
263 
264         workflowDocument.saveDocumentData();
265     }
266 
267     @Override
268     public String getCurrentRouteLevelName(WorkflowDocument workflowDocument) throws WorkflowException {
269         if (LOG.isDebugEnabled()) {
270             LOG.debug("getting current route level name for document(" + workflowDocument.getDocumentId());
271         }
272 //        return KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId()).getCurrentRouteLevelName();
273         WorkflowDocument freshCopyWorkflowDoc = loadWorkflowDocument(workflowDocument.getDocumentId(), GlobalVariables.getUserSession().getPerson());
274         return getCurrentRouteNodeNames(freshCopyWorkflowDoc);
275     }
276 
277 
278 
279     @Override
280     public String getCurrentRouteNodeNames(WorkflowDocument workflowDocument) {
281         Set<String> nodeNames = workflowDocument.getNodeNames();
282         if (nodeNames.isEmpty()) {
283             return "";
284         }
285         StringBuilder builder = new StringBuilder();
286         for (String nodeName : nodeNames) {
287             builder.append(nodeName).append(", ");
288         }
289         builder.setLength(builder.length() - 2);
290         return builder.toString();
291     }
292 
293     private void handleAdHocRouteRequests(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
294         handleAdHocRouteRequests(workflowDocument, annotation, adHocRecipients, null);
295     }
296 
297     /**
298      * Convenience method for generating ad hoc requests for a given document
299      *
300      * @param annotation
301      * @param adHocRecipients
302      * @throws InvalidActionTakenException
303      */
304     private void handleAdHocRouteRequests(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients, String notificationLabel) throws WorkflowException {
305 
306         if (adHocRecipients != null && adHocRecipients.size() > 0) {
307             String currentNode = null;
308             Set<String> currentNodes = workflowDocument.getNodeNames();
309             if (currentNodes.isEmpty()) {
310                 List<RouteNodeInstance> nodes = KewApiServiceLocator.getWorkflowDocumentService().getTerminalRouteNodeInstances(
311                         workflowDocument.getDocumentId());
312                 currentNodes = new HashSet<String>();
313                 for (RouteNodeInstance node : nodes) {
314                     currentNodes.add(node.getName());
315                 }
316             }
317             // for now just pick a node and go with it...
318             currentNode = currentNodes.iterator().next();
319 
320             List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
321             List<AdHocRouteWorkgroup> adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>();
322 
323             for (AdHocRouteRecipient recipient : adHocRecipients) {
324                 if (StringUtils.isNotEmpty(recipient.getId())) {
325                     String newAnnotation = annotation;
326                     if ( StringUtils.isBlank( annotation ) ) {
327                         try {
328                             String message = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
329                                     RiceKeyConstants.MESSAGE_ADHOC_ANNOTATION);
330                             newAnnotation = MessageFormat.format(message, GlobalVariables.getUserSession().getPrincipalName() );
331                         } catch ( Exception ex ) {
332                             LOG.warn("Unable to set annotation", ex );
333                         }
334                     }
335                     if (AdHocRouteRecipient.PERSON_TYPE.equals(recipient.getType())) {
336                         // TODO make the 1 a constant
337                         Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(recipient.getName());
338                         if (principal == null) {
339                             if(((AdHocRoutePerson)recipient)!=null && ((AdHocRoutePerson)recipient).getPerson()!=null){
340                                 principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(((AdHocRoutePerson)recipient).getPerson().getPrincipalName());
341                             }
342                             if(principal == null){
343                             throw new RiceRuntimeException("Could not locate principal with name '" + recipient.getId() + "'");
344                             }
345                         }
346                         workflowDocument.adHocToPrincipal(ActionRequestType.fromCode(recipient.getActionRequested()), currentNode, newAnnotation, principal.getPrincipalId(), "", true, notificationLabel);
347                         AdHocRoutePerson personRecipient  = (AdHocRoutePerson)recipient;
348                         adHocRoutePersons.add(personRecipient);
349                     }
350                     else {
351                         Group group = KimApiServiceLocator.getGroupService().getGroup(recipient.getId());
352                         if (group == null) {
353                             throw new RiceRuntimeException("Could not locate group with id '" + recipient.getId() + "'");
354                         }
355                         workflowDocument.adHocToGroup(ActionRequestType.fromCode(recipient.getActionRequested()), currentNode, newAnnotation, group.getId() , "", true, notificationLabel);
356                         AdHocRouteWorkgroup groupRecipient  = (AdHocRouteWorkgroup)recipient;
357                         adHocRouteWorkgroups.add(groupRecipient);
358                     }
359                 }
360             }
361             KRADServiceLocator.getBusinessObjectService().delete(adHocRoutePersons);
362             KRADServiceLocator.getBusinessObjectService().delete(adHocRouteWorkgroups);
363         }
364     }
365 
366     /**
367      * Convenience method to filter out any ad hoc recipients that should not be allowed given the action requested of the user that
368      * is taking action on the document
369      *
370      * @param adHocRecipients
371      */
372     private List<AdHocRouteRecipient> filterAdHocRecipients(List<AdHocRouteRecipient> adHocRecipients, String[] validTypes) {
373         // now filter out any but ack or fyi from the ad hoc list
374         List<AdHocRouteRecipient> realAdHocRecipients = new ArrayList<AdHocRouteRecipient>();
375         if (adHocRecipients != null) {
376             for (AdHocRouteRecipient proposedRecipient : adHocRecipients) {
377                 if (StringUtils.isNotBlank(proposedRecipient.getActionRequested())) {
378                     for (int i = 0; i < validTypes.length; i++) {
379                         if (validTypes[i].equals(proposedRecipient.getActionRequested())) {
380                             realAdHocRecipients.add(proposedRecipient);
381                         }
382                     }
383                 }
384             }
385         }
386         return realAdHocRecipients;
387     }
388 
389     /**
390      * Completes workflow document
391      *
392      * @see WorkflowDocumentService#complete(org.kuali.rice.kew.api.WorkflowDocument, String, java.util.List)
393      */
394     public void complete(WorkflowDocument workflowDocument, String annotation, List adHocRecipients) throws WorkflowException {
395         if (LOG.isDebugEnabled()) {
396             LOG.debug("routing flexDoc(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
397         }
398         handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_COMPLETE_REQ,KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ }));
399         workflowDocument.complete(annotation);
400     }
401 }