1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.messaging.exceptionhandling;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22
23 import org.apache.log4j.MDC;
24 import org.kuali.rice.core.api.exception.RiceRuntimeException;
25 import org.kuali.rice.kew.actionitem.ActionItem;
26 import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
27 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
28 import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
29 import org.kuali.rice.kew.api.WorkflowRuntimeException;
30 import org.kuali.rice.kew.api.action.ActionRequestStatus;
31 import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
32 import org.kuali.rice.kew.engine.RouteContext;
33 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
34 import org.kuali.rice.kew.exception.RouteManagerException;
35 import org.kuali.rice.kew.exception.WorkflowDocumentExceptionRoutingService;
36 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
37 import org.kuali.rice.kew.framework.postprocessor.PostProcessor;
38 import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
39 import org.kuali.rice.kew.role.RoleRouteModule;
40 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
41 import org.kuali.rice.kew.service.KEWServiceLocator;
42 import org.kuali.rice.kew.api.KewApiConstants;
43 import org.kuali.rice.kew.util.PerformanceLogger;
44 import org.kuali.rice.krad.util.KRADConstants;
45 import org.kuali.rice.ksb.messaging.PersistedMessageBO;
46 import org.kuali.rice.ksb.service.KSBServiceLocator;
47
48
49 public class ExceptionRoutingServiceImpl implements WorkflowDocumentExceptionRoutingService {
50
51 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ExceptionRoutingServiceImpl.class);
52
53 public DocumentRouteHeaderValue placeInExceptionRouting(String errorMessage, PersistedMessageBO persistedMessage, String documentId) throws Exception {
54 RouteNodeInstance nodeInstance = null;
55 KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId);
56 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
57 RouteContext routeContext = establishRouteContext(document, null);
58 List<RouteNodeInstance> activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(documentId);
59 if (!activeNodeInstances.isEmpty()) {
60
61 nodeInstance = activeNodeInstances.get(0);
62 }
63 return placeInExceptionRouting(errorMessage, nodeInstance, persistedMessage, routeContext, document, true);
64 }
65
66 public DocumentRouteHeaderValue placeInExceptionRouting(Throwable throwable, PersistedMessageBO persistedMessage, String documentId) throws Exception {
67 return placeInExceptionRouting(throwable, persistedMessage, documentId, true);
68 }
69
70
71
72
73
74 public void placeInExceptionRoutingLastDitchEffort(Throwable throwable, PersistedMessageBO persistedMessage, String documentId) throws Exception {
75 placeInExceptionRouting(throwable, persistedMessage, documentId, false);
76 }
77
78 protected DocumentRouteHeaderValue placeInExceptionRouting(Throwable throwable, PersistedMessageBO persistedMessage, String documentId, boolean invokePostProcessor) throws Exception {
79 KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId);
80 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
81 throwable = unwrapRouteManagerExceptionIfPossible(throwable);
82 RouteContext routeContext = establishRouteContext(document, throwable);
83 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
84 Throwable cause = determineActualCause(throwable, 0);
85 String errorMessage = (cause != null && cause.getMessage() != null) ? cause.getMessage() : "";
86 return placeInExceptionRouting(errorMessage, nodeInstance, persistedMessage, routeContext, document, invokePostProcessor);
87 }
88
89 protected DocumentRouteHeaderValue placeInExceptionRouting(String errorMessage, RouteNodeInstance nodeInstance, PersistedMessageBO persistedMessage, RouteContext routeContext, DocumentRouteHeaderValue document, boolean invokePostProcessor) throws Exception {
90 String documentId = document.getDocumentId();
91 MDC.put("docId", documentId);
92 PerformanceLogger performanceLogger = new PerformanceLogger(documentId);
93 try {
94
95
96 List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
97 for (ActionRequestValue actionRequest : actionRequests) {
98 if (actionRequest.isActive()) {
99 actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
100 for (ActionItem actionItem : actionRequest.getActionItems()) {
101 KEWServiceLocator.getActionListService().deleteActionItem(actionItem);
102 }
103 KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
104 }
105 }
106
107 LOG.debug("Generating exception request for doc : " + documentId);
108 if (errorMessage == null) {
109 errorMessage = "";
110 }
111 if (errorMessage.length() > KewApiConstants.MAX_ANNOTATION_LENGTH) {
112 errorMessage = errorMessage.substring(0, KewApiConstants.MAX_ANNOTATION_LENGTH);
113 }
114 List<ActionRequestValue> exceptionRequests = new ArrayList<ActionRequestValue>();
115 if (nodeInstance.getRouteNode().isExceptionGroupDefined()) {
116 exceptionRequests = generateExceptionGroupRequests(routeContext);
117 } else {
118 exceptionRequests = generateKimExceptionRequests(routeContext);
119 }
120 if (exceptionRequests.isEmpty()) {
121 LOG.warn("Failed to generate exception requests for exception routing!");
122 }
123 document = activateExceptionRequests(routeContext, exceptionRequests, errorMessage, invokePostProcessor);
124
125 if (persistedMessage == null) {
126 LOG.warn("Attempting to delete null persisted message.");
127 } else {
128 KSBServiceLocator.getMessageQueueService().delete(persistedMessage);
129 }
130 } finally {
131 performanceLogger.log("Time to generate exception request.");
132 MDC.remove("docId");
133 }
134
135 return document;
136 }
137
138 protected void notifyStatusChange(DocumentRouteHeaderValue routeHeader, String newStatusCode, String oldStatusCode) throws InvalidActionTakenException {
139 DocumentRouteStatusChange statusChangeEvent = new DocumentRouteStatusChange(routeHeader.getDocumentId(), routeHeader.getAppDocId(), oldStatusCode, newStatusCode);
140 try {
141 LOG.debug("Notifying post processor of status change "+oldStatusCode+"->"+newStatusCode);
142 PostProcessor postProcessor = routeHeader.getDocumentType().getPostProcessor();
143 ProcessDocReport report = postProcessor.doRouteStatusChange(statusChangeEvent);
144 if (!report.isSuccess()) {
145 LOG.warn(report.getMessage(), report.getProcessException());
146 throw new InvalidActionTakenException(report.getMessage());
147 }
148 } catch (Exception ex) {
149 LOG.warn(ex, ex);
150 throw new WorkflowRuntimeException(ex);
151 }
152 }
153
154 protected List<ActionRequestValue> generateExceptionGroupRequests(RouteContext routeContext) {
155 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
156 ActionRequestFactory arFactory = new ActionRequestFactory(routeContext.getDocument(), null);
157 ActionRequestValue exceptionRequest = arFactory.createActionRequest(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, new Integer(0), new KimGroupRecipient(nodeInstance.getRouteNode().getExceptionWorkgroup()), "Exception Workgroup for route node " + nodeInstance.getName(), KewApiConstants.EXCEPTION_REQUEST_RESPONSIBILITY_ID, Boolean.TRUE, "");
158 return Collections.singletonList(exceptionRequest);
159 }
160
161 protected List<ActionRequestValue> generateKimExceptionRequests(RouteContext routeContext) throws Exception {
162 RoleRouteModule roleRouteModule = new RoleRouteModule();
163 roleRouteModule.setNamespace(KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE);
164 roleRouteModule.setResponsibilityTemplateName(KewApiConstants.EXCEPTION_ROUTING_RESPONSIBILITY_TEMPLATE_NAME);
165 List<ActionRequestValue> requests = roleRouteModule.findActionRequests(routeContext);
166
167 requests = KEWServiceLocator.getActionRequestService().getRootRequests(requests);
168 processExceptionRequests(requests);
169 return requests;
170 }
171
172
173
174
175
176
177
178 protected void processExceptionRequests(List<ActionRequestValue> exceptionRequests) {
179 if (exceptionRequests != null) {
180 for (ActionRequestValue actionRequest : exceptionRequests) {
181 processExceptionRequest(actionRequest);
182 }
183 }
184 }
185
186
187
188
189
190 protected void processExceptionRequest(ActionRequestValue actionRequest) {
191 actionRequest.setForceAction(true);
192 actionRequest.setNodeInstance(null);
193 processExceptionRequests(actionRequest.getChildrenRequests());
194 }
195
196
197
198
199
200
201
202
203
204 protected DocumentRouteHeaderValue activateExceptionRequests(RouteContext routeContext, List<ActionRequestValue> exceptionRequests, String exceptionMessage, boolean invokePostProcessor) throws Exception {
205 setExceptionAnnotations(exceptionRequests, exceptionMessage);
206
207 DocumentRouteHeaderValue rh = KEWServiceLocator.getRouteHeaderService().getRouteHeader(routeContext.getDocument().getDocumentId());
208 String oldStatus = rh.getDocRouteStatus();
209 rh.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_EXCEPTION_CD);
210 if (invokePostProcessor) {
211 notifyStatusChange(rh, KewApiConstants.ROUTE_HEADER_EXCEPTION_CD, oldStatus);
212 }
213 DocumentRouteHeaderValue documentRouteHeaderValue = KEWServiceLocator.getRouteHeaderService().
214 saveRouteHeader(rh);
215 KEWServiceLocator.getActionRequestService().activateRequests(exceptionRequests);
216 return documentRouteHeaderValue;
217 }
218
219
220
221
222 protected void setExceptionAnnotations(List<ActionRequestValue> actionRequests, String exceptionMessage) {
223 for (ActionRequestValue actionRequest : actionRequests) {
224 actionRequest.setAnnotation(exceptionMessage);
225 }
226 }
227
228 private Throwable unwrapRouteManagerExceptionIfPossible(Throwable throwable) {
229 if (throwable instanceof InvocationTargetException) {
230 throwable = throwable.getCause();
231 }
232 if (throwable != null && (! (throwable instanceof RouteManagerException)) && throwable.getCause() instanceof RouteManagerException) {
233 throwable = throwable.getCause();
234 }
235 return throwable;
236 }
237
238 protected Throwable determineActualCause(Throwable throwable, int depth) {
239 if (depth >= 10) {
240 return throwable;
241 }
242 if ((throwable instanceof InvocationTargetException) || (throwable instanceof RouteManagerException)) {
243 if (throwable.getCause() != null) {
244 return determineActualCause(throwable.getCause(), ++depth);
245 }
246 }
247 return throwable;
248 }
249
250 protected RouteContext establishRouteContext(DocumentRouteHeaderValue document, Throwable throwable) {
251 RouteContext routeContext = new RouteContext();
252 if (throwable instanceof RouteManagerException) {
253 RouteManagerException rmException = (RouteManagerException) throwable;
254 routeContext = rmException.getRouteContext();
255 } else {
256 routeContext.setDocument(document);
257 List<RouteNodeInstance> activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
258 if (!activeNodeInstances.isEmpty()) {
259
260 RouteNodeInstance nodeInstance = (RouteNodeInstance) activeNodeInstances.get(0);
261 routeContext.setNodeInstance(nodeInstance);
262 }
263 }
264 if (routeContext.getNodeInstance() == null) {
265
266 routeContext.setNodeInstance((RouteNodeInstance) document.getInitialRouteNodeInstances().get(0));
267 }
268 return routeContext;
269 }
270 }