001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kew.engine.node;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.Iterator;
021import java.util.List;
022
023import org.apache.commons.collections.CollectionUtils;
024import org.apache.commons.lang.StringUtils;
025import org.apache.log4j.MDC;
026import org.kuali.rice.kew.actionitem.ActionItem;
027import org.kuali.rice.kew.actionrequest.ActionRequestValue;
028import org.kuali.rice.kew.api.action.ActionRequestStatus;
029import org.kuali.rice.kew.api.exception.WorkflowException;
030import org.kuali.rice.kew.engine.RouteContext;
031import org.kuali.rice.kew.engine.RouteHelper;
032import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
033import org.kuali.rice.kew.service.KEWServiceLocator;
034import org.kuali.rice.kew.api.KewApiConstants;
035import org.kuali.rice.kew.util.PerformanceLogger;
036import 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 */
045public 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}