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.LinkedHashMap;
18  import java.util.List;
19  import java.util.Map;
20  
21  import javax.xml.namespace.QName;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
25  import org.kuali.rice.kew.api.KewApiConstants;
26  import org.kuali.rice.kew.api.KewApiServiceLocator;
27  import org.kuali.rice.kew.api.WorkflowDocument;
28  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
29  import org.kuali.rice.kew.api.action.ActionRequest;
30  import org.kuali.rice.kew.api.action.ActionTaken;
31  import org.kuali.rice.kew.api.action.ActionType;
32  import org.kuali.rice.kew.api.document.WorkflowDocumentService;
33  import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
34  import org.kuali.rice.kew.framework.postprocessor.AfterProcessEvent;
35  import org.kuali.rice.kew.framework.postprocessor.BeforeProcessEvent;
36  import org.kuali.rice.kew.framework.postprocessor.DeleteEvent;
37  import org.kuali.rice.kew.framework.postprocessor.DocumentLockingEvent;
38  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
39  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
40  import org.kuali.rice.kew.framework.postprocessor.IDocumentEvent;
41  import org.kuali.rice.kew.framework.postprocessor.PostProcessor;
42  import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
43  import org.kuali.rice.kim.api.identity.principal.Principal;
44  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
45  import org.kuali.rice.student.StudentWorkflowConstants;
46  import org.kuali.rice.student.bo.KualiStudentKimAttributes;
47  import org.kuali.student.r1.common.rice.StudentIdentityConstants;
48  import org.kuali.student.r1.core.proposal.ProposalConstants;
49  import org.kuali.student.r2.common.exceptions.OperationFailedException;
50  import org.kuali.student.r2.common.util.AttributeHelper;
51  import org.kuali.student.r2.common.util.ContextUtils;
52  import org.kuali.student.r2.core.proposal.dto.ProposalInfo;
53  import org.kuali.student.r2.core.proposal.service.ProposalService;
54  
55  public class KualiStudentPostProcessorBase implements PostProcessor{
56  
57  
58  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiStudentPostProcessorBase.class);
59  
60      private ProposalService proposalService;
61  
62      @Override
63      public ProcessDocReport afterProcess(AfterProcessEvent arg0) throws Exception {
64          return new ProcessDocReport(true);
65  	}
66  
67      @Override
68  	public ProcessDocReport beforeProcess(BeforeProcessEvent arg0) throws Exception {
69          return new ProcessDocReport(true);
70  	}
71  
72  	@Override
73  	public ProcessDocReport doActionTaken(ActionTakenEvent actionTakenEvent) throws Exception {
74  	    boolean success = true;
75          ActionTaken actionTaken = actionTakenEvent.getActionTaken();
76          String actionTakeCode = actionTakenEvent.getActionTaken().getActionTaken().getCode();
77  		// on a save action we may not have access to the proposal object because the transaction may not have committed
78  		if (!StringUtils.equals(KewApiConstants.ROUTE_HEADER_SAVED_CD, actionTakeCode)) {
79              ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(actionTakenEvent.getDocumentId().toString(), ContextUtils.getContextInfo());
80              if (actionTaken == null) {
81                  throw new OperationFailedException("No action taken found for document id " + actionTakenEvent.getDocumentId());
82              }
83      	    if (StringUtils.equals(KewApiConstants.ACTION_TAKEN_SU_DISAPPROVED_CD, actionTakeCode)) {
84      	        // the custom method below is needed for the unique problem of the states being set for a Withdraw action in KS
85      	        processSuperUserDisapproveActionTaken(actionTakenEvent, actionTaken, proposalInfo);
86      	    }
87              // only attempt to remove the adhoc permission if the action taken was not an adhoc revocation
88      	    else if (!StringUtils.equals(KewApiConstants.ACTION_TAKEN_ADHOC_REVOKED_CD, actionTakeCode)) {
89                  List<ActionRequest> actionRequests = getWorkflowDocumentService().getRootActionRequests(actionTakenEvent.getDocumentId());
90       			for (ActionRequest actionRequest : actionRequests) {
91      		        if (actionRequest.isAdHocRequest() && actionRequest.isUserRequest()) {
92      		            processActionTakenOnAdhocRequest(actionTakenEvent, actionRequest);
93      		        }
94      	        }
95              }
96              success = processCustomActionTaken(actionTakenEvent, actionTaken, proposalInfo);
97  		} else {
98  		    success = processCustomSaveActionTaken(actionTakenEvent, actionTaken);
99  		}
100         return new ProcessDocReport(success);
101 	}
102 
103     @Override
104     public ProcessDocReport afterActionTaken(ActionType actionPerformed, ActionTakenEvent event) throws Exception {
105         return new ProcessDocReport(Boolean.TRUE);
106     }
107 
108     protected boolean processCustomActionTaken(ActionTakenEvent actionTakenEvent, ActionTaken actionTaken, ProposalInfo proposalInfo) throws Exception {
109         // do nothing
110         return true;
111     }
112 
113     protected boolean processCustomSaveActionTaken(ActionTakenEvent actionTakenEvent, ActionTaken actionTaken) throws Exception {
114         // do nothing
115         return true;
116     }
117 
118     protected void processActionTakenOnAdhocRequest(ActionTakenEvent actionTakenEvent, ActionRequest actionRequest) throws Exception {
119         ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(actionTakenEvent.getDocumentId(), ContextUtils.getContextInfo());
120         WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForSystemUser(), proposalInfo.getType());
121         LOG.info("Clearing EDIT permissions added via adhoc requests to principal id: " + actionRequest.getPrincipalId());
122         removeEditAdhocPermissions(actionRequest.getPrincipalId(), doc);
123     }
124 
125     protected void processSuperUserDisapproveActionTaken(ActionTakenEvent actionTakenEvent, ActionTaken actionTaken, ProposalInfo proposalInfo) throws Exception {
126         LOG.info("Action taken was 'Super User Disapprove' which is a 'Withdraw' in Kuali Student");
127         LOG.info("Will set proposal state to '" + ProposalConstants.PROPOSAL_STATE_WITHDRAWN + "'");
128         updateProposal(actionTakenEvent, ProposalConstants.PROPOSAL_STATE_WITHDRAWN, proposalInfo);
129         processWithdrawActionTaken(actionTakenEvent, proposalInfo);
130 	}
131 
132     protected void processWithdrawActionTaken(ActionTakenEvent actionTakenEvent, ProposalInfo proposalInfo) throws Exception {
133         // do nothing but allow for child classes to override
134     }
135 
136     @Override
137     public ProcessDocReport doDeleteRouteHeader(DeleteEvent arg0) throws Exception {
138         return new ProcessDocReport(true);
139     }
140 
141     @Override
142     public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange documentRouteLevelChange) throws Exception {
143         ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(documentRouteLevelChange.getDocumentId(), ContextUtils.getContextInfo());
144 
145         // if this is the initial route then clear only edit permissions as per KSLUM-192
146         if (StringUtils.equals(StudentWorkflowConstants.DEFAULT_WORKFLOW_DOCUMENT_START_NODE_NAME,documentRouteLevelChange.getOldNodeName())) {
147             // remove edit perm for all adhoc action requests to a user for the route node we just exited
148             WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForSystemUser(), proposalInfo.getType()/*documentRouteLevelChange.getDocumentId()*/);
149                 // TODO: evaluate group or role level changes by not using isUserRequest()
150             for (ActionRequest actionRequest : doc.getRootActionRequests()) {
151                 if (actionRequest.isAdHocRequest() && actionRequest.isUserRequest() &&
152                         StringUtils.equals(documentRouteLevelChange.getOldNodeName(),actionRequest.getNodeName())) {
153                     LOG.info("Clearing EDIT permissions added via adhoc requests to principal id: " + actionRequest.getPrincipalId());
154                     removeEditAdhocPermissions(actionRequest.getPrincipalId(), doc);
155                 }
156             }
157         }
158         else {
159             LOG.warn("Will not clear any permissions added via adhoc requests");
160         }
161         boolean success = processCustomRouteLevelChange(documentRouteLevelChange, proposalInfo);
162         return new ProcessDocReport(success);
163 	}
164     
165 	protected boolean processCustomRouteLevelChange(
166 			DocumentRouteLevelChange documentRouteLevelChange,
167 			ProposalInfo proposalInfo) throws Exception {
168 		//Update the proposal with the new node name
169 		new AttributeHelper (proposalInfo.getAttributes()).put("workflowNode", documentRouteLevelChange.getNewNodeName());
170 		getProposalService().updateProposal(proposalInfo.getId(), proposalInfo, ContextUtils.getContextInfo());
171         return true;
172     }
173     
174     @Override
175     public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange documentRouteStatusChange) throws Exception {
176         boolean success = true;
177         // if document is transitioning from INITIATED to SAVED then transaction prevents us from retrieving the proposal
178         if (StringUtils.equals(KewApiConstants.ROUTE_HEADER_INITIATED_CD, documentRouteStatusChange.getOldRouteStatus()) && 
179                 StringUtils.equals(KewApiConstants.ROUTE_HEADER_SAVED_CD, documentRouteStatusChange.getNewRouteStatus())) {
180             // assume the proposal status is already correct
181             success = processCustomRouteStatusSavedStatusChange(documentRouteStatusChange);
182         } else {
183             ProposalInfo proposalInfo = getProposalService().getProposalByWorkflowId(documentRouteStatusChange.getDocumentId(), ContextUtils.getContextInfo());
184             
185             // update the proposal state if the proposalState value is not null (allows for clearing of the state)
186             String proposalState = getProposalStateForRouteStatus(proposalInfo.getState(), documentRouteStatusChange.getNewRouteStatus());
187             updateProposal(documentRouteStatusChange, proposalState, proposalInfo);
188             success = processCustomRouteStatusChange(documentRouteStatusChange, proposalInfo);
189         }
190         return new ProcessDocReport(success);
191     } 
192    
193 	protected boolean processCustomRouteStatusChange(DocumentRouteStatusChange statusChangeEvent, ProposalInfo proposalInfo) throws Exception {
194 	    // do nothing but allow override
195 	    return true;
196 	}
197 
198     protected boolean processCustomRouteStatusSavedStatusChange(DocumentRouteStatusChange statusChangeEvent) throws Exception {
199         // do nothing but allow override
200         return true;
201     }
202 
203     @Override
204 	public List<String> getDocumentIdsToLock(DocumentLockingEvent arg0) throws Exception {
205 		return null;
206 	}
207 
208     /**
209      * @param currentProposalState - the current state set on the proposal
210      * @param newWorkflowStatusCode - the new route status code that is getting set on the workflow document
211      * @return the proposal state to set or null if the proposal does not need it's state changed
212      */
213     protected String getProposalStateForRouteStatus(String currentProposalState, String newWorkflowStatusCode) {
214         if (StringUtils.equals(ProposalConstants.PROPOSAL_STATE_WITHDRAWN, currentProposalState)) {
215             // if the current proposal state is Withdrawn... don't change the proposal state
216             return null;
217         }
218         if (StringUtils.equals(KewApiConstants.ROUTE_HEADER_SAVED_CD, newWorkflowStatusCode)) {
219             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_SAVED);
220         } else if (KewApiConstants.ROUTE_HEADER_ENROUTE_CD.equals(newWorkflowStatusCode)) {
221             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_ENROUTE);
222         } else if (KewApiConstants.ROUTE_HEADER_CANCEL_CD .equals(newWorkflowStatusCode)) {
223             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_CANCELLED);
224         } else if (KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(newWorkflowStatusCode)) {
225             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_REJECTED);
226         } else if (KewApiConstants.ROUTE_HEADER_PROCESSED_CD.equals(newWorkflowStatusCode)) {
227             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_APPROVED);
228         } else if (KewApiConstants.ROUTE_HEADER_EXCEPTION_CD.equals(newWorkflowStatusCode)) {
229             return getProposalStateFromNewState(currentProposalState, ProposalConstants.PROPOSAL_STATE_EXCEPTION);
230         } else {
231             // no status to set
232             return null;
233         }
234     }
235 
236     /**
237      * Default behavior is to return the <code>newProposalState</code> variable only if it differs from the
238      * <code>currentProposalState</code> value. Otherwise <code>null</code> will be returned.
239      */
240     protected String getProposalStateFromNewState(String currentProposalState, String newProposalState) {
241         if (LOG.isInfoEnabled()) {
242             LOG.info("current proposal state is '" + currentProposalState + "' and new proposal state will be '" + newProposalState + "'");
243         }
244         return getStateFromNewState(currentProposalState, newProposalState);
245     }
246 
247     /**
248      * Default behavior is to return the <code>newState</code> variable only if it differs from the
249      * <code>currentState</code> value. Otherwise <code>null</code> will be returned.
250      */
251     protected String getStateFromNewState(String currentState, String newState) {
252         if (StringUtils.equals(currentState, newState)) {
253             if (LOG.isInfoEnabled()) {
254                 LOG.info("returning null as current state and new state are both '" + currentState + "'");
255             }
256             return null;
257         }
258         return newState;
259     }
260 
261     protected void removeEditAdhocPermissions(String principalId, WorkflowDocument doc) {
262         Map<String,String> qualifications = new LinkedHashMap<String,String>();
263         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentTypeName());
264         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getApplicationDocumentId());
265         KimApiServiceLocator.getRoleService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_EDIT_PERMISSIONS_ROLE_NAME, qualifications);
266     }
267 
268     protected void removeCommentAdhocPermissions(String roleNamespace, String roleName, String principalId, WorkflowDocument doc) {
269         Map<String,String> qualifications = new LinkedHashMap<String,String>();
270         qualifications.put(KualiStudentKimAttributes.DOCUMENT_TYPE_NAME,doc.getDocumentTypeName());
271         qualifications.put(KualiStudentKimAttributes.QUALIFICATION_DATA_ID,doc.getApplicationDocumentId());
272         KimApiServiceLocator.getRoleService().removePrincipalFromRole(principalId, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAMESPACE, StudentWorkflowConstants.ROLE_NAME_ADHOC_ADD_COMMENT_PERMISSIONS_ROLE_NAME, qualifications);
273     }
274 
275     protected String getPrincipalIdForSystemUser() {
276         Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
277         if (principal == null) {
278             throw new RuntimeException("Cannot find Principal for principal name: " + StudentIdentityConstants.SYSTEM_USER_PRINCIPAL_NAME);
279         }
280         return principal.getPrincipalId();
281     }
282 
283     protected void updateProposal(IDocumentEvent iDocumentEvent, String proposalState, ProposalInfo proposalInfo) throws Exception {
284         if (LOG.isInfoEnabled()) {
285             LOG.info("Setting state '" + proposalState + "' on Proposal with docId='" + proposalInfo.getWorkflowId() + "' and proposalId='" + proposalInfo.getId() + "'");
286         }
287         boolean requiresSave = false;
288         if (proposalState != null) {
289             proposalInfo.setState(proposalState);
290             requiresSave = true;
291         }
292         requiresSave |= preProcessProposalSave(iDocumentEvent, proposalInfo);
293         if (requiresSave) {
294             getProposalService().updateProposal(proposalInfo.getId(), proposalInfo, ContextUtils.getContextInfo());
295         }
296     }
297 
298     protected boolean preProcessProposalSave(IDocumentEvent iDocumentEvent, ProposalInfo proposalInfo) {
299         return false;
300     }
301 
302     protected ProposalService getProposalService() {
303         if (this.proposalService == null) {
304             this.proposalService = (ProposalService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/proposal","ProposalService"));
305         }
306         return this.proposalService;
307     }
308 
309     protected WorkflowDocumentService getWorkflowDocumentService() {
310 		return KewApiServiceLocator.getWorkflowDocumentService();
311 	}
312 }