View Javadoc

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