View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.student.lum.workflow;
17  import java.util.List;
18  
19  import javax.xml.namespace.QName;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.core.resourceloader.GlobalResourceLoader;
23  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
24  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
25  import org.kuali.rice.kew.dto.ActionRequestDTO;
26  import org.kuali.rice.kew.postprocessor.ActionTakenEvent;
27  import org.kuali.rice.kew.postprocessor.AfterProcessEvent;
28  import org.kuali.rice.kew.postprocessor.BeforeProcessEvent;
29  import org.kuali.rice.kew.postprocessor.DeleteEvent;
30  import org.kuali.rice.kew.postprocessor.DocumentLockingEvent;
31  import org.kuali.rice.kew.postprocessor.DocumentRouteLevelChange;
32  import org.kuali.rice.kew.postprocessor.DocumentRouteStatusChange;
33  import org.kuali.rice.kew.postprocessor.IDocumentEvent;
34  import org.kuali.rice.kew.postprocessor.PostProcessor;
35  import org.kuali.rice.kew.postprocessor.ProcessDocReport;
36  import org.kuali.rice.kew.service.KEWServiceLocator;
37  import org.kuali.rice.kew.service.WorkflowDocument;
38  import org.kuali.rice.kew.util.KEWConstants;
39  import org.kuali.rice.kim.bo.entity.dto.KimPrincipalInfo;
40  import org.kuali.rice.kim.bo.types.dto.AttributeSet;
41  import org.kuali.rice.kim.service.KIMServiceLocator;
42  import org.kuali.rice.student.StudentWorkflowConstants;
43  import org.kuali.rice.student.bo.KualiStudentKimAttributes;
44  import org.kuali.student.common.exceptions.OperationFailedException;
45  import org.kuali.student.common.rice.StudentIdentityConstants;
46  import org.kuali.student.core.proposal.ProposalConstants;
47  import org.kuali.student.core.proposal.dto.ProposalInfo;
48  import org.kuali.student.core.proposal.service.ProposalService;
49  public class KualiStudentPostProcessorBase implements PostProcessor{
50  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiStudentPostProcessorBase.class);
51  
52      private ProposalService proposalService;
53  
54      public ProcessDocReport afterProcess(AfterProcessEvent arg0) throws Exception {
55          return new ProcessDocReport(true);
56  	}
57  
58  	public ProcessDocReport beforeProcess(BeforeProcessEvent arg0) throws Exception {
59          return new ProcessDocReport(true);
60  	}
61  
62  	public ProcessDocReport doActionTaken(ActionTakenEvent actionTakenEvent) throws Exception {
63  		ActionTakenValue actionTaken = KEWServiceLocator.getActionTakenService().findByActionTakenId(actionTakenEvent.getActionTaken().getActionTakenId());
64  		if (actionTaken == null) {
65  		    if (LOG.isInfoEnabled()) {
66  		        LOG.info("Could not find valid ActionTakenValue for doc id '" + actionTakenEvent.getRouteHeaderId() + "'" + 
67  		                ((actionTakenEvent.getActionTaken() == null) ? "" : " for action: " + actionTakenEvent.getActionTaken().getActionTakenLabel()));
68  		    }
69  		    actionTaken = actionTakenEvent.getActionTaken();
70  		}
71  		boolean success = true;
72  		// on a save action we may not have access to the proposal object because the transaction may not have committed
73  		if (!StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, actionTaken.getActionTaken())) {
74              ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(actionTakenEvent.getRouteHeaderId().toString());
75              if (actionTaken == null) {
76                  throw new OperationFailedException("No action taken found for document id " + actionTakenEvent.getRouteHeaderId());
77              }
78      	    if (StringUtils.equals(KEWConstants.ACTION_TAKEN_SU_DISAPPROVED_CD, actionTaken.getActionTaken())) {
79      	        // the custom method below is needed for the unique problem of the states being set for a Withdraw action in KS
80      	        processSuperUserDisapproveActionTaken(actionTakenEvent, actionTaken, proposalInfo);
81      	    }
82              // only attempt to remove the adhoc permission if the action taken was not an adhoc revocation 
83      	    else if (!StringUtils.equals(KEWConstants.ACTION_TAKEN_ADHOC_REVOKED_CD, actionTaken.getActionTaken())) {
84      			for (ActionRequestValue actionRequest : actionTaken.getActionRequests()) {
85      		        if (actionRequest.isAdHocRequest() && actionRequest.isUserRequest()) {
86      		            processActionTakenOnAdhocRequest(actionTakenEvent, actionRequest);
87      		        }
88      	        }
89              }
90              success = processCustomActionTaken(actionTakenEvent, actionTaken, proposalInfo);
91  		} else {
92  		    success = processCustomSaveActionTaken(actionTakenEvent, actionTaken);
93  		}
94          return new ProcessDocReport(success);
95  	}
96  
97      protected boolean processCustomActionTaken(ActionTakenEvent actionTakenEvent, ActionTakenValue actionTaken, ProposalInfo proposalInfo) throws Exception {
98          // do nothing
99          return true;
100     }
101 
102     protected boolean processCustomSaveActionTaken(ActionTakenEvent actionTakenEvent, ActionTakenValue actionTaken) throws Exception {
103         // do nothing
104         return true;
105     }
106 
107     protected void processActionTakenOnAdhocRequest(ActionTakenEvent actionTakenEvent, ActionRequestValue actionRequestValue) throws Exception {
108         WorkflowDocument doc = new WorkflowDocument(getPrincipalIdForSystemUser(), actionTakenEvent.getRouteHeaderId());
109         LOG.info("Clearing EDIT permissions added via adhoc requests to principal id: " + actionRequestValue.getPrincipalId());
110         removeEditAdhocPermissions(actionRequestValue.getPrincipalId(), doc);
111     }
112 
113     protected void processSuperUserDisapproveActionTaken(ActionTakenEvent actionTakenEvent, ActionTakenValue actionTaken, ProposalInfo proposalInfo) throws Exception {
114         LOG.info("Action taken was 'Super User Disapprove' which is a 'Withdraw' in Kuali Student");
115         LOG.info("Will set proposal state to '" + ProposalConstants.PROPOSAL_STATE_WITHDRAWN + "'");
116         updateProposal(actionTakenEvent, ProposalConstants.PROPOSAL_STATE_WITHDRAWN, proposalInfo);
117         processWithdrawActionTaken(actionTakenEvent, proposalInfo);
118 	}
119 
120     protected void processWithdrawActionTaken(ActionTakenEvent actionTakenEvent, ProposalInfo proposalInfo) throws Exception {
121         // do nothing but allow for child classes to override
122     }
123 
124     public ProcessDocReport doDeleteRouteHeader(DeleteEvent arg0) throws Exception {
125         return new ProcessDocReport(true);
126 	}
127 
128 	public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange documentRouteLevelChange) throws Exception {
129         ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(documentRouteLevelChange.getRouteHeaderId().toString());
130 
131 		// if this is the initial route then clear only edit permissions as per KSLUM-192
132 		if (StringUtils.equals(StudentWorkflowConstants.DEFAULT_WORKFLOW_DOCUMENT_START_NODE_NAME,documentRouteLevelChange.getOldNodeName())) {
133 			// remove edit perm for all adhoc action requests to a user for the route node we just exited
134 	        WorkflowDocument doc = new WorkflowDocument(getPrincipalIdForSystemUser(), documentRouteLevelChange.getRouteHeaderId());
135 			for (ActionRequestDTO actionRequestDTO : doc.getActionRequests()) {
136 				if (actionRequestDTO.isAdHocRequest() && actionRequestDTO.isUserRequest() && 
137 						StringUtils.equals(documentRouteLevelChange.getOldNodeName(),actionRequestDTO.getNodeName())) {
138 					LOG.info("Clearing EDIT permissions added via adhoc requests to principal id: " + actionRequestDTO.getPrincipalId());
139 					removeEditAdhocPermissions(actionRequestDTO.getPrincipalId(), doc);
140 				}
141 	        }
142 		}
143 		else {
144 			LOG.warn("Will not clear any permissions added via adhoc requests");
145 		}
146 		boolean success = processCustomRouteLevelChange(documentRouteLevelChange, proposalInfo);
147 		return new ProcessDocReport(success);
148 	}
149 
150 	protected boolean processCustomRouteLevelChange(
151 			DocumentRouteLevelChange documentRouteLevelChange,
152 			ProposalInfo proposalInfo) throws Exception {
153 		//Update the proposal with the new node name
154 		proposalInfo.getAttributes().put("workflowNode", documentRouteLevelChange.getNewNodeName());
155 		getProposalService().updateProposal(proposalInfo.getId(), proposalInfo);
156 		return true;
157 	}
158 
159 	public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
160 	    boolean success = true;
161 	    // if document is transitioning from INITIATED to SAVED then transaction prevents us from retrieving the proposal
162 	    if (StringUtils.equals(KEWConstants.ROUTE_HEADER_INITIATED_CD, statusChangeEvent.getOldRouteStatus()) && 
163 	            StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, statusChangeEvent.getNewRouteStatus())) {
164 	        // assume the proposal status is already correct
165             success = processCustomRouteStatusSavedStatusChange(statusChangeEvent);
166 	    } else {
167             ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(statusChangeEvent.getRouteHeaderId().toString());
168             
169             // update the proposal state if the proposalState value is not null (allows for clearing of the state)
170             String proposalState = getProposalStateForRouteStatus(proposalInfo.getState(), statusChangeEvent.getNewRouteStatus());
171             updateProposal(statusChangeEvent, proposalState, proposalInfo);
172             success = processCustomRouteStatusChange(statusChangeEvent, proposalInfo);
173 	    }
174         return new ProcessDocReport(success);
175 	}
176 
177 	protected boolean processCustomRouteStatusChange(DocumentRouteStatusChange statusChangeEvent, ProposalInfo proposalInfo) throws Exception {
178 	    // do nothing but allow override
179 	    return true;
180 	}
181 
182     protected boolean processCustomRouteStatusSavedStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
183         // do nothing but allow override
184         return true;
185     }
186 
187 	public List<Long> getDocumentIdsToLock(DocumentLockingEvent arg0) throws Exception {
188 		return null;
189 	}
190 
191     /**
192      * @param currentProposalState - the current state set on the proposal
193      * @param newWorkflowStatusCode - the new route status code that is getting set on the workflow document
194      * @return the proposal state to set or null if the proposal does not need it's state changed
195      */
196     protected String getProposalStateForRouteStatus(String currentProposalState, String newWorkflowStatusCode) {
197         if (StringUtils.equals(ProposalConstants.PROPOSAL_STATE_WITHDRAWN, currentProposalState)) {
198             // if the current proposal state is Withdrawn... don't change the proposal state
199             return null;
200         }
201         if (StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, newWorkflowStatusCode)) {
202             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_SAVED);
203         } else if (KEWConstants.ROUTE_HEADER_ENROUTE_CD.equals(newWorkflowStatusCode)) {
204             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_ENROUTE);
205         } else if (KEWConstants.ROUTE_HEADER_CANCEL_CD .equals(newWorkflowStatusCode)) {
206             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_CANCELLED);
207         } else if (KEWConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(newWorkflowStatusCode)) {
208             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_REJECTED);
209         } else if (KEWConstants.ROUTE_HEADER_PROCESSED_CD.equals(newWorkflowStatusCode)) {
210             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_APPROVED);
211         } else if (KEWConstants.ROUTE_HEADER_EXCEPTION_CD.equals(newWorkflowStatusCode)) {
212             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_EXCEPTION);
213         } else {
214             // no status to set
215             return null;
216         }
217     }
218 
219     /**
220      * Default behavior is to return the <code>newProposalState</code> variable only if it differs from the
221      * <code>currentProposalState</code> value. Otherwise <code>null</code> will be returned.
222      */
223     protected String getProposalStateFromNewState(String currentProposalState, String newProposalState) {
224         if (LOG.isInfoEnabled()) {
225             LOG.info("current proposal state is '" + currentProposalState + "' and new proposal state will be '" + newProposalState + "'");
226         }
227         return getStateFromNewState(currentProposalState, newProposalState);
228     }
229 
230     /**
231      * Default behavior is to return the <code>newState</code> variable only if it differs from the
232      * <code>currentState</code> value. Otherwise <code>null</code> will be returned.
233      */
234     protected String getStateFromNewState(String currentState, String newState) {
235         if (StringUtils.equals(currentState, newState)) {
236             if (LOG.isInfoEnabled()) {
237                 LOG.info("returning null as current state and new state are both '" + currentState + "'");
238             }
239             return null;
240         }
241         return newState;
242     }
243 
244     protected void removeEditAdhocPermissions(String principalId, WorkflowDocument doc) {
245         AttributeSet qualifications = new AttributeSet();
246         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentType());
247         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getAppDocId());
248         KIMServiceLocator.getRoleManagementService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAME, qualifications);       
249     }
250 
251     protected void removeCommentAdhocPermissions(String roleNamespace, String roleName, String principalId, WorkflowDocument doc) {
252         AttributeSet qualifications = new AttributeSet();
253         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentType());
254         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getAppDocId());
255         KIMServiceLocator.getRoleManagementService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAME, qualifications);
256     }
257 
258     protected String getPrincipalIdForSystemUser() {
259         KimPrincipalInfo principal = KIMServiceLocator.getIdentityManagementService().getPrincipalByPrincipalName(StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
260         if (principal == null) {
261             throw new RuntimeException("Cannot find Principal for principal name: " + StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
262         }
263         return principal.getPrincipalId();
264     }
265 
266     protected void updateProposal(IDocumentEvent iDocumentEvent, String proposalState, ProposalInfo proposalInfo) throws Exception {
267         if (LOG.isInfoEnabled()) {
268             LOG.info("Setting state '" + proposalState + "' on Proposal with docId='" + proposalInfo.getWorkflowId() + "' and proposalId='" + proposalInfo.getId() + "'");
269         }
270         boolean requiresSave = false;
271         if (proposalState != null) {
272             proposalInfo.setState(proposalState);
273             requiresSave = true;
274         }
275         requiresSave |= preProcessProposalSave(iDocumentEvent, proposalInfo);
276         if (requiresSave) {
277             getProposalService().updateProposal(proposalInfo.getId(), proposalInfo);
278         }
279     }
280 
281     protected boolean preProcessProposalSave(IDocumentEvent iDocumentEvent, ProposalInfo proposalInfo) {
282         return false;
283     }
284 
285     protected ProposalService getProposalService() {
286         if (this.proposalService == null) {
287             this.proposalService = (ProposalService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/proposal","ProposalService"));
288         }
289         return this.proposalService;
290     }
291 
292 }