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(DocumentRouteLevelChange documentRouteLevelChange, ProposalInfo proposalInfo) throws Exception {
151 	    // do nothing but allow override
152 	    return true;
153 	}
154 
155 	public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
156 	    boolean success = true;
157 	    // if document is transitioning from INITIATED to SAVED then transaction prevents us from retrieving the proposal
158 	    if (StringUtils.equals(KEWConstants.ROUTE_HEADER_INITIATED_CD, statusChangeEvent.getOldRouteStatus()) && 
159 	            StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, statusChangeEvent.getNewRouteStatus())) {
160 	        // assume the proposal status is already correct
161             success = processCustomRouteStatusSavedStatusChange(statusChangeEvent);
162 	    } else {
163             ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(statusChangeEvent.getRouteHeaderId().toString());
164             
165             // update the proposal state if the proposalState value is not null (allows for clearing of the state)
166             String proposalState = getProposalStateForRouteStatus(proposalInfo.getState(), statusChangeEvent.getNewRouteStatus());
167             updateProposal(statusChangeEvent, proposalState, proposalInfo);
168             success = processCustomRouteStatusChange(statusChangeEvent, proposalInfo);
169 	    }
170         return new ProcessDocReport(success);
171 	}
172 
173 	protected boolean processCustomRouteStatusChange(DocumentRouteStatusChange statusChangeEvent, ProposalInfo proposalInfo) throws Exception {
174 	    // do nothing but allow override
175 	    return true;
176 	}
177 
178     protected boolean processCustomRouteStatusSavedStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
179         // do nothing but allow override
180         return true;
181     }
182 
183 	public List<Long> getDocumentIdsToLock(DocumentLockingEvent arg0) throws Exception {
184 		return null;
185 	}
186 
187     /**
188      * @param currentProposalState - the current state set on the proposal
189      * @param newWorkflowStatusCode - the new route status code that is getting set on the workflow document
190      * @return the proposal state to set or null if the proposal does not need it's state changed
191      */
192     protected String getProposalStateForRouteStatus(String currentProposalState, String newWorkflowStatusCode) {
193         if (StringUtils.equals(ProposalConstants.PROPOSAL_STATE_WITHDRAWN, currentProposalState)) {
194             // if the current proposal state is Withdrawn... don't change the proposal state
195             return null;
196         }
197         if (StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, newWorkflowStatusCode)) {
198             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_SAVED);
199         } else if (KEWConstants.ROUTE_HEADER_ENROUTE_CD.equals(newWorkflowStatusCode)) {
200             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_ENROUTE);
201         } else if (KEWConstants.ROUTE_HEADER_CANCEL_CD .equals(newWorkflowStatusCode)) {
202             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_CANCELLED);
203         } else if (KEWConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(newWorkflowStatusCode)) {
204             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_REJECTED);
205         } else if (KEWConstants.ROUTE_HEADER_PROCESSED_CD.equals(newWorkflowStatusCode)) {
206             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_APPROVED);
207         } else if (KEWConstants.ROUTE_HEADER_EXCEPTION_CD.equals(newWorkflowStatusCode)) {
208             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_EXCEPTION);
209         } else {
210             // no status to set
211             return null;
212         }
213     }
214 
215     /**
216      * Default behavior is to return the <code>newProposalState</code> variable only if it differs from the
217      * <code>currentProposalState</code> value. Otherwise <code>null</code> will be returned.
218      */
219     protected String getProposalStateFromNewState(String currentProposalState, String newProposalState) {
220         if (LOG.isInfoEnabled()) {
221             LOG.info("current proposal state is '" + currentProposalState + "' and new proposal state will be '" + newProposalState + "'");
222         }
223         return getStateFromNewState(currentProposalState, newProposalState);
224     }
225 
226     /**
227      * Default behavior is to return the <code>newState</code> variable only if it differs from the
228      * <code>currentState</code> value. Otherwise <code>null</code> will be returned.
229      */
230     protected String getStateFromNewState(String currentState, String newState) {
231         if (StringUtils.equals(currentState, newState)) {
232             if (LOG.isInfoEnabled()) {
233                 LOG.info("returning null as current state and new state are both '" + currentState + "'");
234             }
235             return null;
236         }
237         return newState;
238     }
239 
240     protected void removeEditAdhocPermissions(String principalId, WorkflowDocument doc) {
241         AttributeSet qualifications = new AttributeSet();
242         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentType());
243         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getAppDocId());
244         KIMServiceLocator.getRoleManagementService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAME, qualifications);       
245     }
246 
247     protected void removeCommentAdhocPermissions(String roleNamespace, String roleName, String principalId, WorkflowDocument doc) {
248         AttributeSet qualifications = new AttributeSet();
249         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentType());
250         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getAppDocId());
251         KIMServiceLocator.getRoleManagementService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAME, qualifications);
252     }
253 
254     protected String getPrincipalIdForSystemUser() {
255         KimPrincipalInfo principal = KIMServiceLocator.getIdentityManagementService().getPrincipalByPrincipalName(StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
256         if (principal == null) {
257             throw new RuntimeException("Cannot find Principal for principal name: " + StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
258         }
259         return principal.getPrincipalId();
260     }
261 
262     protected void updateProposal(IDocumentEvent iDocumentEvent, String proposalState, ProposalInfo proposalInfo) throws Exception {
263         if (LOG.isInfoEnabled()) {
264             LOG.info("Setting state '" + proposalState + "' on Proposal with docId='" + proposalInfo.getWorkflowId() + "' and proposalId='" + proposalInfo.getId() + "'");
265         }
266         boolean requiresSave = false;
267         if (proposalState != null) {
268             proposalInfo.setState(proposalState);
269             requiresSave = true;
270         }
271         requiresSave |= preProcessProposalSave(iDocumentEvent, proposalInfo);
272         if (requiresSave) {
273             getProposalService().updateProposal(proposalInfo.getId(), proposalInfo);
274         }
275     }
276 
277     protected boolean preProcessProposalSave(IDocumentEvent iDocumentEvent, ProposalInfo proposalInfo) {
278         return false;
279     }
280 
281     protected ProposalService getProposalService() {
282         if (this.proposalService == null) {
283             this.proposalService = (ProposalService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/proposal","ProposalService"));
284         }
285         return this.proposalService;
286     }
287 
288 }