View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.service.impl;
18  
19  import java.math.BigDecimal;
20  import java.sql.Timestamp;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import javax.jws.WebService;
34  
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.log4j.Logger;
37  import org.kuali.rice.core.exception.RiceRuntimeException;
38  import org.kuali.rice.core.resourceloader.GlobalResourceLoader;
39  import org.kuali.rice.kew.actionitem.ActionItem;
40  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
41  import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
42  import org.kuali.rice.kew.actionrequest.Recipient;
43  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
44  import org.kuali.rice.kew.definition.AttributeDefinition;
45  import org.kuali.rice.kew.docsearch.DocSearchCriteriaDTO;
46  import org.kuali.rice.kew.docsearch.DocumentSearchResultComponents;
47  import org.kuali.rice.kew.doctype.bo.DocumentType;
48  import org.kuali.rice.kew.documentlink.DocumentLink;
49  import org.kuali.rice.kew.dto.ActionItemDTO;
50  import org.kuali.rice.kew.dto.ActionRequestDTO;
51  import org.kuali.rice.kew.dto.ActionTakenDTO;
52  import org.kuali.rice.kew.dto.DTOConverter;
53  import org.kuali.rice.kew.dto.DocumentContentDTO;
54  import org.kuali.rice.kew.dto.DocumentDetailDTO;
55  import org.kuali.rice.kew.dto.DocumentLinkDTO;
56  import org.kuali.rice.kew.dto.DocumentSearchCriteriaDTO;
57  import org.kuali.rice.kew.dto.DocumentSearchResultDTO;
58  import org.kuali.rice.kew.dto.DocumentStatusTransitionDTO;
59  import org.kuali.rice.kew.dto.DocumentTypeDTO;
60  import org.kuali.rice.kew.dto.PropertyDefinitionDTO;
61  import org.kuali.rice.kew.dto.ReportCriteriaDTO;
62  import org.kuali.rice.kew.dto.RouteHeaderDTO;
63  import org.kuali.rice.kew.dto.RouteNodeInstanceDTO;
64  import org.kuali.rice.kew.dto.RuleDTO;
65  import org.kuali.rice.kew.dto.RuleExtensionDTO;
66  import org.kuali.rice.kew.dto.RuleReportCriteriaDTO;
67  import org.kuali.rice.kew.dto.WorkflowAttributeDefinitionDTO;
68  import org.kuali.rice.kew.dto.WorkflowAttributeValidationErrorDTO;
69  import org.kuali.rice.kew.engine.ActivationContext;
70  import org.kuali.rice.kew.engine.CompatUtils;
71  import org.kuali.rice.kew.engine.RouteContext;
72  import org.kuali.rice.kew.engine.node.KeyValuePair;
73  import org.kuali.rice.kew.engine.node.RouteNode;
74  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
75  import org.kuali.rice.kew.engine.simulation.SimulationCriteria;
76  import org.kuali.rice.kew.engine.simulation.SimulationEngine;
77  import org.kuali.rice.kew.engine.simulation.SimulationResults;
78  import org.kuali.rice.kew.exception.WorkflowException;
79  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
80  import org.kuali.rice.kew.routeheader.DocumentStatusTransition;
81  import org.kuali.rice.kew.rule.FlexRM;
82  import org.kuali.rice.kew.rule.RuleBaseValues;
83  import org.kuali.rice.kew.rule.WorkflowAttribute;
84  import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
85  import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
86  import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
87  import org.kuali.rice.kew.service.KEWServiceLocator;
88  import org.kuali.rice.kew.service.WorkflowUtility;
89  import org.kuali.rice.kew.util.KEWConstants;
90  import org.kuali.rice.kew.util.KEWWebServiceConstants;
91  import org.kuali.rice.kew.util.Utilities;
92  import org.kuali.rice.kew.web.session.UserSession;
93  import org.kuali.rice.kim.bo.entity.KimPrincipal;
94  import org.kuali.rice.kim.bo.types.dto.AttributeSet;
95  import org.kuali.rice.kim.service.KIMServiceLocator;
96  import org.kuali.rice.kns.util.KNSConstants;
97  import org.kuali.rice.kns.util.ObjectUtils;
98  
99  @SuppressWarnings({"unchecked"})
100 @WebService(endpointInterface = KEWWebServiceConstants.WorkflowUtility.INTERFACE_CLASS,
101         serviceName = KEWWebServiceConstants.WorkflowUtility.WEB_SERVICE_NAME,
102         portName = KEWWebServiceConstants.WorkflowUtility.WEB_SERVICE_PORT,
103         targetNamespace = KEWWebServiceConstants.MODULE_TARGET_NAMESPACE)
104 public class WorkflowUtilityWebServiceImpl implements WorkflowUtility {
105 
106     private static final Logger LOG = Logger.getLogger(WorkflowUtilityWebServiceImpl.class);
107 
108     public RouteHeaderDTO getRouteHeaderWithPrincipal(String principalId, Long documentId) throws WorkflowException {
109         if (documentId == null) {
110             LOG.error("null routeHeaderId passed in.  Throwing RuntimeExcpetion");
111             throw new RuntimeException("Null documentId passed in.");
112         }
113         if (principalId == null) {
114             LOG.error("null principalId passed in.");
115             throw new RuntimeException("null principalId passed in");
116         }
117         if ( LOG.isDebugEnabled() ) {
118             LOG.debug("Fetching RouteHeaderVO [id="+documentId+", user="+principalId+"]");
119         }
120         DocumentRouteHeaderValue document = loadDocument(documentId);
121         RouteHeaderDTO routeHeaderVO = DTOConverter.convertRouteHeader(document, principalId);
122         if (routeHeaderVO == null) {
123         	LOG.error("Returning null RouteHeaderVO [id=" + documentId + ", user=" + principalId + "]");
124         }
125         if ( LOG.isDebugEnabled() ) {
126             LOG.debug("Returning RouteHeaderVO [id=" + documentId + ", user=" + principalId + "]");
127         }
128         return routeHeaderVO;
129     }
130 
131     public AttributeSet getActionsRequested(String principalId, Long documentId) {
132         if (documentId == null) {
133             LOG.error("null routeHeaderId passed in.  Throwing RuntimeExcpetion");
134             throw new RuntimeException("Null documentId passed in.");
135         }
136         if (principalId == null) {
137             LOG.error("null principalId passed in.");
138             throw new RuntimeException("null principalId passed in");
139         }
140         if ( LOG.isDebugEnabled() ) {
141             LOG.debug("Fetching DocumentRouteHeaderValue [id="+documentId+", user="+principalId+"]");
142         }
143         DocumentRouteHeaderValue document = loadDocument(documentId);
144         return KEWServiceLocator.getActionRequestService().getActionsRequested(document, principalId, true);
145     }
146 
147     public RouteHeaderDTO getRouteHeader(Long documentId) throws WorkflowException {
148         if (documentId == null) {
149             LOG.error("null routeHeaderId passed in.");
150             throw new RuntimeException("null routeHeaderId passed in");
151         }
152         if ( LOG.isDebugEnabled() ) {
153             LOG.debug("Fetching RouteHeaderVO [id="+documentId+"]");
154         }
155         DocumentRouteHeaderValue document = loadDocument(documentId);
156         
157         UserSession userSession = UserSession.getAuthenticatedUser();
158         String principalId = null;
159         if (userSession != null) { // get the principalId if we can
160         	principalId = userSession.getPrincipalId();
161         }
162         
163         RouteHeaderDTO routeHeaderVO = DTOConverter.convertRouteHeader(document, principalId);
164         if (routeHeaderVO == null) {
165         	LOG.error("Returning null RouteHeaderVO [id=" + documentId + "]");
166         }
167         if ( LOG.isDebugEnabled() ) {
168             LOG.debug("Returning RouteHeaderVO [id=" + documentId + "]");
169         }
170         return routeHeaderVO;
171     }
172 
173     public String getDocumentStatus(Long documentId) throws WorkflowException {
174 	if (documentId == null) {
175 	    LOG.error("null documentId passed in.");
176             throw new IllegalArgumentException("null documentId passed in");
177 	}
178 	String documentStatus = KEWServiceLocator.getRouteHeaderService().getDocumentStatus(documentId);
179 	if (StringUtils.isEmpty(documentStatus)) {
180 	    throw new WorkflowException("Could not locate a document with the ID " + documentId);
181 	}
182 	return documentStatus;
183     }
184 
185     public DocumentDetailDTO getDocumentDetail(Long documentId) throws WorkflowException {
186         if (documentId == null) {
187             LOG.error("null documentId passed in.");
188             throw new RuntimeException("null documentId passed in");
189         }
190         if ( LOG.isDebugEnabled() ) {
191         	LOG.debug("Fetching DocumentDetailVO [id="+documentId+"]");
192         }
193         DocumentRouteHeaderValue document = loadDocument(documentId);
194         DocumentDetailDTO documentDetailVO = DTOConverter.convertDocumentDetail(document);
195         if (documentDetailVO == null) {
196         	LOG.error("Returning null DocumentDetailVO [id=" + documentId + "]");
197         }
198         if ( LOG.isDebugEnabled() ) {
199         	LOG.debug("Returning DocumentDetailVO [id=" + documentId + "]");
200         }
201         return documentDetailVO;
202     }
203 
204     public RouteNodeInstanceDTO getNodeInstance(Long nodeInstanceId) throws WorkflowException {
205         if (nodeInstanceId == null) {
206             LOG.error("null nodeInstanceId passed in.");
207             throw new RuntimeException("null nodeInstanceId passed in");
208         }
209         if ( LOG.isDebugEnabled() ) {
210         	LOG.debug("Fetching RouteNodeInstanceVO [id="+nodeInstanceId+"]");
211         }
212         RouteNodeInstance nodeInstance = KEWServiceLocator.getRouteNodeService().findRouteNodeInstanceById(nodeInstanceId);
213         return DTOConverter.convertRouteNodeInstance(nodeInstance);
214     }
215 
216     public DocumentTypeDTO getDocumentType(Long documentTypeId) throws WorkflowException {
217         if (documentTypeId == null) {
218             LOG.error("null documentTypeId passed in.");
219             throw new RuntimeException("null documentTypeId passed in.");
220         }
221         if ( LOG.isDebugEnabled() ) {
222         	LOG.debug("Fetching DocumentTypeVO [documentTypeId="+documentTypeId+"]");
223         }
224         return KEWServiceLocator.getDocumentTypeService().getDocumentTypeVO(documentTypeId);
225     }
226 
227     public DocumentTypeDTO getDocumentTypeByName(String documentTypeName) throws WorkflowException {
228         if (documentTypeName == null) {
229             LOG.error("null documentTypeName passed in.");
230             throw new RuntimeException("null documentTypeName passed in");
231         }
232         if ( LOG.isDebugEnabled() ) {
233         	LOG.debug("Fetching DocumentTypeVO [documentTypeName="+documentTypeName+"]");
234         }
235         DocumentTypeDTO documentType = KEWServiceLocator.getDocumentTypeService().getDocumentTypeVO(documentTypeName);
236         return documentType;
237     }
238 
239     public Long getNewResponsibilityId() {
240     	LOG.debug("Getting new responsibility id.");
241         Long rid = KEWServiceLocator.getResponsibilityIdService().getNewResponsibilityId();
242         if ( LOG.isDebugEnabled() ) {
243         	LOG.debug("returning responsibility Id " + rid);
244         }
245         return rid;
246     }
247 
248     public Integer getUserActionItemCount(String principalId) throws WorkflowException {
249         return Integer.valueOf(KEWServiceLocator.getActionListService().getCount(principalId));
250     }
251 
252 	public ActionItemDTO[] getActionItemsForPrincipal(String principalId) throws WorkflowException {
253         //added by Derek
254         Collection actionItems = KEWServiceLocator.getActionListService().getActionList(principalId, null);
255         ActionItemDTO[] actionItemVOs = new ActionItemDTO[actionItems.size()];
256         int i = 0;
257         for (Iterator iterator = actionItems.iterator(); iterator.hasNext(); i++) {
258             ActionItem actionItem = (ActionItem) iterator.next();
259             actionItemVOs[i] = DTOConverter.convertActionItem(actionItem);
260         }
261         return actionItemVOs;
262     }
263 
264     public ActionItemDTO[] getAllActionItems(Long routeHeaderId) throws WorkflowException {
265         Collection actionItems = KEWServiceLocator.getActionListService().getActionListForSingleDocument(routeHeaderId);
266         ActionItemDTO[] actionItemVOs = new ActionItemDTO[actionItems.size()];
267         int i = 0;
268         for (Iterator iterator = actionItems.iterator(); iterator.hasNext(); i++) {
269             ActionItem actionItem = (ActionItem) iterator.next();
270             actionItemVOs[i] = DTOConverter.convertActionItem(actionItem);
271         }
272         return actionItemVOs;
273     }
274 
275     public ActionItemDTO[] getActionItems(Long routeHeaderId, String[] actionRequestedCodes) throws WorkflowException {
276         List<String> actionRequestedCds = Arrays.asList(actionRequestedCodes);
277         ActionItemDTO[] actionItems = getAllActionItems(routeHeaderId);
278         List<ActionItemDTO> matchingActionitems = new ArrayList<ActionItemDTO>();
279         for (int i = 0; i < actionItems.length; i++) {
280             ActionItemDTO actionItemVO = actionItems[i];
281             if (actionRequestedCds.contains(actionItemVO.getActionRequestCd())) {
282                 matchingActionitems.add(actionItemVO);
283             }
284         }
285         ActionItemDTO[] returnActionItems = new ActionItemDTO[matchingActionitems.size()];
286         int j = 0;
287         for (ActionItemDTO actionItemVO : matchingActionitems) {
288             returnActionItems[j] = actionItemVO;
289             j++;
290         }
291         return returnActionItems;
292     }
293 
294     public ActionRequestDTO[] getAllActionRequests(Long routeHeaderId) throws WorkflowException {
295         return getActionRequests(routeHeaderId, null, null);
296     }
297 
298     /**
299      * Returns a flattened list of ActionRequests which match the given criteria.
300      * Because the list is flattened, that means that all children requests from
301      * all graphs are returned in the top-level list.
302      */
303     public ActionRequestDTO[] getActionRequests(Long routeHeaderId, String nodeName, String principalId) throws WorkflowException {
304         if (routeHeaderId == null) {
305             LOG.error("null routeHeaderId passed in.");
306             throw new RuntimeException("null routeHeaderId passed in.");
307         }
308         if ( LOG.isDebugEnabled() ) {
309         	LOG.debug("Fetching ActionRequestVOs [docId="+routeHeaderId+"]");
310         }
311         List actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByRouteHeaderId(routeHeaderId);
312         List matchingActionRequests = new ArrayList();
313         for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
314             ActionRequestValue actionRequestValue = (ActionRequestValue) iterator.next();
315             if (actionRequestMatches(actionRequestValue, nodeName, principalId)) {
316                 matchingActionRequests.add(actionRequestValue);
317             }
318         }
319         ActionRequestDTO[] actionRequestVOs = new ActionRequestDTO[matchingActionRequests.size()];
320         int i = 0;
321         for (Iterator iter = matchingActionRequests.iterator(); iter.hasNext(); i++) {
322             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
323             actionRequestVOs[i] = DTOConverter.convertActionRequest(actionRequest);
324         }
325         return actionRequestVOs;
326     }
327 
328     private boolean actionRequestMatches(ActionRequestValue actionRequest, String nodeName, String principalId) throws WorkflowException {
329         boolean matchesUserId = true;  // assume a match in case user is empty
330         boolean matchesNodeName = true;  // assume a match in case node name is empty
331         if (StringUtils.isNotBlank(nodeName)) {
332             matchesNodeName = nodeName.equals(actionRequest.getPotentialNodeName());
333         }
334         if (principalId != null) {
335             matchesUserId = actionRequest.isRecipientRoutedRequest(principalId);
336         }
337         return matchesNodeName && matchesUserId;
338     }
339 
340     public ActionTakenDTO[] getActionsTaken(Long routeHeaderId) throws WorkflowException {
341         if (routeHeaderId == null) {
342             LOG.error("null routeHeaderId passed in.");
343             throw new RuntimeException("null routeHeaderId passed in.");
344         }
345         if ( LOG.isDebugEnabled() ) {
346         	LOG.debug("Fetching ActionTakenVOs [docId="+routeHeaderId+"]");
347         }
348         Collection actionsTaken = KEWServiceLocator.getActionTakenService().findByRouteHeaderId(routeHeaderId);
349         ActionTakenDTO[] actionTakenVOs = new ActionTakenDTO[actionsTaken.size()];
350         int i = 0;
351         for (Iterator iter = actionsTaken.iterator(); iter.hasNext(); i++) {
352             ActionTakenValue actionTaken = (ActionTakenValue) iter.next();
353             actionTakenVOs[i] = DTOConverter.convertActionTakenWithActionRequests(actionTaken);
354         }
355         return actionTakenVOs;
356     }
357 
358     /**
359      * This work is also being done in the bowels of convertDocumentContentVO in DTOConverter so some code
360      * could be reduced.
361      *
362      * @param definition
363      * @return WorkflowAttributeValidationErrorVO[] errors from client input into attribute
364      */
365     public WorkflowAttributeValidationErrorDTO[] validateWorkflowAttributeDefinitionVO(WorkflowAttributeDefinitionDTO definition) throws WorkflowException {
366         if (definition == null) {
367             LOG.error("null definition passed in.");
368             throw new RuntimeException("null definition passed in.");
369         }
370         if ( LOG.isDebugEnabled() ) {
371         	LOG.debug("Validating WorkflowAttributeDefinitionVO [attributeName="+definition.getAttributeName()+"]");
372         }
373         AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinitionVO(definition, null);
374         WorkflowAttribute attribute = null;
375         if (attributeDefinition != null) {
376         	attribute = (WorkflowAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
377         }
378         if (attribute instanceof GenericXMLRuleAttribute) {
379             Map<String, String> attributePropMap = new HashMap<String, String>();
380             GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
381             xmlAttribute.setRuleAttribute(attributeDefinition.getRuleAttribute());
382             for (int i = 0; i < definition.getProperties().length; i++) {
383 		PropertyDefinitionDTO property = definition.getProperties()[i];
384 		attributePropMap.put(property.getName(), property.getValue());
385 	    }
386             xmlAttribute.setParamMap(attributePropMap);
387 	}
388         //validate inputs from client application if the attribute is capable
389         if (attribute instanceof WorkflowAttributeXmlValidator) {
390             List errors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
391             WorkflowAttributeValidationErrorDTO[] errorVOs = new WorkflowAttributeValidationErrorDTO[errors.size()];
392             for (int i = 0; i < errorVOs.length; i++) {
393                 errorVOs[i] = DTOConverter.convertWorkflowAttributeValidationError((WorkflowAttributeValidationError)errors.get(i));
394             }
395             return errorVOs;
396         } else {
397             // WORKAROUND: if it is not validatable, then just quietly succeed
398             return new WorkflowAttributeValidationErrorDTO[0];
399         }
400     }
401 
402     public RouteNodeInstanceDTO[] getDocumentRouteNodeInstances(Long documentId) throws WorkflowException {
403     	if ( LOG.isDebugEnabled() ) {
404     		LOG.debug("Fetching RouteNodeInstanceVOs [docId=" + documentId + "]");
405     	}
406     	return convertRouteNodeInstances(KEWServiceLocator.getRouteNodeService().getFlattenedNodeInstances(loadDocument(documentId), true));
407     }
408 
409     public RouteNodeInstanceDTO[] getActiveNodeInstances(Long documentId) throws WorkflowException {
410     	if ( LOG.isDebugEnabled() ) {
411     		LOG.debug("Fetching active RouteNodeInstanceVOs [docId=" + documentId + "]");
412     	}
413         loadDocument(documentId);
414         return convertRouteNodeInstances(KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(documentId));
415     }
416 
417     public RouteNodeInstanceDTO[] getTerminalNodeInstances(Long documentId) throws WorkflowException {
418     	if ( LOG.isDebugEnabled() ) {
419     		LOG.debug("Fetching terminal RouteNodeInstanceVOs [docId=" + documentId + "]");
420     	}
421     	loadDocument(documentId);
422         return convertRouteNodeInstances(KEWServiceLocator.getRouteNodeService().getTerminalNodeInstances(documentId));
423     }
424 
425     public RouteNodeInstanceDTO[] getCurrentNodeInstances(Long documentId) throws WorkflowException {
426     	if ( LOG.isDebugEnabled() ) {
427     		LOG.debug("Fetching current RouteNodeInstanceVOs [docId=" + documentId + "]");
428     	}
429     	loadDocument(documentId);
430     	return convertRouteNodeInstances(KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId));
431     }
432 
433     private RouteNodeInstanceDTO[] convertRouteNodeInstances(List nodeInstances) throws WorkflowException {
434         RouteNodeInstanceDTO[] nodeInstanceVOs = new RouteNodeInstanceDTO[nodeInstances.size()];
435         int i = 0;
436         for (Iterator iter = nodeInstances.iterator(); iter.hasNext(); ) {
437             nodeInstanceVOs[i++] = DTOConverter.convertRouteNodeInstance((RouteNodeInstance) iter.next());
438         }
439         return nodeInstanceVOs;
440     }
441 
442     public boolean isUserInRouteLog(Long routeHeaderId, String principalId, boolean lookFuture) throws WorkflowException {
443     	return isUserInRouteLogWithOptionalFlattening(routeHeaderId, principalId, lookFuture, false);
444     }
445     
446     public boolean isUserInRouteLogWithOptionalFlattening(Long routeHeaderId, String principalId, boolean lookFuture, boolean flattenNodes) throws WorkflowException {
447         if (routeHeaderId == null) {
448             LOG.error("null routeHeaderId passed in.");
449             throw new RuntimeException("null routeHeaderId passed in.");
450         }
451         if (principalId == null ){
452             LOG.error("null principalId passed in.");
453             throw new RiceRuntimeException("null principalId passed in.");
454         }
455         boolean authorized = false;
456         if ( LOG.isDebugEnabled() ) {
457         	LOG.debug("Evaluating isUserInRouteLog [docId=" + routeHeaderId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
458         }
459         DocumentRouteHeaderValue routeHeader = loadDocument(routeHeaderId);
460         KimPrincipal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
461         List actionsTaken = KEWServiceLocator.getActionTakenService().findByRouteHeaderIdWorkflowId(routeHeaderId, principal.getPrincipalId());
462 
463         if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
464         	return true;
465         }
466 
467         if (actionsTaken.size() > 0) {
468         	LOG.debug("found action taken by user");
469         	authorized = true;
470         }
471 
472         List actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByRouteHeaderId(routeHeaderId);
473         if (actionRequestListHasPrincipal(principal, actionRequests)) {
474         	authorized = true;
475         }
476 
477         if (!lookFuture) {
478         	return authorized;
479         }
480 
481 
482         SimulationEngine simulationEngine = new SimulationEngine();
483         SimulationCriteria criteria = new SimulationCriteria(routeHeaderId);
484         criteria.setDestinationNodeName(null); // process entire document to conclusion
485         criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
486         criteria.setFlattenNodes(flattenNodes);
487 
488         try {
489         	SimulationResults results = simulationEngine.runSimulation(criteria);
490         	if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
491         		authorized = true;
492         	}
493         } catch (Exception e) {
494         	throw new RiceRuntimeException(e);
495         }
496 
497         return authorized;
498     }
499 
500     /**
501      * @see org.kuali.rice.kew.service.WorkflowUtility#getPrincipalIdsInRouteLog(java.lang.Long, boolean)
502      */
503     public String[] getPrincipalIdsInRouteLog(Long routeHeaderId, boolean lookFuture) throws WorkflowException {
504         if (routeHeaderId == null) {
505             LOG.error("null routeHeaderId passed in.");
506             throw new RuntimeException("null routeHeaderId passed in.");
507         }
508     	Set<String> principalIds = new HashSet<String>();
509         try {
510         	if ( LOG.isDebugEnabled() ) {
511         		LOG.debug("Evaluating isUserInRouteLog [docId=" + routeHeaderId + ", lookFuture=" + lookFuture + "]");
512         	}
513             DocumentRouteHeaderValue routeHeader = loadDocument(routeHeaderId);
514             List<ActionTakenValue> actionsTakens =
515             	(List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByRouteHeaderId(routeHeaderId);
516             //TODO: confirm that the initiator is not already there in the actionstaken
517             principalIds.add(routeHeader.getInitiatorWorkflowId());
518             for(ActionTakenValue actionTaken: actionsTakens){
519             	principalIds.add(actionTaken.getPrincipalId());
520             }
521             List<ActionRequestValue> actionRequests =
522             	KEWServiceLocator.getActionRequestService().findAllActionRequestsByRouteHeaderId(routeHeaderId);
523             for(ActionRequestValue actionRequest: actionRequests){
524             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
525             }
526             if (!lookFuture) {
527             	return principalIds.toArray(new String[]{});
528             }
529             SimulationEngine simulationEngine = new SimulationEngine();
530             SimulationCriteria criteria = new SimulationCriteria(routeHeaderId);
531             criteria.setDestinationNodeName(null); // process entire document to conclusion
532             SimulationResults results = simulationEngine.runSimulation(criteria);
533             actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
534             for(ActionRequestValue actionRequest: actionRequests){
535             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
536             }
537         } catch (Exception ex) {
538             LOG.warn("Problems getting principalIds in Route Log for routeHeaderId: "+routeHeaderId+". Exception:"+ex.getMessage(),ex);
539         }
540     	return principalIds.toArray(new String[]{});
541     }
542 
543 	/**
544 	 * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
545 	 * groups if need be.
546 	 * 
547 	 * @param actionRequest
548 	 */
549 	private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
550 		List<String> results = Collections.emptyList();
551 		if (actionRequest.getPrincipalId() != null) {
552 			results = Collections.singletonList(actionRequest.getPrincipalId());
553 		} else if (actionRequest.getGroupId() != null) {
554 			List<String> principalIdsForGroup = 
555 				KIMServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
556 			if (principalIdsForGroup != null) {
557 				results = principalIdsForGroup;
558 			}
559 		}
560 		return results;
561 	}
562 
563     /***
564      * @see org.kuali.rice.kew.service.WorkflowUtility#getPrincipalIdsWithPendingActionRequestByActionRequestedAndDocId(java.lang.String, java.lang.Long)
565      */
566     public String[] getPrincipalIdsWithPendingActionRequestByActionRequestedAndDocId(String actionRequestedCd, Long routeHeaderId){
567     	List<String> results = KEWServiceLocator.getActionRequestService().
568     				getPrincipalIdsWithPendingActionRequestByActionRequestedAndDocId(actionRequestedCd, routeHeaderId);
569     	if (ObjectUtils.isNull(results)) {
570     		return null;
571     	}
572     	return results.toArray(new String[]{});
573     }
574 
575     private boolean actionRequestListHasPrincipal(KimPrincipal principal, List actionRequests) throws WorkflowException {
576         for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
577             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
578             if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
579                 return true;
580             }
581         }
582         return false;
583     }
584 
585     private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
586         for (Recipient recipient : recipients) {
587             if (actionRequest.isRecipientRoutedRequest(recipient)) {
588                 return true;
589             }
590         }
591         return false;
592     }
593 
594     /**
595      * @see org.kuali.rice.kew.service.WorkflowUtility#documentWillHaveAtLeastOneActionRequest(org.kuali.rice.kew.dto.ReportCriteriaDTO, java.lang.String[], boolean)
596      */
597     public boolean documentWillHaveAtLeastOneActionRequest(ReportCriteriaDTO reportCriteriaDTO, String[] actionRequestedCodes, boolean ignoreCurrentActionRequests) {
598         try {
599 	        SimulationEngine simulationEngine = new SimulationEngine();
600 	        SimulationCriteria criteria = DTOConverter.convertReportCriteriaDTO(reportCriteriaDTO);
601 	        // set activate requests to true by default so force action works correctly
602 	        criteria.setActivateRequests(Boolean.TRUE);
603 	        SimulationResults results = simulationEngine.runSimulation(criteria);
604             List actionRequestsToProcess = results.getSimulatedActionRequests();
605             if (!ignoreCurrentActionRequests) {
606                 actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
607             }
608             for (Iterator iter = actionRequestsToProcess.iterator(); iter.hasNext();) {
609 				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
610                 if (actionRequest.isDone()) {
611                     // an action taken has eliminated this request from being active
612                     continue;
613                 }
614 				// if no action request codes are passed in.... assume any request found is
615 		    	if ( (actionRequestedCodes == null) || (actionRequestedCodes.length == 0) ) {
616 		    		// we found an action request
617 		    		return true;
618 		    	}
619 		    	// check the action requested codes passed in
620 		    	for (int i = 0; i < actionRequestedCodes.length; i++) {
621 					String requestedActionRequestCode = actionRequestedCodes[i];
622 					if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
623 					    boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
624 					    if (satisfiesDestinationUserCriteria) {
625 					        if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
626 					            return true;
627 					        } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
628 					            return true;
629 					        }
630 					    }
631 					}
632 				}
633 			}
634 	        return false;
635         } catch (Exception ex) {
636         	String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
637             LOG.error(error,ex);
638             if (ex instanceof RuntimeException) {
639             	throw (RuntimeException)ex;
640             }
641             throw new RuntimeException(error, ex);
642         }
643     }
644 
645     public boolean isLastApproverInRouteLevel(Long routeHeaderId, String principalId, Integer routeLevel) throws WorkflowException {
646         if (routeLevel == null) {
647             LOG.error("null routeLevel passed in.");
648             throw new RuntimeException("null routeLevel passed in.");
649         }
650         if ( LOG.isDebugEnabled() ) {
651         	LOG.debug("Evaluating isLastApproverInRouteLevel [docId=" + routeHeaderId + ", principalId=" + principalId + ", routeLevel=" + routeLevel + "]");
652         }
653         DocumentRouteHeaderValue document = loadDocument(routeHeaderId);
654         RouteNode node = CompatUtils.getNodeForLevel(document.getDocumentType(), routeLevel);
655         if (node == null) {
656             throw new RuntimeException("Cannot resolve given route level to an approriate node name: " + routeLevel);
657         }
658         return isLastApproverAtNode(routeHeaderId, principalId, node.getRouteNodeName());
659     }
660 
661     public boolean isLastApproverAtNode(Long routeHeaderId, String principalId, String nodeName) throws WorkflowException {
662         if (routeHeaderId == null) {
663             LOG.error("null routeHeaderId passed in.");
664             throw new RuntimeException("null routeHeaderId passed in.");
665         }
666         if (principalId == null ){
667             LOG.error("null principalId passed in.");
668             throw new RuntimeException("null principalId passed in.");
669         }
670         if ( LOG.isDebugEnabled() ) {
671         	LOG.debug("Evaluating isLastApproverAtNode [docId=" + routeHeaderId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
672         }
673         loadDocument(routeHeaderId);
674         // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
675         // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
676         // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
677         boolean activateFirst = Utilities.getKNSParameterBooleanValue(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.FEATURE_DETAIL_TYPE, KEWConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND, false);
678 
679         List requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(routeHeaderId, KEWConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
680         if (requests == null || requests.isEmpty()) {
681             return false;
682         }
683         ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
684         for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
685             ActionRequestValue request = (ActionRequestValue) iterator.next();
686             if (activateFirst && !request.isActive()) {
687                 KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
688             }
689             if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
690                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
691             } else if (request.isGroupRequest() && KIMServiceLocator.getIdentityManagementService().isMemberOfGroup(principalId, request.getGroup().getGroupId())) {
692                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
693             }
694         }
695         boolean allDeactivated = true;
696         for (Iterator iter = requests.iterator(); iter.hasNext();) {
697             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
698             allDeactivated = allDeactivated && actionRequest.isDeactivated();
699         }
700         return allDeactivated;
701     }
702 
703     /**
704      * Used to determine if a given route level will produce Approve Action Requests.
705      *
706      * @deprecated use routeNodeHasApproverActionRequest instead
707      */
708     public boolean routeLevelHasApproverActionRequest(String documentTypeName, String docContent, Integer routeLevel) throws WorkflowException {
709         if (documentTypeName == null) {
710             LOG.error("null document type name passed in.");
711             throw new RuntimeException("null document type passed in.");
712         }
713         if (routeLevel == null) {
714             LOG.error("null routeLevel passed in.");
715             throw new RuntimeException("null routeLevel passed in.");
716         }
717         if ( LOG.isDebugEnabled() ) {
718         	LOG.debug("Evaluating routeLevelHasApproverActionRequest [docTypeName=" + documentTypeName + ", routeLevel=" + routeLevel + "]");
719         }
720         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
721         if (!CompatUtils.isRouteLevelCompatible(documentType)) {
722             throw new WorkflowException("The given document type is not route level compatible: " + documentTypeName);
723         }
724         RouteNode routeNode = CompatUtils.getNodeForLevel(documentType, routeLevel);
725         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, routeLevel);
726     }
727 
728     public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) throws WorkflowException {
729         if (documentTypeName == null) {
730             LOG.error("null docType passed in.");
731             throw new RuntimeException("null docType passed in.");
732         }
733         if (nodeName == null) {
734             LOG.error("null nodeName passed in.");
735             throw new RuntimeException("null nodeName passed in.");
736         }
737         if ( LOG.isDebugEnabled() ) {
738         	LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
739         }
740         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
741         RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
742         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KEWConstants.INVALID_ROUTE_LEVEL));
743     }
744 
745     /**
746      * Really this method needs to be implemented using the routingReport functionality (the SimulationEngine).
747      * This would get rid of the needs for us to call to FlexRM directly.
748      */
749     private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) throws WorkflowException {
750         if (documentType == null) {
751             LOG.error("could not locate document type.");
752             throw new RuntimeException("could not locate document type.");
753         }
754         if (docContent == null) {
755             LOG.error("null docContent passed in.");
756             throw new RuntimeException("null docContent passed in.");
757         }
758         if (node == null) {
759             LOG.error("could not locate route node.");
760             throw new RuntimeException("could not locate route node.");
761         }
762 
763         DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
764         routeHeader.setRouteHeaderId(new Long(0));
765         routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
766         routeHeader.setDocRouteLevel(routeLevel);
767         routeHeader.setDocVersion(new Integer(KEWConstants.CURRENT_DOCUMENT_VERSION));
768 
769         if (node.getRuleTemplate() != null && node.isFlexRM()) {
770             String ruleTemplateName = node.getRuleTemplate().getName();
771             routeHeader.setDocContent(docContent);
772             routeHeader.setDocRouteStatus(KEWConstants.ROUTE_HEADER_INITIATED_CD);
773             FlexRM flexRM = new FlexRM();
774     		RouteContext context = RouteContext.getCurrentRouteContext();
775     		context.setDocument(routeHeader);
776     		try {
777     			List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
778     			for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
779     				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
780     				if (actionRequest.isApproveOrCompleteRequest()) {
781     					return true;
782     				}
783     			}
784     		} finally {
785     			RouteContext.clearCurrentRouteContext();
786     		}
787         }
788         return false;
789     }
790 
791     private void incomingParamCheck(Object object, String name) {
792         if (object == null) {
793             LOG.error("null " + name + " passed in.");
794             throw new RuntimeException("null " + name + " passed in.");
795         }
796     }
797 
798     public void reResolveRole(String documentTypeName, String roleName, String qualifiedRoleNameLabel) throws WorkflowException {
799         incomingParamCheck(documentTypeName, "documentTypeName");
800         incomingParamCheck(roleName, "roleName");
801         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
802         if ( LOG.isDebugEnabled() ) {
803         	LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
804         }
805     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
806     	if (Utilities.isEmpty(qualifiedRoleNameLabel)) {
807     		KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
808     	} else {
809     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
810     	}
811     }
812 
813     public void reResolveRoleByDocumentId(Long documentId, String roleName, String qualifiedRoleNameLabel) throws WorkflowException {
814         incomingParamCheck(documentId, "documentId");
815         incomingParamCheck(roleName, "roleName");
816         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
817         if ( LOG.isDebugEnabled() ) {
818         	LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
819         }
820         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
821     	if (Utilities.isEmpty(qualifiedRoleNameLabel)) {
822     		KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
823     	} else {
824     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
825     	}
826     }
827 
828     public DocumentDetailDTO routingReport(ReportCriteriaDTO reportCriteria) throws WorkflowException {
829         incomingParamCheck(reportCriteria, "reportCriteria");
830         if ( LOG.isDebugEnabled() ) {
831         	LOG.debug("Executing routing report [docId=" + reportCriteria.getRouteHeaderId() + ", docTypeName=" + reportCriteria.getDocumentTypeName() + "]");
832         }
833         SimulationCriteria criteria = DTOConverter.convertReportCriteriaDTO(reportCriteria);
834         return DTOConverter.convertDocumentDetail(KEWServiceLocator.getRoutingReportService().report(criteria));
835     }
836 
837     public boolean isFinalApprover(Long routeHeaderId, String principalId) throws WorkflowException {
838         incomingParamCheck(routeHeaderId, "routeHeaderId");
839         incomingParamCheck(principalId, "principalId");
840         if ( LOG.isDebugEnabled() ) {
841         	LOG.debug("Evaluating isFinalApprover [docId=" + routeHeaderId + ", principalId=" + principalId + "]");
842         }
843         DocumentRouteHeaderValue routeHeader = loadDocument(routeHeaderId);
844         List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(routeHeaderId);
845         List finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
846         if (finalApproverNodes.isEmpty()) {
847         	if ( LOG.isDebugEnabled() ) {
848         		LOG.debug("Could not locate final approval nodes for document " + routeHeaderId);
849         	}
850             return false;
851         }
852         Set finalApproverNodeNames = new HashSet();
853         for (Iterator iterator = finalApproverNodes.iterator(); iterator.hasNext();) {
854             RouteNode node = (RouteNode) iterator.next();
855             finalApproverNodeNames.add(node.getRouteNodeName());
856         }
857 
858         int approveRequest = 0;
859         for (Iterator iter = requests.iterator(); iter.hasNext();) {
860             ActionRequestValue request = (ActionRequestValue) iter.next();
861             RouteNodeInstance nodeInstance = request.getNodeInstance();
862             if (nodeInstance == null) {
863             	if ( LOG.isDebugEnabled() ) {
864             		LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
865             	}
866                 return false;
867             }
868             if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
869                 if (request.isApproveOrCompleteRequest()) {
870                     approveRequest++;
871                     if ( LOG.isDebugEnabled() ) {
872                     	LOG.debug("Found request is approver " + request.getActionRequestId());
873                     }
874                     if (! request.isRecipientRoutedRequest(principalId)) {
875                     	if ( LOG.isDebugEnabled() ) {
876                     		LOG.debug("Action Request not for user " + principalId);
877                     	}
878                         return false;
879                     }
880                 }
881             }
882         }
883 
884         if (approveRequest == 0) {
885             return false;
886         }
887         if ( LOG.isDebugEnabled() ) {
888         	LOG.debug("Principal "+principalId+" is final approver for document " + routeHeaderId);
889         }
890         return true;
891     }
892 
893     public boolean isSuperUserForDocumentType(String principalId, Long documentTypeId) throws WorkflowException {
894     	if ( LOG.isDebugEnabled() ) {
895     		LOG.debug("Determining super user status [principalId=" + principalId + ", documentTypeId=" + documentTypeId + "]");
896     	}
897     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findById(documentTypeId);
898     	boolean isSuperUser = KEWServiceLocator.getDocumentTypePermissionService().canAdministerRouting(principalId, documentType);
899     	if ( LOG.isDebugEnabled() ) {
900     		LOG.debug("Super user status is " + isSuperUser + ".");
901     	}
902     	return isSuperUser;
903     }
904 
905     private DocumentRouteHeaderValue loadDocument(Long documentId) {
906         return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
907     }
908 
909     public DocumentContentDTO getDocumentContent(Long routeHeaderId) throws WorkflowException {
910     	if ( LOG.isDebugEnabled() ) {
911     		LOG.debug("Fetching document content [docId=" + routeHeaderId + "]");
912     	}
913     	DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeHeaderId);
914     	return DTOConverter.convertDocumentContent(document.getDocContent(), routeHeaderId);
915     }
916 
917 	public String[] getPreviousRouteNodeNames(Long documentId) throws WorkflowException {
918 		if ( LOG.isDebugEnabled() ) {
919 			LOG.debug("Fetching previous node names [docId=" + documentId + "]");
920 		}
921 		DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
922 		//going conservative for now.  if the doc isn't enroute or exception nothing will be returned.
923 		if (document.isEnroute() || document.isInException()) {
924 
925 			List activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document);
926 			long largetActivatedNodeId = 0;
927 			for (Iterator iter = activeNodeInstances.iterator(); iter.hasNext();) {
928 				RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iter.next();
929 				if (routeNodeInstance.getRouteNode().getRouteNodeId().longValue() > largetActivatedNodeId) {
930 					largetActivatedNodeId = routeNodeInstance.getRouteNode().getRouteNodeId().longValue();
931 				}
932 			}
933 
934 			List routeNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodeInstances(document, false);
935 			List nodeNames = new ArrayList();
936 
937 			for (Iterator iter = routeNodes.iterator(); iter.hasNext();) {
938 				RouteNodeInstance routeNode = (RouteNodeInstance) iter.next();
939 				if (routeNode.isComplete() && !nodeNames.contains(routeNode.getName())) {
940 					//if the prototype of the nodeInstance we're analyzing is less than the largest id of all our active prototypes
941 					//then add it to the list.  This is an attempt to account for return to previous hitting a single node multiple times
942 					if (routeNode.getRouteNode().getRouteNodeId().longValue() < largetActivatedNodeId) {
943 						nodeNames.add(routeNode.getName());
944 					}
945 				}
946 			}
947 			return (String[]) nodeNames.toArray(new String[nodeNames.size()]);
948 		} else {
949 			return new String[0];
950 		}
951 	}
952 
953     public RuleDTO[] ruleReport(RuleReportCriteriaDTO ruleReportCriteria) throws WorkflowException {
954         incomingParamCheck(ruleReportCriteria, "ruleReportCriteria");
955         if (ruleReportCriteria == null) {
956             throw new IllegalArgumentException("At least one criterion must be sent in a RuleReportCriteriaDTO object");
957         }
958         if ( LOG.isDebugEnabled() ) {
959         	LOG.debug("Executing rule report [responsibleUser=" + ruleReportCriteria.getResponsiblePrincipalId() + ", responsibleWorkgroup=" +
960                     ruleReportCriteria.getResponsibleGroupId() + "]");
961         }
962         Map extensionValues = new HashMap();
963         if (ruleReportCriteria.getRuleExtensionVOs() != null) {
964             for (int i = 0; i < ruleReportCriteria.getRuleExtensionVOs().length; i++) {
965                 RuleExtensionDTO ruleExtensionVO = ruleReportCriteria.getRuleExtensionVOs()[i];
966                 KeyValuePair ruleExtension = DTOConverter.convertRuleExtensionVO(ruleExtensionVO);
967                 extensionValues.put(ruleExtension.getKey(), ruleExtension.getValue());
968             }
969         }
970         Collection<String> actionRequestCodes = new ArrayList<String>();
971         if ( (ruleReportCriteria.getActionRequestCodes() != null) && (ruleReportCriteria.getActionRequestCodes().length != 0) ) {
972             actionRequestCodes = Arrays.asList(ruleReportCriteria.getActionRequestCodes());
973         }
974         Collection rulesFound = KEWServiceLocator.getRuleService().search(ruleReportCriteria.getDocumentTypeName(),ruleReportCriteria.getRuleTemplateName(),
975                 ruleReportCriteria.getRuleDescription(), ruleReportCriteria.getResponsibleGroupId(),
976                 ruleReportCriteria.getResponsiblePrincipalId(),
977                 ruleReportCriteria.isConsiderWorkgroupMembership(),ruleReportCriteria.isIncludeDelegations(),
978                 ruleReportCriteria.isActiveIndicator(),extensionValues,actionRequestCodes);
979         RuleDTO[] returnableRules = new RuleDTO[rulesFound.size()];
980         int i = 0;
981         for (Iterator iter = rulesFound.iterator(); iter.hasNext();) {
982             RuleBaseValues rule = (RuleBaseValues) iter.next();
983             returnableRules[i] = DTOConverter.convertRule(rule);
984             i++;
985         }
986         return returnableRules;
987     }
988 
989     public DocumentSearchResultDTO performDocumentSearch(DocumentSearchCriteriaDTO criteriaVO) throws WorkflowException {
990         return performDocumentSearchWithPrincipal(null, criteriaVO);
991     }
992 
993     public DocumentSearchResultDTO performDocumentSearchWithPrincipal(String principalId, DocumentSearchCriteriaDTO criteriaVO) throws WorkflowException {
994         DocSearchCriteriaDTO criteria = DTOConverter.convertDocumentSearchCriteriaDTO(criteriaVO);
995         criteria.setOverridingUserSession(true);
996         if (principalId != null) {
997         	KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
998         } else {
999         	// if the principal is null then we need to use the system "kr" user for execution of the search
1000         	principalId = KEWServiceLocator.getIdentityHelperService().getSystemPrincipal().getPrincipalId();
1001         }
1002         DocumentSearchResultComponents components = KEWServiceLocator.getDocumentSearchService().getListRestrictedByCriteria(principalId, criteria);
1003         DocumentSearchResultDTO resultVO = DTOConverter.convertDocumentSearchResultComponents(components);
1004         resultVO.setOverThreshold(criteria.isOverThreshold());
1005         resultVO.setSecurityFilteredRows(Integer.valueOf(criteria.getSecurityFilteredRows()));
1006         return resultVO;
1007     }
1008 
1009     /**
1010      * @see org.kuali.rice.kew.service.WorkflowUtility#getDocumentInitiatorPrincipalId(java.lang.Long)
1011      */
1012     public String getDocumentInitiatorPrincipalId(Long routeHeaderId)
1013     		throws WorkflowException {
1014         if (routeHeaderId == null) {
1015             LOG.error("null routeHeaderId passed in.");
1016             throw new RuntimeException("null routeHeaderId passed in.");
1017         }
1018 
1019         DocumentRouteHeaderValue header = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeHeaderId, false);
1020         if ( header == null) {
1021         	return null;
1022         }
1023     	return header.getInitiatorWorkflowId();
1024     }
1025     /**
1026      * @see org.kuali.rice.kew.service.WorkflowUtility#getDocumentRoutedByPrincipalId(java.lang.Long)
1027      */
1028     public String getDocumentRoutedByPrincipalId(Long routeHeaderId)
1029     		throws WorkflowException {
1030         if (routeHeaderId == null) {
1031             LOG.error("null routeHeaderId passed in.");
1032             throw new RuntimeException("null routeHeaderId passed in.");
1033         }
1034 
1035         DocumentRouteHeaderValue header = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeHeaderId, false);
1036         if ( header == null) {
1037         	return null;
1038         }
1039     	return header.getRoutedByUserWorkflowId();
1040     }
1041 
1042     /**
1043 	 *
1044 	 * @see org.kuali.rice.kew.service.WorkflowUtility#getSearchableAttributeDateTimeValuesByKey(java.lang.Long, java.lang.String)
1045 	 */
1046 	public Timestamp[] getSearchableAttributeDateTimeValuesByKey(
1047 			Long documentId, String key) {
1048 		List<Timestamp> results = KEWServiceLocator.getRouteHeaderService().getSearchableAttributeDateTimeValuesByKey(documentId, key);
1049 		if (ObjectUtils.isNull(results)) {
1050 			return null;
1051 		}
1052 		return results.toArray(new Timestamp[]{});
1053 	}
1054 
1055 	/**
1056 	 *
1057 	 * @see org.kuali.rice.kew.service.WorkflowUtility#getSearchableAttributeFloatValuesByKey(java.lang.Long, java.lang.String)
1058 	 */
1059 	public BigDecimal[] getSearchableAttributeFloatValuesByKey(Long documentId, String key) {
1060 		List<BigDecimal> results = KEWServiceLocator.getRouteHeaderService().getSearchableAttributeFloatValuesByKey(documentId, key);
1061 		if (ObjectUtils.isNull(results)) {
1062 			return null;
1063 		}
1064 		return results.toArray(new BigDecimal[]{});
1065 	}
1066 
1067 	/**
1068 	 *
1069 	 * @see org.kuali.rice.kew.service.WorkflowUtility#getSearchableAttributeLongValuesByKey(java.lang.Long, java.lang.String)
1070 	 */
1071 	public Long[] getSearchableAttributeLongValuesByKey(Long documentId, String key) {
1072 		List<Long> results = KEWServiceLocator.getRouteHeaderService().getSearchableAttributeLongValuesByKey(documentId, key);
1073 		if (ObjectUtils.isNull(results)) {
1074 			return null;
1075 		}
1076 		return results.toArray(new Long[]{});
1077 	}
1078 
1079 	/**
1080 	 *
1081 	 * @see org.kuali.rice.kew.service.WorkflowUtility#getSearchableAttributeStringValuesByKey(java.lang.Long, java.lang.String)
1082 	 */
1083 	public String[] getSearchableAttributeStringValuesByKey(Long documentId, String key) {
1084 		List<String> results = KEWServiceLocator.getRouteHeaderService().getSearchableAttributeStringValuesByKey(documentId, key);
1085 		if (ObjectUtils.isNull(results)) {
1086 			return null;
1087 		}
1088 		return results.toArray(new String[]{});
1089 	}
1090 
1091     public String getFutureRequestsKey(String principalId) {
1092         return KEWConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_KEY + "," + principalId + "," + new Date().toString() + ", " + Math.random();
1093     }
1094 
1095     public String getReceiveFutureRequestsValue() {
1096         return KEWConstants.RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
1097     }
1098 
1099     public String getDoNotReceiveFutureRequestsValue() {
1100         return KEWConstants.DONT_RECEIVE_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
1101     }
1102 
1103     public String getClearFutureRequestsValue() {
1104         return KEWConstants.CLEAR_FUTURE_REQUESTS_BRANCH_STATE_VALUE;
1105     }
1106     
1107     public boolean hasRouteNode(String documentTypeName, String routeNodeName) throws WorkflowException {
1108         if (documentTypeName == null) {
1109             LOG.error("null documentTypeName passed in.");
1110             throw new RuntimeException("null documentTypeName passed in");
1111         }
1112         if (routeNodeName == null) {
1113             LOG.error("null routeNodeName passed in.");
1114             throw new RuntimeException("null routeNodeName passed in");
1115         }
1116     	DocumentTypeDTO docType = getDocumentTypeByName(documentTypeName);
1117     	if(docType==null){
1118             LOG.error("docType null for the documentTypeName passed in "+documentTypeName);
1119             throw new RuntimeException("docType null for the documentTypeName passed in "+documentTypeName);
1120         }
1121     	RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(docType.getDocTypeId(), routeNodeName);
1122     	
1123     	if(routeNode==null){
1124     		if(docType.getDocTypeParentName() == null)
1125     			return false;
1126     		else
1127     			return hasRouteNode(docType.getDocTypeParentName(), routeNodeName);	
1128     	}
1129     	else
1130     		return true;
1131     	
1132     }
1133 
1134     public boolean isCurrentActiveDocumentType(String documentTypeName) throws WorkflowException {
1135     	DocumentType docType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1136     	return docType != null && docType.isActive();
1137     }
1138     
1139 	public DocumentDetailDTO getDocumentDetailFromAppId(
1140 			String documentTypeName, String appId) throws WorkflowException {
1141         if (documentTypeName == null) {
1142             LOG.error("null documentTypeName passed in.");
1143             throw new RuntimeException("null documentTypeName passed in");
1144         }
1145         if (appId == null) {
1146             LOG.error("null appId passed in.");
1147             throw new RuntimeException("null appId passed in");
1148         }
1149         
1150         Collection routeHeaderIds = KEWServiceLocator.getRouteHeaderService().findByDocTypeAndAppId(documentTypeName, appId);
1151         
1152         if(routeHeaderIds==null||routeHeaderIds.isEmpty()){
1153             LOG.error("No RouteHeader Ids found for criteria");
1154     		throw new WorkflowException("No RouteHeader Ids found for criteria");
1155         }
1156         if(routeHeaderIds.size()>1){
1157             LOG.error("More than one RouteHeader Id found for criteria");
1158     		throw new WorkflowException("More than one RouteHeader Id found for criteria");
1159 		}
1160         
1161         return getDocumentDetail((Long)routeHeaderIds.iterator().next());
1162 	}
1163 	
1164 	public String getAppDocId(Long documentId) {
1165  	 	return KEWServiceLocator.getRouteHeaderService().getAppDocId(documentId);
1166  	}
1167     
1168     public DocumentStatusTransitionDTO[] getDocumentStatusTransitionHistory(Long documentId) throws WorkflowException {
1169         if (documentId == null) {
1170             LOG.error("null routeHeaderId passed in.");
1171             throw new RuntimeException("null routeHeaderId passed in");
1172         }
1173         if ( LOG.isDebugEnabled() ) {
1174             LOG.debug("Fetching document status transition history [id="+documentId+"]");
1175         }
1176         DocumentRouteHeaderValue document = loadDocument(documentId);
1177         
1178         UserSession userSession = UserSession.getAuthenticatedUser();
1179         String principalId = null;
1180         if (userSession != null) { // get the principalId if we can
1181         	principalId = userSession.getPrincipalId();
1182         }
1183         List<DocumentStatusTransition> list = document.getAppDocStatusHistory();
1184 
1185         DocumentStatusTransitionDTO[] transitionHistory = new DocumentStatusTransitionDTO[list.size()];        
1186         int i = 0;
1187         for (Iterator iter = list.iterator(); iter.hasNext();) {
1188         	DocumentStatusTransition transition = (DocumentStatusTransition) iter.next();
1189             transitionHistory[i] = DTOConverter.convertDocumentStatusTransition(transition);
1190             i++;
1191         }
1192         return transitionHistory;
1193     }
1194 
1195 	//for document link
1196 
1197 	public void deleteDocumentLink(DocumentLinkDTO docLink) throws WorkflowException {
1198 		KEWServiceLocator.getDocumentLinkService().deleteDocumentLink(initDocLink(docLink));
1199 	}
1200 
1201 	/**
1202 	 * This overridden method ...
1203 	 * 
1204 	 * @see org.kuali.rice.kew.routeheader.service.WorkflowDocumentService#addDocumentLink(org.kuali.rice.kew.documentlink.DocumentLink)
1205 	 */
1206 	public void addDocumentLink(DocumentLinkDTO docLinkVO) throws WorkflowException {
1207 		KEWServiceLocator.getDocumentLinkService().saveDocumentLink(initDocLink(docLinkVO));
1208 	}
1209 
1210 	/**
1211 	 * This overridden method ...
1212 	 * 
1213 	 * @see org.kuali.rice.kew.routeheader.service.WorkflowDocumentService#getgetLinkedDocumentsByDocId(java.lang.Long)
1214 	 */
1215 	public List<DocumentLinkDTO> getLinkedDocumentsByDocId(Long id) throws WorkflowException {
1216 		return DTOConverter.convertDocumentLinkToArrayList(KEWServiceLocator.getDocumentLinkService().getLinkedDocumentsByDocId(id));
1217 	}
1218 
1219 	/**
1220 	 * This overridden method ...
1221 	 * 
1222 	 * @see org.kuali.rice.kew.routeheader.service.WorkflowDocumentService#getDocumentLink(org.kuali.rice.kew.documentlink.DocumentLink)
1223 	 */
1224 	public DocumentLinkDTO getLinkedDocument(DocumentLinkDTO docLinkVO) throws WorkflowException{
1225 		return DTOConverter.convertDocumentLink(KEWServiceLocator.getDocumentLinkService().getLinkedDocument(initDocLink(docLinkVO)));
1226 	}
1227 
1228 	/**
1229 	 * This overridden method ...
1230 	 * 
1231 	 * @see org.kuali.rice.kew.routeheader.service.WorkflowDocumentService#deleteDocumentLinkByDocId(java.lang.Long)
1232 	 */
1233 	public void deleteDocumentLinksByDocId(Long id) throws WorkflowException{
1234 		KEWServiceLocator.getDocumentLinkService().deleteDocumentLinksByDocId(id);
1235 	}
1236 
1237 	private DocumentLink initDocLink(DocumentLinkDTO docLinkVO){
1238 		DocumentLink docLink = new DocumentLink();
1239 		docLink.setDocLinkId(docLinkVO.getLinbkId());
1240 		docLink.setOrgnDocId(docLinkVO.getOrgnDocId());
1241 		docLink.setDestDocId(docLinkVO.getDestDocId());
1242 
1243 		return docLink;
1244 	}
1245 }