001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.engine.node;
017    
018    import java.util.ArrayList;
019    import java.util.Collections;
020    import java.util.Iterator;
021    import java.util.List;
022    
023    import org.apache.commons.collections.CollectionUtils;
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.log4j.MDC;
026    import org.kuali.rice.kew.actionitem.ActionItem;
027    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
028    import org.kuali.rice.kew.api.action.ActionRequestStatus;
029    import org.kuali.rice.kew.api.exception.WorkflowException;
030    import org.kuali.rice.kew.engine.RouteContext;
031    import org.kuali.rice.kew.engine.RouteHelper;
032    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
033    import org.kuali.rice.kew.service.KEWServiceLocator;
034    import org.kuali.rice.kew.api.KewApiConstants;
035    import org.kuali.rice.kew.util.PerformanceLogger;
036    import org.kuali.rice.kew.util.Utilities;
037    
038    
039    /**
040     * A node which will activate any requests on it, returning true when there are no more requests 
041     * which require activation.
042     * 
043     * @author Kuali Rice Team (rice.collab@kuali.org)
044     */
045    public class RequestActivationNode extends RequestActivationNodeBase {
046    
047        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( RequestActivationNode.class );
048        private static long generatedRequestPriority = 0;
049    
050        public SimpleResult process(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
051            DocumentRouteHeaderValue document = routeContext.getDocument();
052            RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
053            if (routeContext.isSimulation()) {
054                if (routeContext.getActivationContext().isActivateRequests()) {
055                    activateRequests(routeContext, document, nodeInstance);
056                }
057                return new SimpleResult(true);
058            } else if (!activateRequests(routeContext, document, nodeInstance) && shouldTransition(document, nodeInstance)) {
059                return new SimpleResult(true);
060            } else {
061                return new SimpleResult(false);
062            }            
063        }
064        
065        public boolean shouldTransition(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
066            List requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
067            boolean shouldTransition = true;
068            for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
069                ActionRequestValue request = (ActionRequestValue) iterator.next();
070                if (request.isApproveOrCompleteRequest()) {
071                    shouldTransition = false;
072                    break;
073                }
074            }
075            return shouldTransition;
076        }
077    
078        /**
079         * Activates the action requests that are pending at this routelevel of the document. The requests are processed by
080         * priority and then request ID. It is implicit in the access that the requests are activated according to the route
081         * level above all.
082         *
083         * <p>FYI and acknowledement requests do not cause the processing to stop. Only action requests for approval or
084         * completion cause the processing to stop and then only for route level with a serialized or priority-parallel
085         * activation policy. Only requests at the current document's current route level are activated. Inactive requests
086         * at a lower level cause a routing exception.</p>
087         *
088         * <p>Exception routing and adhoc routing are processed slightly differently.</p>
089         * 
090         * @return true if the any approval actions were activated.
091         */
092        public boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) throws WorkflowException {
093            MDC.put("docId", document.getDocumentId());
094            PerformanceLogger performanceLogger = new PerformanceLogger(document.getDocumentId());
095            List<ActionItem> generatedActionItems = new ArrayList<ActionItem>();
096            List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
097            if (context.isSimulation()) {
098                    for (ActionRequestValue ar : context.getDocument().getActionRequests()) {
099                            // logic check below duplicates behavior of the ActionRequestService.findPendingRootRequestsByDocIdAtRouteNode(documentId, routeNodeInstanceId) method
100                                    if (ar.getCurrentIndicator()
101                                                    && (ActionRequestStatus.INITIALIZED.getCode().equals(ar.getStatus()) || ActionRequestStatus.ACTIVATED.getCode().equals(ar.getStatus()))
102                                                    && ar.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())
103                                                    && ar.getParentActionRequest() == null) {
104                                            requests.add(ar);
105                                    }
106                            }
107                requests.addAll(context.getEngineState().getGeneratedRequests());
108            } else {
109                requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
110            }
111            if ( LOG.isDebugEnabled() ) {
112                    LOG.debug("Pending Root Requests " + requests.size());
113            }
114            boolean activatedApproveRequest = activateRequestsCustom( context, requests, generatedActionItems, document, nodeInstance );
115    
116            // now let's send notifications, since this code needs to be able to activate each request individually, we need
117            // to collection all action items and then notify after all have been generated
118            notify(context, generatedActionItems, nodeInstance);
119    
120            performanceLogger.log("Time to activate requests.");
121            return activatedApproveRequest;
122        }
123    
124        protected boolean activateRequestsCustom( RouteContext context, List<ActionRequestValue> requests, List<ActionItem> generatedActionItems, 
125                    DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) throws WorkflowException {
126            Collections.sort(requests, new Utilities.PrioritySorter());
127            String activationType = nodeInstance.getRouteNode().getActivationType();
128            if (StringUtils.isBlank(activationType)) {
129                // not sure if this is really necessary, but preserves behavior prior to introduction of priority-parallel activation
130                activationType = KewApiConstants.ROUTE_LEVEL_SEQUENCE;
131            }
132            boolean isParallel = KewApiConstants.ROUTE_LEVEL_PARALLEL.equals(activationType);
133            boolean isPriorityParallel = KewApiConstants.ROUTE_LEVEL_PRIORITY_PARALLEL.equals(activationType);
134            boolean isSequential = KewApiConstants.ROUTE_LEVEL_SEQUENCE.equals(activationType);
135    
136            boolean activatedApproveRequest = false;
137            if (CollectionUtils.isNotEmpty(requests)) {
138                // if doing priority-parallel
139                int currentPriority = requests.get(0).getPriority();
140                for (ActionRequestValue request : requests ) {
141                    if (request.getParentActionRequest() != null || request.getNodeInstance() == null) {
142                        // 1. disregard request if it's not a top-level request
143                        // 2. disregard request if it's a "future" request and hasn't been attached to a node instance yet
144                        continue;
145                    }
146                    if (activatedApproveRequest && (!context.isSimulation() || !context.getActivationContext().isActivateRequests())) {
147                        if (isSequential || (isPriorityParallel && request.getPriority() != currentPriority)) {
148                            break;
149                        }
150                    }
151                    currentPriority = request.getPriority();
152                    if (request.isActive()) {
153                        activatedApproveRequest = activatedApproveRequest || request.isApproveOrCompleteRequest();
154                        continue;
155                    }
156                    logProcessingMessage(request);
157                    if ( LOG.isDebugEnabled() ) {
158                        LOG.debug("Activating request: " + request);
159                    }
160                    activatedApproveRequest = activateRequest(context, request, nodeInstance, generatedActionItems) || activatedApproveRequest;
161                }
162            }
163            return activatedApproveRequest;
164        }
165        
166        protected boolean activateRequest(RouteContext context, ActionRequestValue actionRequest, RouteNodeInstance nodeInstance, List generatedActionItems) {
167            if (actionRequest.isRoleRequest()) {
168                List actionRequests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(actionRequest.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
169                for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
170                    ActionRequestValue siblingRequest = (ActionRequestValue) iterator.next();
171                    if (actionRequest.getRoleName().equals(siblingRequest.getRoleName())) {
172                        generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(siblingRequest, context.getActivationContext()));
173                    }
174                }
175            }
176            generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(actionRequest, context.getActivationContext()));
177            return actionRequest.isApproveOrCompleteRequest() && ! actionRequest.isDone();
178        }
179        
180        protected void saveActionRequest(RouteContext context, ActionRequestValue actionRequest) {
181            if (!context.isSimulation()) {
182                KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
183            } else {
184                actionRequest.setActionRequestId(String.valueOf(generatedRequestPriority++));
185                context.getEngineState().getGeneratedRequests().add(actionRequest);    
186            }
187            
188        }
189        
190        protected void saveDocument(RouteContext context, DocumentRouteHeaderValue document) {
191            if (!context.isSimulation()) {
192                KEWServiceLocator.getRouteHeaderService().saveRouteHeader(document);
193            }
194        }
195    
196        protected void logProcessingMessage(ActionRequestValue request) {
197            if (LOG.isDebugEnabled()) {
198                    RouteNodeInstance nodeInstance = request.getNodeInstance();
199                StringBuffer buffer = new StringBuffer();
200                buffer.append("Processing AR: ").append(request.getActionRequestId()).append("\n");
201                buffer.append("AR Node Name: ").append(nodeInstance != null ? nodeInstance.getName() : "null").append("\n");
202                buffer.append("AR RouteLevel: ").append(request.getRouteLevel()).append("\n");
203                buffer.append("AR Request Code: ").append(request.getActionRequested()).append("\n");
204                buffer.append("AR Request priority: ").append(request.getPriority()).append("\n");
205                LOG.debug(buffer);
206            }
207        }
208                
209    }