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 * A node which will activate any requests on it, returning true when there are no more requests which require
040 * activation.
041 *
042 * @author Kuali Rice Team (rice.collab@kuali.org)
043 */
044public class RequestActivationNode extends RequestActivationNodeBase {
045
046    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RequestActivationNode.class);
047    private static long generatedRequestPriority = 0;
048
049    @Override
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,
059                nodeInstance)) {
060            return new SimpleResult(true);
061        } else {
062            return new SimpleResult(false);
063        }
064    }
065
066    /**
067     * Returns true if this node has completed it's work and should transition to the next node.
068     *
069     * <p>This implementation will return true if there are no remaining pending approve or complete action requests at
070     * the given node instance. Subclasses can override this method to customize the behavior of how this determination
071     * is made.</p>
072     *
073     * @param document the document the is being processed
074     * @param nodeInstance the current node instance that is being processed
075     * @return true if this node has completed it's work, false otherwise
076     */
077    protected boolean shouldTransition(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
078        List<ActionRequestValue> requests =
079                KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
080                        document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
081        boolean shouldTransition = true;
082        for (ActionRequestValue request : requests) {
083            if (request.isApproveOrCompleteRequest()) {
084                shouldTransition = false;
085                break;
086            }
087        }
088        return shouldTransition;
089    }
090
091    /**
092     * Activates the action requests that are pending at this routelevel of the
093     * document. The requests are processed by priority and then request ID. It
094     * is implicit in the access that the requests are activated according to
095     * the route level above all.
096     * <p>
097     * FYI and acknowledgment requests do not cause the processing to stop. Only
098     * action requests for approval or completion cause the processing to stop
099     * and then only for route level with a serialized activation policy. Only
100     * requests at the current document's current route level are activated.
101     * Inactive requests at a lower level cause a routing exception.
102     * <p>
103     * Exception routing and adhoc routing are processed slightly differently.
104     *
105     * @return True if the any approval actions were activated.
106     * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
107     * @throws WorkflowException
108     */
109    public boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document,
110            RouteNodeInstance nodeInstance) throws WorkflowException {
111        MDC.put("docId", document.getDocumentId());
112        PerformanceLogger performanceLogger = new PerformanceLogger(document.getDocumentId());
113        List<ActionItem> generatedActionItems = new ArrayList<ActionItem>();
114        List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
115        if (context.isSimulation()) {
116            for (ActionRequestValue ar : context.getDocument().getActionRequests()) {
117                // TODO logic check below duplicates behavior of the ActionRequestService.findPendingRootRequestsByDocIdAtRouteNode(documentId, routeNodeInstanceId) method
118                if (ar.getCurrentIndicator()
119                        && (ActionRequestStatus.INITIALIZED.getCode().equals(ar.getStatus())
120                        || ActionRequestStatus.ACTIVATED.getCode().equals(ar.getStatus()))
121                        && ar.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())
122                        && ar.getParentActionRequest() == null) {
123                    requests.add(ar);
124                }
125            }
126            requests.addAll(context.getEngineState().getGeneratedRequests());
127        } else {
128            requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
129                    document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
130        }
131        if (LOG.isDebugEnabled()) {
132            LOG.debug("Pending Root Requests " + requests.size());
133        }
134        boolean activatedApproveRequest = activateRequestsCustom(context, requests, generatedActionItems, document,
135                nodeInstance);
136
137        // now let's send notifications, since this code needs to be able to activate each request individually, we need
138        // to collection all action items and then notify after all have been generated
139        notify(context, generatedActionItems, nodeInstance);
140
141        performanceLogger.log("Time to activate requests.");
142        return activatedApproveRequest;
143    }
144
145    protected boolean activateRequestsCustom(RouteContext context, List<ActionRequestValue> requests,
146            List<ActionItem> generatedActionItems, DocumentRouteHeaderValue document,
147            RouteNodeInstance nodeInstance) throws WorkflowException {
148        // make a copy of the list so that we can sort it
149        requests = new ArrayList<ActionRequestValue>(requests);
150        Collections.sort(requests, new Utilities.PrioritySorter());
151        String activationType = nodeInstance.getRouteNode().getActivationType();
152        if (StringUtils.isBlank(activationType)) {
153            // not sure if this is really necessary, but preserves behavior prior to introduction of priority-parallel activation
154            activationType = KewApiConstants.ROUTE_LEVEL_SEQUENCE;
155        }
156        boolean isParallel = KewApiConstants.ROUTE_LEVEL_PARALLEL.equals(activationType);
157        boolean isPriorityParallel = KewApiConstants.ROUTE_LEVEL_PRIORITY_PARALLEL.equals(activationType);
158        boolean isSequential = KewApiConstants.ROUTE_LEVEL_SEQUENCE.equals(activationType);
159
160        boolean activatedApproveRequest = false;
161        if (CollectionUtils.isNotEmpty(requests)) {
162            // if doing priority-parallel
163            int currentPriority = requests.get(0).getPriority();
164            for (ActionRequestValue request : requests) {
165                if (request.getParentActionRequest() != null || request.getNodeInstance() == null) {
166                    // 1. disregard request if it's not a top-level request
167                    // 2. disregard request if it's a "future" request and hasn't been attached to a node instance yet
168                    continue;
169                }
170                if (activatedApproveRequest && (!context.isSimulation() || !context.getActivationContext()
171                        .isActivateRequests())) {
172                    if (isSequential || (isPriorityParallel && request.getPriority() != currentPriority)) {
173                        break;
174                    }
175                }
176                currentPriority = request.getPriority();
177                if (request.isActive()) {
178                    activatedApproveRequest = activatedApproveRequest || request.isApproveOrCompleteRequest();
179                    continue;
180                }
181                logProcessingMessage(request);
182                if (LOG.isDebugEnabled()) {
183                    LOG.debug("Activating request: " + request);
184                }
185                activatedApproveRequest = activateRequest(context, request, nodeInstance, generatedActionItems)
186                        || activatedApproveRequest;
187            }
188        }
189        return activatedApproveRequest;
190    }
191
192    protected boolean activateRequest(RouteContext context, ActionRequestValue actionRequest,
193            RouteNodeInstance nodeInstance, List<ActionItem> generatedActionItems) {
194        if (actionRequest.isRoleRequest()) {
195            List<ActionRequestValue> actionRequests =
196                    KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
197                            actionRequest.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
198            for (ActionRequestValue siblingRequest : actionRequests) {
199                if (actionRequest.getRoleName().equals(siblingRequest.getRoleName())) {
200                    KEWServiceLocator.getActionRequestService().activateRequestNoNotification(siblingRequest,
201                            context.getActivationContext());
202                    // the generated action items can be found in the activation context
203                    generatedActionItems.addAll(context.getActivationContext().getGeneratedActionItems());
204                }
205            }
206        }
207        actionRequest = KEWServiceLocator.getActionRequestService().activateRequestNoNotification(actionRequest,
208                context.getActivationContext());
209        // the generated action items can be found in the activation context
210        generatedActionItems.addAll(context.getActivationContext().getGeneratedActionItems());
211        return actionRequest.isApproveOrCompleteRequest() && !actionRequest.isDone();
212    }
213
214    protected ActionRequestValue saveActionRequest(RouteContext context, ActionRequestValue actionRequest) {
215        if (!context.isSimulation()) {
216            return KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
217        } else {
218            actionRequest.setActionRequestId(String.valueOf(generatedRequestPriority++));
219            context.getEngineState().getGeneratedRequests().add(actionRequest);
220            return actionRequest;
221        }
222
223    }
224
225    protected DocumentRouteHeaderValue saveDocument(RouteContext context, DocumentRouteHeaderValue document) {
226        if (!context.isSimulation()) {
227            document = KEWServiceLocator.getRouteHeaderService().saveRouteHeader(document);
228            context.setDocument(document);
229        }
230        return document;
231    }
232
233    protected void logProcessingMessage(ActionRequestValue request) {
234        if (LOG.isDebugEnabled()) {
235            RouteNodeInstance nodeInstance = request.getNodeInstance();
236            StringBuffer buffer = new StringBuffer();
237            buffer.append("Processing AR: ").append(request.getActionRequestId()).append("\n");
238            buffer.append("AR Node Name: ").append(nodeInstance != null ? nodeInstance.getName() : "null").append("\n");
239            buffer.append("AR RouteLevel: ").append(request.getRouteLevel()).append("\n");
240            buffer.append("AR Request Code: ").append(request.getActionRequested()).append("\n");
241            buffer.append("AR Request priority: ").append(request.getPriority()).append("\n");
242            LOG.debug(buffer);
243        }
244    }
245
246}