View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.actions;
17  
18  import java.util.Collection;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Set;
22  
23  import org.apache.log4j.MDC;
24  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
25  import org.kuali.rice.kew.actionrequest.Recipient;
26  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
27  import org.kuali.rice.kew.api.KewApiServiceLocator;
28  import org.kuali.rice.kew.api.action.MovePoint;
29  import org.kuali.rice.kew.api.document.DocumentOrchestrationQueue;
30  import org.kuali.rice.kew.api.document.DocumentProcessingOptions;
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.RouteNode;
34  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
35  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
36  import org.kuali.rice.kew.service.KEWServiceLocator;
37  import org.kuali.rice.kew.api.KewApiConstants;
38  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
39  
40  
41  /**
42   * Returns a document to a previous node in the route.
43   *
44   * Current implementation only supports returning to a node on the main branch of the
45   * document.
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public class MoveDocumentAction extends ActionTakenEvent {
50  
51      protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
52  
53      private MovePoint movePoint;
54  
55      public MoveDocumentAction(DocumentRouteHeaderValue routeHeader, PrincipalContract principal) {
56          super(KewApiConstants.ACTION_TAKEN_MOVE_CD, routeHeader, principal);
57      }
58  
59      public MoveDocumentAction(DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, MovePoint movePoint) {
60          super(KewApiConstants.ACTION_TAKEN_MOVE_CD, routeHeader, principal, annotation);
61          this.movePoint = movePoint;
62      }
63  
64      /* (non-Javadoc)
65       * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List)
66       */
67      @Override
68      public String validateActionRules() {
69          return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId()), KEWServiceLocator.getRouteNodeService().getActiveRouteNodeNames(getRouteHeader().getDocumentId()));
70      }
71  
72      @Override
73  	public String validateActionRules(List<ActionRequestValue> actionRequests) {
74          return validateActionRules(actionRequests, KEWServiceLocator.getRouteNodeService().getActiveRouteNodeNames(getRouteHeader().getDocumentId()));
75  	}
76  
77      private String validateActionRules(List<ActionRequestValue> actionRequests, Collection<String> activeNodes) {
78          if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) {
79              return "Document is not in a state to be moved";
80          }
81          if (activeNodes.isEmpty()) {
82              return "Document has no active nodes.";
83          }
84          List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ);
85          if (!isActionCompatibleRequest(filteredActionRequests)) {
86              return "No request for the user is compatible with the MOVE action";
87          }
88          return "";
89      }
90  
91  
92      /* (non-Javadoc)
93       * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List)
94       */
95      public boolean isActionCompatibleRequest(List<ActionRequestValue> requests) {
96          //Move is always correct because the client application has authorized it
97          return true;
98      }
99  
100     public void recordAction() throws InvalidActionTakenException {
101         MDC.put("docId", getRouteHeader().getDocumentId());
102         updateSearchableAttributesIfPossible();
103         LOG.debug("Moving document " + getRouteHeader().getDocumentId() + " to point: " + displayMovePoint(movePoint) + ", annotation: " + annotation);
104 
105         List actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KewApiConstants.ACTION_REQUEST_COMPLETE_REQ);
106         Collection activeNodes = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(getRouteHeader().getDocumentId());
107         String errorMessage = validateActionRules(actionRequests,activeNodes);
108         if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) {
109             throw new InvalidActionTakenException(errorMessage);
110         }
111 
112         //KULRICE-12283:Modified the logic so this action moves the document to enroute status before attempting to move it to another node if it is initialized or saved
113         if (getRouteHeader().isStateInitiated() || getRouteHeader().isStateSaved()) {
114             markDocumentEnroute(getRouteHeader());
115             getRouteHeader().setRoutedByUserWorkflowId(getPrincipal().getPrincipalId());
116         }
117             RouteNodeInstance startNodeInstance = determineStartNode(activeNodes, movePoint);
118 
119             LOG.debug("Record the move action");
120             Recipient delegator = findDelegatorForActionRequests(actionRequests);
121             ActionTakenValue actionTaken = saveActionTaken(delegator);
122             getActionRequestService().deactivateRequests(actionTaken, actionRequests);
123             notifyActionTaken(actionTaken);
124 
125             // TODO this whole bit is a bit hacky at the moment
126             if (movePoint.getStepsToMove() > 0) {
127                 Set<String> targetNodeNames = new HashSet<String>();
128                 targetNodeNames.add(determineFutureNodeName(startNodeInstance, movePoint));
129 
130         	    final boolean shouldIndex = getRouteHeader().getDocumentType().hasSearchableAttributes() && RouteContext.getCurrentRouteContext().isSearchIndexingRequestedForContext();
131                 String applicationId = routeHeader.getDocumentType().getApplicationId();
132                 DocumentOrchestrationQueue orchestrationQueue = KewApiServiceLocator.getDocumentOrchestrationQueue(
133                         routeHeader.getDocumentId(), applicationId);
134                 org.kuali.rice.kew.api.document.OrchestrationConfig orchestrationConfig =
135                     org.kuali.rice.kew.api.document.OrchestrationConfig.create(actionTaken.getActionTakenId(), targetNodeNames);
136                 //KULRICE-12283: Modified this to pass along two new flags to indicate that acks and FYIs should be deactivated with the move
137                 DocumentProcessingOptions options = DocumentProcessingOptions.create(true, shouldIndex, false, true, true);
138                 orchestrationQueue.orchestrateDocument(routeHeader.getDocumentId(), getPrincipal().getPrincipalId(), orchestrationConfig, options);
139             } else {
140                 String targetNodeName = determineReturnNodeName(startNodeInstance, movePoint);
141                 ReturnToPreviousNodeAction returnAction = new ReturnToPreviousNodeAction(KewApiConstants.ACTION_TAKEN_MOVE_CD, getRouteHeader(), getPrincipal(), annotation, targetNodeName, false);
142                 
143                 returnAction.recordAction();
144             }
145     }
146 
147     private RouteNodeInstance determineStartNode(Collection<RouteNodeInstance> activeNodes, MovePoint movePoint) throws InvalidActionTakenException {
148         RouteNodeInstance startNodeInstance = null;
149         for (RouteNodeInstance nodeInstance : activeNodes)
150         {
151             if (nodeInstance.getName().equals(movePoint.getStartNodeName()))
152             {
153                 if (startNodeInstance != null)
154                 {
155                     throw new InvalidActionTakenException("More than one active node exists with the given name:  " + movePoint.getStartNodeName());
156                 }
157                 startNodeInstance = nodeInstance;
158             }
159         }
160         if (startNodeInstance == null) {
161             throw new InvalidActionTakenException("Could not locate an active node with the given name: " + movePoint.getStartNodeName());
162         }
163         return startNodeInstance;
164     }
165 
166     private String determineFutureNodeName(RouteNodeInstance startNodeInstance, MovePoint movePoint) throws InvalidActionTakenException {
167         return determineFutureNodeName(startNodeInstance.getRouteNode(), movePoint, 0, new HashSet());
168     }
169 
170     private String determineFutureNodeName(RouteNode node, MovePoint movePoint, int currentStep, Set nodesProcessed) throws InvalidActionTakenException {
171         if (nodesProcessed.contains(node.getRouteNodeId())) {
172             throw new InvalidActionTakenException("Detected a cycle at node " + node.getRouteNodeName() + " when attempting to move document.");
173         }
174         nodesProcessed.add(node.getRouteNodeId());
175         if (currentStep == movePoint.getStepsToMove()) {
176             return node.getRouteNodeName();
177         }
178         List nextNodes = node.getNextNodes();
179         if (nextNodes.size() == 0) {
180             throw new InvalidActionTakenException("Could not proceed forward, there are no more nodes in the route.  Halted on step " + currentStep);
181         }
182         if (nextNodes.size() != 1) {
183             throw new InvalidActionTakenException("Cannot move forward in a multi-branch path.  Located "+nextNodes.size()+" branches.  Halted on step " + currentStep);
184         }
185         return determineFutureNodeName((RouteNode)nextNodes.get(0), movePoint, currentStep+1, nodesProcessed);
186     }
187 
188     private String determineReturnNodeName(RouteNodeInstance startNodeInstance, MovePoint movePoint) throws InvalidActionTakenException {
189         return determineReturnNodeName(startNodeInstance.getRouteNode(), movePoint, 0);
190     }
191 
192     private String determineReturnNodeName(RouteNode node, MovePoint movePoint, int currentStep) throws InvalidActionTakenException {
193         if (currentStep == movePoint.getStepsToMove()) {
194             return node.getRouteNodeName();
195         }
196         List previousNodes = node.getPreviousNodes();
197         if (previousNodes.size() == 0) {
198             throw new InvalidActionTakenException("Could not locate the named target node in the document's past route.  Halted on step " + currentStep);
199         }
200         if (previousNodes.size() != 1) {
201             throw new InvalidActionTakenException("Located a multi-branch path, could not proceed backward past this point.  Halted on step " + currentStep);
202         }
203         return determineReturnNodeName((RouteNode)previousNodes.get(0), movePoint, currentStep-1);
204     }
205 
206     private String displayMovePoint(MovePoint movePoint) {
207         return "fromNode="+movePoint.getStartNodeName()+", stepsToMove="+movePoint.getStepsToMove();
208     }
209     //KULRICE-12283: Copied a method from the BlanketApproveAction which moves a document to enroute status so we can perform a move on it
210     protected void markDocumentEnroute(DocumentRouteHeaderValue routeHeader) throws InvalidActionTakenException {
211         String oldStatus = routeHeader.getDocRouteStatus();
212         routeHeader.markDocumentEnroute();
213         String newStatus = routeHeader.getDocRouteStatus();
214         notifyStatusChange(newStatus, oldStatus);
215         KEWServiceLocator.getRouteHeaderService().saveRouteHeader(routeHeader);
216     }
217 }