1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
43
44
45
46
47
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
65
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
93
94
95 public boolean isActionCompatibleRequest(List<ActionRequestValue> requests) {
96
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
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
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
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
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 }