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.messaging.exceptionhandling;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  
25  import org.apache.log4j.MDC;
26  import org.kuali.rice.core.exception.RiceRuntimeException;
27  import org.kuali.rice.core.util.ExceptionUtils;
28  import org.kuali.rice.kew.actionitem.ActionItem;
29  import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
30  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
31  import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
32  import org.kuali.rice.kew.engine.RouteContext;
33  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
34  import org.kuali.rice.kew.exception.InvalidActionTakenException;
35  import org.kuali.rice.kew.exception.RouteManagerException;
36  import org.kuali.rice.kew.exception.WorkflowDocumentExceptionRoutingService;
37  import org.kuali.rice.kew.exception.WorkflowRuntimeException;
38  import org.kuali.rice.kew.postprocessor.DocumentRouteStatusChange;
39  import org.kuali.rice.kew.postprocessor.PostProcessor;
40  import org.kuali.rice.kew.postprocessor.ProcessDocReport;
41  import org.kuali.rice.kew.role.RoleRouteModule;
42  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
43  import org.kuali.rice.kew.service.KEWServiceLocator;
44  import org.kuali.rice.kew.util.KEWConstants;
45  import org.kuali.rice.kew.util.PerformanceLogger;
46  import org.kuali.rice.kns.util.KNSConstants;
47  import org.kuali.rice.ksb.messaging.PersistedMessage;
48  import org.kuali.rice.ksb.service.KSBServiceLocator;
49  
50  
51  public class ExceptionRoutingServiceImpl implements WorkflowDocumentExceptionRoutingService {
52  
53      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ExceptionRoutingServiceImpl.class);
54  
55      public void placeInExceptionRouting(String errorMessage, PersistedMessage persistedMessage, Long routeHeaderId) throws Exception {
56   	 	RouteNodeInstance nodeInstance = null;
57   	 	KEWServiceLocator.getRouteHeaderService().lockRouteHeader(routeHeaderId, true);
58   	 	DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeHeaderId);
59   	 	RouteContext routeContext = establishRouteContext(document, null);
60   	 	List activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(routeHeaderId);
61   	 	if (!activeNodeInstances.isEmpty()) {
62   	 		// take the first active nodeInstance found.
63   	 		nodeInstance = (RouteNodeInstance) activeNodeInstances.get(0);
64   	 	}
65   	 	placeInExceptionRouting(errorMessage, nodeInstance, persistedMessage, routeContext, document, true);
66   	 }
67      
68      public void placeInExceptionRouting(Throwable throwable, PersistedMessage persistedMessage, Long routeHeaderId) throws Exception {
69      	placeInExceptionRouting(throwable, persistedMessage, routeHeaderId, true);
70      }
71      
72      /**
73       * In our case here, our last ditch effort to put the document into exception routing will try to do so without invoking
74       * the Post Processor for do route status change to "Exception" status.
75       */
76      public void placeInExceptionRoutingLastDitchEffort(Throwable throwable, PersistedMessage persistedMessage, Long routeHeaderId) throws Exception {
77      	placeInExceptionRouting(throwable, persistedMessage, routeHeaderId, false);
78      }
79      
80      protected void placeInExceptionRouting(Throwable throwable, PersistedMessage persistedMessage, Long routeHeaderId, boolean invokePostProcessor) throws Exception {
81      	KEWServiceLocator.getRouteHeaderService().lockRouteHeader(routeHeaderId, true);
82      	DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeHeaderId);
83      	throwable = unwrapRouteManagerExceptionIfPossible(throwable);
84          RouteContext routeContext = establishRouteContext(document, throwable);
85          RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
86      	Throwable cause = determineActualCause(throwable, 0);
87          String errorMessage = (cause != null && cause.getMessage() != null) ? cause.getMessage() : "";
88      	placeInExceptionRouting(errorMessage, nodeInstance, persistedMessage, routeContext, document, invokePostProcessor);
89      }
90      
91      protected void placeInExceptionRouting(String errorMessage, RouteNodeInstance nodeInstance, PersistedMessage persistedMessage, RouteContext routeContext, DocumentRouteHeaderValue document, boolean invokePostProcessor) throws Exception {
92      	Long routeHeaderId = document.getRouteHeaderId();
93          MDC.put("docId", routeHeaderId);
94          PerformanceLogger performanceLogger = new PerformanceLogger(routeHeaderId);
95          try {
96  
97              // mark all active requests to initialized and delete the action items
98              List actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(routeHeaderId);
99              for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
100                 ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
101                 if (actionRequest.isActive()) {
102                     actionRequest.setStatus(KEWConstants.ACTION_REQUEST_INITIALIZED);
103                     for (Iterator iterator = actionRequest.getActionItems().iterator(); iterator.hasNext();) {
104                         KEWServiceLocator.getActionListService().deleteActionItem((ActionItem) iterator.next());
105                     }
106                     KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
107                 }
108             }
109 
110             LOG.debug("Generating exception request for doc : " + routeHeaderId);
111             if (errorMessage == null) {
112             	errorMessage = "";
113             }
114             if (errorMessage.length() > KEWConstants.MAX_ANNOTATION_LENGTH) {
115                 errorMessage = errorMessage.substring(0, KEWConstants.MAX_ANNOTATION_LENGTH);
116             }
117             List<ActionRequestValue> exceptionRequests = new ArrayList<ActionRequestValue>();
118             if (nodeInstance.getRouteNode().isExceptionGroupDefined()) {
119             	exceptionRequests = generateExceptionGroupRequests(routeContext);
120             } else {
121             	exceptionRequests = generateKimExceptionRequests(routeContext);
122             }
123             if (exceptionRequests.isEmpty()) {
124             	throw new RiceRuntimeException("Failed to generate exception requests for exception routing!");
125             }
126             activateExceptionRequests(routeContext, exceptionRequests, errorMessage, invokePostProcessor);
127             KSBServiceLocator.getRouteQueueService().delete(persistedMessage);
128         } finally {
129             performanceLogger.log("Time to generate exception request.");
130             MDC.remove("docId");
131         }
132     }
133 
134     protected void notifyStatusChange(DocumentRouteHeaderValue routeHeader, String newStatusCode, String oldStatusCode) throws InvalidActionTakenException {
135         DocumentRouteStatusChange statusChangeEvent = new DocumentRouteStatusChange(routeHeader.getRouteHeaderId(), routeHeader.getAppDocId(), oldStatusCode, newStatusCode);
136         try {
137             LOG.debug("Notifying post processor of status change "+oldStatusCode+"->"+newStatusCode);
138             PostProcessor postProcessor = routeHeader.getDocumentType().getPostProcessor();
139             ProcessDocReport report = postProcessor.doRouteStatusChange(statusChangeEvent);
140             if (!report.isSuccess()) {
141                 LOG.warn(report.getMessage(), report.getProcessException());
142                 throw new InvalidActionTakenException(report.getMessage());
143             }
144         } catch (Exception ex) {
145             LOG.warn(ex, ex);
146             throw new WorkflowRuntimeException(ex);
147         }
148     }
149     
150     protected List<ActionRequestValue> generateExceptionGroupRequests(RouteContext routeContext) {
151     	RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
152     	ActionRequestFactory arFactory = new ActionRequestFactory(routeContext.getDocument(), nodeInstance);
153     	ActionRequestValue exceptionRequest = arFactory.createActionRequest(KEWConstants.ACTION_REQUEST_COMPLETE_REQ, new Integer(0), new KimGroupRecipient(nodeInstance.getRouteNode().getExceptionWorkgroup()), "Exception Workgroup for route node " + nodeInstance.getName(), KEWConstants.EXCEPTION_REQUEST_RESPONSIBILITY_ID, Boolean.TRUE, "");
154     	return Collections.singletonList(exceptionRequest);
155     }
156     
157     protected List<ActionRequestValue> generateKimExceptionRequests(RouteContext routeContext) throws Exception {
158     	RoleRouteModule roleRouteModule = new RoleRouteModule();
159     	roleRouteModule.setNamespace(KNSConstants.KUALI_RICE_WORKFLOW_NAMESPACE);
160     	roleRouteModule.setResponsibilityTemplateName(KEWConstants.EXCEPTION_ROUTING_RESPONSIBILITY_TEMPLATE_NAME);
161     	List<ActionRequestValue> requests = roleRouteModule.findActionRequests(routeContext);
162     	processExceptionRequests(requests);
163     	return requests;
164     }
165     
166     
167     
168     /**
169      * Takes the given list of Action Requests and ensures their attributes are set properly for exception
170      * routing requests.  Namely, this ensures that all "force action" values are set to "true".
171      */
172     protected void processExceptionRequests(List<ActionRequestValue> exceptionRequests) {
173     	if (exceptionRequests != null) {
174     		for (ActionRequestValue actionRequest : exceptionRequests) {
175     			processExceptionRequest(actionRequest);
176     		}
177     	}
178     }
179     
180     /**
181      * Processes a single exception request, ensuring that it's force action flag is set to true.
182      * It then recurses through any children requests.
183      */
184     protected void processExceptionRequest(ActionRequestValue actionRequest) {
185     	actionRequest.setForceAction(true);
186     	processExceptionRequests(actionRequest.getChildrenRequests());
187     }
188     
189     /**
190      * End IU Customization
191      * @param routeContext
192      * @param exceptionRequests
193      * @param exceptionMessage
194      * @throws Exception
195      */
196     
197     protected void activateExceptionRequests(RouteContext routeContext, List<ActionRequestValue> exceptionRequests, String exceptionMessage, boolean invokePostProcessor) throws Exception {
198     	setExceptionAnnotations(exceptionRequests, exceptionMessage);
199     	// TODO is there a reason we reload the document here?
200     	DocumentRouteHeaderValue rh = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeContext.getDocument().getRouteHeaderId());
201     	String oldStatus = rh.getDocRouteStatus();
202     	rh.setDocRouteStatus(KEWConstants.ROUTE_HEADER_EXCEPTION_CD);
203     	if (invokePostProcessor) {
204     		notifyStatusChange(rh, KEWConstants.ROUTE_HEADER_EXCEPTION_CD, oldStatus);
205     	}
206     	KEWServiceLocator.getRouteHeaderService().saveRouteHeader(rh);
207     	KEWServiceLocator.getActionRequestService().activateRequests(exceptionRequests);
208     }
209     
210     /**
211      * Sets the exception message as the annotation on the top-level Action Requests
212      */
213     protected void setExceptionAnnotations(List<ActionRequestValue> actionRequests, String exceptionMessage) {
214     	for (ActionRequestValue actionRequest : actionRequests) {
215     		actionRequest.setAnnotation(exceptionMessage);
216     	}
217     }
218 
219     private Throwable unwrapRouteManagerExceptionIfPossible(Throwable throwable) {
220     	throwable = ExceptionUtils.unwrapActualCause(throwable);
221     	if (throwable != null && (! (throwable instanceof RouteManagerException)) && throwable.getCause() instanceof RouteManagerException) {
222     		throwable = throwable.getCause();
223     	}
224     	return throwable;
225     }
226 
227     protected Throwable determineActualCause(Throwable throwable, int depth) {
228     	if (depth >= 10) {
229     		return throwable;
230     	}
231     	if ((throwable instanceof InvocationTargetException) || (throwable instanceof RouteManagerException)) {
232     		if (throwable.getCause() != null) {
233     			return determineActualCause(throwable.getCause(), ++depth);
234     		}
235     	}
236     	return throwable;
237     }
238     
239     protected RouteContext establishRouteContext(DocumentRouteHeaderValue document, Throwable throwable) {
240     	RouteContext routeContext = new RouteContext();
241         if (throwable instanceof RouteManagerException) {
242             RouteManagerException rmException = (RouteManagerException) throwable;
243             routeContext = rmException.getRouteContext();
244         } else {
245         	routeContext.setDocument(document);
246             List activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getRouteHeaderId());
247             if (!activeNodeInstances.isEmpty()) {
248                 // take the first active nodeInstance found.
249                 RouteNodeInstance nodeInstance = (RouteNodeInstance) activeNodeInstances.get(0);
250                 routeContext.setNodeInstance(nodeInstance);
251             }
252         }
253         if (routeContext.getNodeInstance() == null) {
254             // get the initial node instance
255             routeContext.setNodeInstance((RouteNodeInstance) document.getInitialRouteNodeInstances().get(0));
256         }
257         return routeContext;
258     }
259 }