View Javadoc

1   /**
2    * 
3    */
4   package org.kuali.student.lum.workflow;
5   
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import javax.xml.namespace.QName;
10  
11  import org.apache.commons.lang.StringUtils;
12  import org.kuali.rice.core.resourceloader.GlobalResourceLoader;
13  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
14  import org.kuali.rice.kew.postprocessor.ActionTakenEvent;
15  import org.kuali.rice.kew.postprocessor.DocumentRouteStatusChange;
16  import org.kuali.rice.kew.postprocessor.IDocumentEvent;
17  import org.kuali.rice.kew.util.KEWConstants;
18  import org.kuali.student.common.dto.DtoConstants;
19  import org.kuali.student.common.exceptions.DoesNotExistException;
20  import org.kuali.student.common.exceptions.OperationFailedException;
21  import org.kuali.student.core.proposal.dto.ProposalInfo;
22  import org.kuali.student.core.statement.dto.ReqComponentInfo;
23  import org.kuali.student.core.statement.dto.StatementTreeViewInfo;
24  import org.kuali.student.lum.course.dto.CourseInfo;
25  import org.kuali.student.lum.course.service.CourseService;
26  import org.springframework.transaction.annotation.Transactional;
27  
28  /**
29   * A base post processor class for Course document types in Workflow.
30   *
31   */
32  @Transactional(readOnly=true, rollbackFor={Throwable.class})
33  public class CoursePostProcessorBase extends KualiStudentPostProcessorBase {
34      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CoursePostProcessorBase.class);
35  
36      private CourseService courseService;
37      private CourseStateChangeServiceImpl courseStateChangeService;
38      
39      @Override
40      protected void processWithdrawActionTaken(ActionTakenEvent actionTakenEvent, ProposalInfo proposalInfo) throws Exception {
41          LOG.info("Will set CLU state to '" + DtoConstants.STATE_NOT_APPROVED + "'");
42          CourseInfo courseInfo = getCourseService().getCourse(getCourseId(proposalInfo));
43          updateCourse(actionTakenEvent, DtoConstants.STATE_NOT_APPROVED, courseInfo);
44      }
45  
46      @Override
47      protected boolean processCustomActionTaken(ActionTakenEvent actionTakenEvent, ActionTakenValue actionTaken, ProposalInfo proposalInfo) throws Exception {
48          String cluId = getCourseId(proposalInfo);
49          CourseInfo courseInfo = getCourseService().getCourse(cluId);
50          
51          updateCourse(actionTakenEvent, null, courseInfo);
52          return true;
53      }
54  
55      @Override
56      protected boolean processCustomRouteStatusChange(DocumentRouteStatusChange statusChangeEvent, ProposalInfo proposalInfo) throws Exception {
57          // update the course state if the cluState value is not null (allows for clearing of the state)
58          String courseId = getCourseId(proposalInfo);
59          String prevEndTermAtpId = proposalInfo.getAttributes().get("prevEndTerm");
60          CourseInfo courseInfo = getCourseService().getCourse(courseId);
61          String courseState = getCluStateForRouteStatus(courseInfo.getState(), statusChangeEvent.getNewRouteStatus());
62          //Use the state change service to update to active and update preceding versions  
63          if(DtoConstants.STATE_ACTIVE.equals(courseState)){
64          	//Change the state using the effective date as the version start date
65          	getCourseStateChangeService().changeState(courseId, courseState, prevEndTermAtpId);
66          }else{
67          	updateCourse(statusChangeEvent, courseState, courseInfo);
68          }
69          return true;
70      }
71  
72      protected String getCourseId(ProposalInfo proposalInfo) throws OperationFailedException {
73          if (proposalInfo.getProposalReference().size() != 1) {
74              LOG.error("Found " + proposalInfo.getProposalReference().size() + " CLU objects linked to proposal with proposalId='" + proposalInfo.getId() + "'. Must have exactly 1 linked.");
75              throw new OperationFailedException("Found " + proposalInfo.getProposalReference().size() + " CLU objects linked to proposal with docId='" + proposalInfo.getWorkflowId() + "' and proposalId='" + proposalInfo.getId() + "'. Must have exactly 1 linked.");
76          }
77          return proposalInfo.getProposalReference().get(0);
78      }
79  
80      /**
81       * @param currentCluState - the current state set on the CLU
82       * @param newWorkflowStatusCode - the new route status code that is getting set on the workflow document
83       * @return the CLU state to set or null if the CLU does not need it's state changed
84       */
85      protected String getCluStateForRouteStatus(String currentCluState, String newWorkflowStatusCode) {
86          if (StringUtils.equals(KEWConstants.ROUTE_HEADER_SAVED_CD, newWorkflowStatusCode)) {
87              return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_DRAFT);
88          } else if (KEWConstants.ROUTE_HEADER_CANCEL_CD .equals(newWorkflowStatusCode)) {
89              return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_NOT_APPROVED);
90          } else if (KEWConstants.ROUTE_HEADER_ENROUTE_CD.equals(newWorkflowStatusCode)) {
91              return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_DRAFT);
92          } else if (KEWConstants.ROUTE_HEADER_DISAPPROVED_CD.equals(newWorkflowStatusCode)) {
93              /* current requirements state that on a Withdraw (which is a KEW Disapproval) the 
94               * CLU state should be submitted so no special handling required here
95               */
96              return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_NOT_APPROVED);
97          } else if (KEWConstants.ROUTE_HEADER_PROCESSED_CD.equals(newWorkflowStatusCode)) {
98              return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_ACTIVE);
99          } else if (KEWConstants.ROUTE_HEADER_EXCEPTION_CD.equals(newWorkflowStatusCode)) {
100             return getCourseStateFromNewState(currentCluState, DtoConstants.STATE_DRAFT);
101         } else {
102             // no status to set
103             return null;
104         }
105     }
106 
107     /**
108      * Default behavior is to return the <code>newCluState</code> variable only if it differs from the
109      * <code>currentCluState</code> value. Otherwise <code>null</code> will be returned.
110      */
111     protected String getCourseStateFromNewState(String currentCourseState, String newCourseState) {
112         if (LOG.isInfoEnabled()) {
113             LOG.info("current CLU state is '" + currentCourseState + "' and new CLU state will be '" + newCourseState + "'");
114         }
115         return getStateFromNewState(currentCourseState, newCourseState);
116     }
117 
118     @Transactional(readOnly=false,noRollbackFor={DoesNotExistException.class},rollbackFor={Throwable.class})
119     protected void updateCourse(IDocumentEvent iDocumentEvent, String courseState, CourseInfo courseInfo) throws Exception {
120         // only change the state if the course is not currently set to that state
121         boolean requiresSave = false;
122         if (courseState != null) {
123             if (LOG.isInfoEnabled()) {
124                 LOG.info("Setting state '" + courseState + "' on CLU with cluId='" + courseInfo.getId() + "'");
125             }
126             courseInfo.setState(courseState);
127             requiresSave = true;
128         }
129         if (LOG.isInfoEnabled()) {
130             LOG.info("Running preProcessCluSave with cluId='" + courseInfo.getId() + "'");
131         }
132         requiresSave |= preProcessCourseSave(iDocumentEvent, courseInfo);
133 
134         if (requiresSave) {
135             getCourseService().updateCourse(courseInfo);
136             
137             //For a newly approved course (w/no prior active versions), make the new course the current version.
138             if (DtoConstants.STATE_ACTIVE.equals(courseState) && courseInfo.getVersionInfo().getCurrentVersionStart() == null){
139             	// TODO: set states of other approved courses to superseded                
140                 
141             	// if current version's state is not active then we can set this course as the active course
142             	//if (!DtoConstants.STATE_ACTIVE.equals(getCourseService().getCourse(getCourseService().getCurrentVersion(CourseServiceConstants.COURSE_NAMESPACE_URI, courseInfo.getVersionInfo().getVersionIndId()).getId()).getState())) { 
143             		getCourseService().setCurrentCourseVersion(courseInfo.getId(), null);
144             	//}
145             }
146             
147             List<StatementTreeViewInfo> statementTreeViewInfos = courseService.getCourseStatements(courseInfo.getId(), null, null);
148             if(statementTreeViewInfos!=null){
149 	            statementTreeViewInfoStateSetter(courseInfo.getState(), statementTreeViewInfos.iterator());
150 	            
151 	            for(Iterator<StatementTreeViewInfo> it = statementTreeViewInfos.iterator(); it.hasNext();)
152 	        		courseService.updateCourseStatement(courseInfo.getId(), it.next());
153             }
154         }
155         
156     }
157 
158     protected boolean preProcessCourseSave(IDocumentEvent iDocumentEvent, CourseInfo courseInfo) {
159         return false;
160     }
161 
162     protected CourseService getCourseService() {
163         if (this.courseService == null) {
164             this.courseService = (CourseService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/course","CourseService")); 
165         }
166         return this.courseService;
167     }
168     protected CourseStateChangeServiceImpl getCourseStateChangeService() {
169         if (this.courseStateChangeService == null) {
170             this.courseStateChangeService = new CourseStateChangeServiceImpl();
171             this.courseStateChangeService.setCourseService(getCourseService());
172         }
173         return this.courseStateChangeService;
174     }    
175     /*
176      * Recursively set state for StatementTreeViewInfo
177      * TODO: We are not able to reuse the code in CourseStateUtil for dependency reason.
178      */   
179     public void statementTreeViewInfoStateSetter(String courseState, Iterator<StatementTreeViewInfo> itr) {
180     	while(itr.hasNext()) {
181         	StatementTreeViewInfo statementTreeViewInfo = (StatementTreeViewInfo)itr.next();
182         	statementTreeViewInfo.setState(courseState);
183         	List<ReqComponentInfo> reqComponents = statementTreeViewInfo.getReqComponents();
184         	for(Iterator<ReqComponentInfo> it = reqComponents.iterator(); it.hasNext();)
185         		it.next().setState(courseState);
186 
187         	statementTreeViewInfoStateSetter(courseState, statementTreeViewInfo.getStatements().iterator());
188         }
189     }
190 }