001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.engine.node;
018:
019: import java.util.ArrayList;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Set;
024:
025: import edu.iu.uis.eden.KEWServiceLocator;
026: import edu.iu.uis.eden.actionrequests.ActionRequestValue;
027: import edu.iu.uis.eden.engine.RouteContext;
028: import edu.iu.uis.eden.engine.RouteHelper;
029: import edu.iu.uis.eden.exception.ResourceUnavailableException;
030: import edu.iu.uis.eden.exception.RouteManagerException;
031: import edu.iu.uis.eden.exception.WorkflowException;
032: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
033: import edu.iu.uis.eden.routemodule.RouteModule;
034: import edu.iu.uis.eden.util.ClassDumper;
035:
036: /**
037: * A node which generates {@link ActionRequestValue} objects from a {@link RouteModule}.
038: *
039: * @author ewestfal
040: */
041: public class RequestsNode extends RequestActivationNode {
042:
043: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
044: .getLogger(RequestsNode.class);
045:
046: private static String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
047:
048: public SimpleResult process(RouteContext routeContext,
049: RouteHelper routeHelper) throws Exception {
050: DocumentRouteHeaderValue document = routeContext.getDocument();
051: RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
052: RouteNode node = nodeInstance.getRouteNode();
053: try {
054: // refreshSearchableAttributes(routeContext);
055: // while no routable actions are activated and there are more routeLevels to process
056: if (nodeInstance.isInitial()) {
057: // document = SpringServiceLocator.getRouteHeaderService().getRouteHeader(document.getRouteHeaderId());
058: if (LOG.isDebugEnabled()) {
059: LOG.debug("RouteHeader info inside routing loop\n"
060: + ClassDumper.dumpFields(document));
061: LOG
062: .debug("Looking for new actionRequests - routeLevel: "
063: + node.getRouteNodeName());
064: }
065: boolean suppressPolicyErrors = isSupressingPolicyErrors(routeContext);
066: boolean pastFinalApprover = isPastFinalApprover(
067: document, nodeInstance);
068: // routeContext.setDocument(document);
069: List requests = getNewActionRequests(routeContext);
070: // for mandatory routes, requests must be generated
071: if ((requests.isEmpty())
072: && node.getMandatoryRouteInd().booleanValue()
073: && !suppressPolicyErrors) {
074: LOG
075: .warn("no requests generated for mandatory route - "
076: + node.getRouteNodeName());
077: throw new RouteManagerException(
078: "No requests generated for mandatory route "
079: + node.getRouteNodeName() + ":"
080: + node.getRouteMethodName(),
081: routeContext);
082: }
083: // determine if we have any approve requests for FinalApprover checks
084: boolean hasApproveRequest = false;
085: for (Iterator iter = requests.iterator(); iter
086: .hasNext();) {
087: ActionRequestValue actionRequest = (ActionRequestValue) iter
088: .next();
089: hasApproveRequest = actionRequest
090: .isApproveOrCompleteRequest()
091: || hasApproveRequest;
092: }
093: // if final approver route level and no approve request send to exception routing
094: if (node.getFinalApprovalInd().booleanValue()) {
095: // we must have an approve request generated if final approver level.
096: if (!hasApproveRequest && !suppressPolicyErrors) {
097: throw new RuntimeException(
098: "No Approve Request generated after final approver");
099: }
100: } else if (pastFinalApprover) {
101: // we can't allow generation of approve requests after final approver. This guys going to exception routing.
102: if (hasApproveRequest && !suppressPolicyErrors) {
103: throw new RuntimeException(
104: "Approve Request generated after final approver");
105: }
106: }
107: }
108: return super .process(routeContext, routeHelper);
109: } catch (Exception e) {
110: LOG.error("Caught exception routing", e);
111: throw new RouteManagerException(e.getMessage(), e,
112: routeContext);
113: }
114: }
115:
116: /**
117: * @param routeLevel
118: * Route level for which the action requests will be generated
119: * @param routeHeader
120: * route header for which the action requests are generated
121: * @param saveFlag
122: * if true the new action requests will be saved, if false they are not written to the db
123: * @return List of ActionRequests - NOTE they are only written to DB if saveFlag is set
124: * @throws WorkflowException
125: * @throws ResourceUnavailableException
126: */
127: public List getNewActionRequests(RouteContext context)
128: throws Exception {
129: RouteNodeInstance nodeInstance = context.getNodeInstance();
130: String routeMethodName = nodeInstance.getRouteNode()
131: .getRouteMethodName();
132: LOG.debug("Looking for action requests in " + routeMethodName
133: + " : "
134: + nodeInstance.getRouteNode().getRouteNodeName());
135: List newRequests = new ArrayList();
136: try {
137: RouteModule routeModule = KEWServiceLocator
138: .getRouteModuleService().findRouteModule(
139: nodeInstance.getRouteNode());
140: List requests = routeModule.findActionRequests(context);
141: for (Iterator iterator = requests.iterator(); iterator
142: .hasNext();) {
143: ActionRequestValue actionRequest = (ActionRequestValue) iterator
144: .next();
145: actionRequest = KEWServiceLocator
146: .getActionRequestService()
147: .initializeActionRequestGraph(actionRequest,
148: context.getDocument(), nodeInstance);
149: saveActionRequest(context, actionRequest);
150: newRequests.add(actionRequest);
151: }
152: } catch (WorkflowException ex) {
153: LOG.warn("Caught WorkflowException during routing", ex);
154: throw new RouteManagerException(ex, context);
155: }
156: return newRequests;
157: }
158:
159: /**
160: * Checks if the document has past the final approver node by walking backward through the previous node instances.
161: * Ignores any previous nodes that have been "revoked".
162: */
163: private boolean isPastFinalApprover(
164: DocumentRouteHeaderValue document,
165: RouteNodeInstance nodeInstance) {
166: FinalApproverContext context = new FinalApproverContext();
167: List revokedNodeInstances = KEWServiceLocator
168: .getRouteNodeService()
169: .getRevokedNodeInstances(document);
170: Set revokedNodeInstanceIds = new HashSet();
171: for (Iterator iterator = revokedNodeInstances.iterator(); iterator
172: .hasNext();) {
173: RouteNodeInstance revokedNodeInstance = (RouteNodeInstance) iterator
174: .next();
175: revokedNodeInstanceIds.add(revokedNodeInstance
176: .getRouteNodeInstanceId());
177: }
178: isPastFinalApprover(nodeInstance.getPreviousNodeInstances(),
179: context, revokedNodeInstanceIds);
180: return context.isPast;
181: }
182:
183: private void isPastFinalApprover(List previousNodeInstances,
184: FinalApproverContext context, Set revokedNodeInstanceIds) {
185: if (previousNodeInstances != null
186: && !previousNodeInstances.isEmpty()) {
187: for (Iterator iterator = previousNodeInstances.iterator(); iterator
188: .hasNext();) {
189: if (context.isPast) {
190: return;
191: }
192: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
193: .next();
194: if (context.inspected.contains(getKey(nodeInstance))) {
195: continue;
196: } else {
197: context.inspected.add(getKey(nodeInstance));
198: }
199: if (Boolean.TRUE.equals(nodeInstance.getRouteNode()
200: .getFinalApprovalInd())) {
201: // if the node instance has been revoked (by a Return To Previous action for example)
202: // then we don't want to consider that node when we determine if we are past final
203: // approval or not
204: if (!revokedNodeInstanceIds.contains(nodeInstance
205: .getRouteNodeInstanceId())) {
206: context.isPast = true;
207: }
208: return;
209: }
210: isPastFinalApprover(nodeInstance
211: .getPreviousNodeInstances(), context,
212: revokedNodeInstanceIds);
213: }
214: }
215: }
216:
217: /**
218: * The method will get a key value which can be used for comparison purposes. If the node instance has a primary key value, it will be returned. However, if the node instance has not been saved to the database (i.e. during a simulation) this method will return the node instance passed in.
219: */
220: private Object getKey(RouteNodeInstance nodeInstance) {
221: Long id = nodeInstance.getRouteNodeInstanceId();
222: return (id != null ? (Object) id : (Object) nodeInstance);
223: }
224:
225: private class FinalApproverContext {
226: public Set inspected = new HashSet();
227: public boolean isPast = false;
228: }
229:
230: public static boolean isSupressingPolicyErrors(
231: RouteContext routeContext) {
232: Boolean suppressPolicyErrors = (Boolean) routeContext
233: .getParameters().get(SUPPRESS_POLICY_ERRORS_KEY);
234: if (suppressPolicyErrors == null || !suppressPolicyErrors) {
235: return false;
236: }
237: return true;
238: }
239:
240: @SuppressWarnings("unchecked")
241: public static void setSupressPolicyErrors(RouteContext routeContext) {
242: routeContext.getParameters().put(SUPPRESS_POLICY_ERRORS_KEY,
243: Boolean.TRUE);
244: }
245: }
|