View Javadoc
1   package org.kuali.student.lum.workflow;
2   
3   import org.kuali.student.r1.core.statement.dto.StatementTreeViewInfo;
4   import org.kuali.student.r1.lum.course.service.CourseServiceConstants;
5   import org.kuali.student.r2.common.dto.AttributeInfo;
6   import org.kuali.student.r2.common.dto.ContextInfo;
7   import org.kuali.student.r2.common.dto.DtoConstants;
8   import org.kuali.student.r2.common.dto.StatusInfo;
9   import org.kuali.student.r2.common.exceptions.CircularReferenceException;
10  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
11  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
12  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
13  import org.kuali.student.r2.common.exceptions.MissingParameterException;
14  import org.kuali.student.r2.common.exceptions.OperationFailedException;
15  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
16  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
17  import org.kuali.student.r2.core.versionmanagement.dto.VersionDisplayInfo;
18  import org.kuali.student.r2.lum.course.dto.CourseInfo;
19  import org.kuali.student.r2.lum.course.service.CourseService;
20  import org.springframework.transaction.annotation.Transactional;
21  
22  import java.util.Iterator;
23  import java.util.List;
24  
25  @Transactional(noRollbackFor = { DoesNotExistException.class }, rollbackFor = { Throwable.class })
26  public class CourseStateChangeServiceImpl {
27  	private CourseService courseService;
28  
29  	/**
30  	 * Change the state of a course to a new state.
31       * This method exists to help handle some extra logic that was required
32       * to implement Pilot courses, Administrative Retire and Retire by Proposal.
33  	 * 
34  	 * @param courseId id of course
35  	 * @param newState the new state for the course
36  	 * @param prevEndTermAtpId the current version end date of course
37  	 * @return
38  	 * @throws Exception
39  	 */
40  	public StatusInfo changeState(String courseId, String newState,	String prevEndTermAtpId,ContextInfo contextInfo) throws Exception {
41  
42  	    // Get the newest version of the course.  This is the one with the latest
43          // sequence number - the very latest course.  Confusing, huh?
44  		CourseInfo courseInfo = courseService.getCourse(courseId,contextInfo);
45  		
46  		// This naming is a bit confusing since current version means
47          // different things.  Just find the cluId in the debugger and
48          // compare against what is in the KSLU_CLU table.  It is older
49          // than the courseInfo variable above
50          CourseInfo currVerCourse = getCurrentVersionOfCourse(courseInfo, contextInfo);
51          
52          // If the versions are equal, this is the only version.  
53          // There are no versions before it
54          boolean isOnlyVersion = (courseInfo.getId().equals(currVerCourse.getId()));
55  
56          // This variable is used to return if this method was successful
57  		StatusInfo ret = new StatusInfo();
58  		
59  		// If we are trying to activate the course (new state coming in is active)
60  		if ((newState!=null) && (newState.equals(DtoConstants.STATE_ACTIVE))) {
61              
62              // Processing for pilot courses.  These are handled a bit differently.
63              // Instead of activating, we are going to retire it.  Also, if there
64              // is a previous version, we'll supersede it.
65              if ((courseInfo!=null) && courseInfo.isPilotCourse()){
66                  
67                  // If this is the only version, it means there are no previous version of the pilot course
68                  // in this case, we don't need to supersede anything.  
69                  if (!isOnlyVersion){
70                      currVerCourse.setStateKey(DtoConstants.STATE_SUPERSEDED);
71                      currVerCourse.setEndTerm(prevEndTermAtpId);
72                      courseService.updateCourse(currVerCourse.getId(), currVerCourse, contextInfo);
73                      updateStatementTreeViewInfoState(currVerCourse, contextInfo);
74  
75                  }
76  
77                  // TODO KSCM-2812 - the hard coded values for keys below should be coming from a constants class
78                  // Pilot Course Creates come through here the 2nd time and
79                  // gets Retired but first, add fields which are required only for Retired State
80                  courseInfo.getAttributes().add(new AttributeInfo("retirementRationale", "Pilot Course"));
81                  courseInfo.getAttributes().add(new AttributeInfo("lastTermOffered", courseInfo.getEndTerm()));
82                  courseInfo.setStateKey(DtoConstants.STATE_ACTIVE);
83                  retireCourse(courseInfo, contextInfo);
84                  
85                  // We MUST run this after the call to retireCourse or else we
86                  // will get a version mismatch exception
87                  if (!isOnlyVersion){
88                      
89                      // For some reason we need to read the course back in to avoid the
90                      // version mismatch exception
91                      courseInfo = courseService.getCourse(courseId, contextInfo);
92                      
93                      // Now update the CURR_VER_START and CURR_VER_END
94                      courseService.setCurrentCourseVersion(courseInfo.getId(),
95                             null, contextInfo);
96                       
97                  }
98                  
99              }else{
100                 // If NOT a pilot, just activate the course
101                 activateCourse(courseInfo, prevEndTermAtpId, contextInfo);
102             }
103         } else if (newState.equals(DtoConstants.STATE_RETIRED)) {
104             // The new state coming in is retired, so just retire the course
105             retireCourse(courseInfo, contextInfo);
106         }
107 
108         ret.setSuccess(true);
109 
110 		return ret;
111 	}
112 
113 	/**
114 	 * Activate a course version. Only course with a state of "Approved" can be activated.
115 	 * 
116 	 * @param courseToActivate
117 	 * @param prevEndTermAtpId the end term we set on the current version
118 	 */
119 	protected void activateCourse(CourseInfo courseToActivate, String prevEndTermAtpId,ContextInfo contextInfo) throws Exception{
120     	CourseInfo currVerCourse = getCurrentVersionOfCourse(courseToActivate,contextInfo);
121     	String existingState = courseToActivate.getStateKey();
122 		String currVerState = currVerCourse.getStateKey();
123 		boolean isCurrVer = (courseToActivate.getId().equals(currVerCourse.getId()));
124 		
125 		if (existingState.equals(DtoConstants.STATE_DRAFT)) {
126 			// since this is approved if isCurrVer we can assume there are no previously active versions to deal with
127 			if (isCurrVer) {
128 				// setstate for thisVerCourse and setCurrentVersion(courseId)
129 				updateCourseVersionStates(courseToActivate, DtoConstants.STATE_ACTIVE, currVerCourse, null, true, prevEndTermAtpId,contextInfo);
130 			} else if (currVerState.equals(DtoConstants.STATE_ACTIVE) ||
131                     currVerState.equals(DtoConstants.STATE_SUSPENDED) ||
132                     currVerState.equals(DtoConstants.STATE_RETIRED)) {
133 				updateCourseVersionStates(courseToActivate, DtoConstants.STATE_ACTIVE, currVerCourse, DtoConstants.STATE_SUPERSEDED, true, prevEndTermAtpId,contextInfo);
134 			}
135 		}
136 	}
137 	
138 	/**
139 	 * Retire a course version. Only course with a state of "Active" or "Suspended" can be retired
140 	 * 
141 	 * @param courseToRetire the course to retire
142 	 */
143 	protected void retireCourse(CourseInfo courseToRetire,ContextInfo contextInfo) throws Exception{
144     	String existingState = courseToRetire.getStateKey();		
145 		
146     	if (existingState.equals(DtoConstants.STATE_ACTIVE) || existingState.equals(DtoConstants.STATE_SUSPENDED)){
147     		courseToRetire.setStateKey(DtoConstants.STATE_RETIRED);
148     		
149     		courseService.updateCourse(courseToRetire.getId(),courseToRetire,contextInfo);
150 			updateStatementTreeViewInfoState(courseToRetire,contextInfo);
151     	}
152 	}
153 	
154 	/**
155 	 * Get the current version of course from another version of course
156 	 * 
157 	 *
158 	 */
159 	protected CourseInfo getCurrentVersionOfCourse(CourseInfo course,ContextInfo contextInfo)
160 			throws Exception {
161 		// Get version independent id of course
162 		String verIndId = course.getVersion().getVersionIndId();
163 
164 		// Get id of current version of course given the versionindependen id
165 		VersionDisplayInfo curVerDisplayInfo = courseService.getCurrentVersion(
166 		CourseServiceConstants.COURSE_NAMESPACE_URI, verIndId,contextInfo);
167 		String curVerId = curVerDisplayInfo.getId();
168 
169 		// Return the current version of the course
170 		CourseInfo currVerCourse = courseService.getCourse(curVerId,contextInfo);
171 
172 		return currVerCourse;
173 	}
174 
175 	/**
176 	 * Based on null values, updates states of thisVerCourse and currVerCourse
177 	 * and sets thisVerCourse as the current version. Attempts to rollback
178 	 * transaction on exception.
179 	 * 
180 	 * @param thisVerCourse
181 	 *            this is the version that the user selected to change the state
182 	 * @param thisVerNewState
183 	 *            this is state that the user selected to change thisVerCourse
184 	 *            to
185 	 * @param currVerCourse
186 	 *            this is the current version of the course
187 	 *            (currentVersionStartDt <= now && currentVersionEndDt > now)
188 	 * @param currVerNewState
189 	 *            this is the state that we need to set the current version to.
190 	 *            Set to null to not update the currVerCourse state.
191 	 * @param makeCurrent
192 	 *            if true we'll set thisVerCourse as the current version.
193 	 * @param prevEndTermAtpId
194 	 *            the end term for the previous version to end on
195 	 * @throws Exception
196 	 */
197 	private void updateCourseVersionStates(CourseInfo thisVerCourse,
198 			String thisVerNewState, CourseInfo currVerCourse,
199 			String currVerNewState, boolean makeCurrent,
200 			String prevEndTermAtpId,ContextInfo contextInfo) throws Exception {
201 		String thisVerPrevState = thisVerCourse.getStateKey();
202 
203 		// if already current, will throw error if you try to make the current
204 		// version the current version.
205 		boolean isCurrent = thisVerCourse.getId().equals(currVerCourse.getId());
206 		if(!makeCurrent || !isCurrent || !thisVerCourse.getVersion().getSequenceNumber().equals(1)){
207 			makeCurrent &= !isCurrent;
208 		}
209 
210 		if (thisVerNewState == null) {
211 			throw new InvalidParameterException("new state cannot be null");
212 		} else {
213 			thisVerCourse.setStateKey(thisVerNewState);
214 			courseService.updateCourse(thisVerCourse.getId(),thisVerCourse,contextInfo);
215 			updateStatementTreeViewInfoState(thisVerCourse,contextInfo);
216 		}
217 
218 		// won't get called if previous exception was thrown
219 		if (currVerNewState != null) {
220 			currVerCourse.setStateKey(currVerNewState);
221 			if(currVerCourse.getEndTerm()==null){
222 				currVerCourse.setEndTerm(prevEndTermAtpId);
223 			}
224 			courseService.updateCourse(currVerCourse.getId(),currVerCourse,contextInfo);
225 			updateStatementTreeViewInfoState(currVerCourse,contextInfo);
226 		}
227 
228 		if (makeCurrent == true) {
229 			courseService.setCurrentCourseVersion(thisVerCourse.getId(),
230 					null,contextInfo);
231 		}
232 
233 		// for all draft and approved courses set the state to superseded.
234 		// we should only need to evaluated versions with sequence number
235 		// higher than previous active course. If the course you're
236 		// activating is the current course check all versions.
237 		if (thisVerPrevState.equals(DtoConstants.STATE_APPROVED)
238 				&& thisVerNewState.equals(DtoConstants.STATE_ACTIVE)) {
239 
240 			List<VersionDisplayInfo> versions = courseService.getVersions(
241 					CourseServiceConstants.COURSE_NAMESPACE_URI, thisVerCourse
242 						.getVersion().getVersionIndId(),contextInfo);
243 
244             Long startSeq = Long.valueOf(1);
245             if (!isCurrent && !currVerCourse.getId().equals(thisVerCourse.getId())) {
246 				startSeq = currVerCourse.getVersion().getSequenceNumber() + 1;
247 			}
248 
249 			for (VersionDisplayInfo versionInfo : versions) {
250 				if (versionInfo.getSequenceNumber() >= startSeq) {
251 					CourseInfo otherCourse = courseService
252 							.getCourse(versionInfo.getId(),contextInfo);
253 					if (otherCourse.getStateKey().equals(
254 							DtoConstants.STATE_APPROVED)
255 							|| otherCourse.getStateKey().equals(
256 									DtoConstants.STATE_SUBMITTED)
257 							|| otherCourse.getStateKey().equals(
258 									DtoConstants.STATE_DRAFT)) {
259 						otherCourse.setStateKey(DtoConstants.STATE_SUPERSEDED);
260 						courseService.updateCourse(otherCourse.getId(),otherCourse,contextInfo);
261 						updateStatementTreeViewInfoState(otherCourse,contextInfo);
262 					}
263 				}
264 			}
265 		}
266 
267 	}
268 
269 	public void setCourseService(CourseService courseService) {
270 		this.courseService = courseService;
271 	}
272 
273 	/**
274 	 * This method will load all the statements in a course from the course web
275 	 * service, recursively update the state of each statement in the statement
276 	 * tree, and save the update statements back to the web service.
277 	 * 
278 	 * 
279 	 * @param courseInfo
280 	 *            The course to update (call setState() in this object to set
281 	 *            the state)
282 	 * @throws DoesNotExistException
283 	 * @throws InvalidParameterException
284 	 * @throws MissingParameterException
285 	 * @throws OperationFailedException
286 	 * @throws PermissionDeniedException
287 	 * @throws DataValidationErrorException
288 	 * @throws CircularReferenceException
289 	 * @throws VersionMismatchException
290 	 */
291 	public void updateStatementTreeViewInfoState(CourseInfo courseInfo,ContextInfo contextInfo)
292 			throws DoesNotExistException, InvalidParameterException,
293 			MissingParameterException, OperationFailedException,
294 			PermissionDeniedException, DataValidationErrorException,
295 			CircularReferenceException, VersionMismatchException {
296 
297 		// Call course web service to get all requirements/statements for this
298 		// course
299 		List<StatementTreeViewInfo> statementTreeViewInfos = courseService
300 				.getCourseStatements(courseInfo.getId(), null, null,contextInfo);
301 
302 		if(statementTreeViewInfos != null){
303             // Recursively update state on all requirements/statements in the tree
304             for (Iterator<StatementTreeViewInfo> it = statementTreeViewInfos.iterator(); it.hasNext();){
305                 StatementUtil.updateStatementTreeViewInfoState(courseInfo.getStateKey(), it.next());
306             }
307     
308             // Call the course web service and update the requirement/statement tree
309             // with the new state
310             for (Iterator<StatementTreeViewInfo> it = statementTreeViewInfos.iterator(); it.hasNext();){
311                 courseService.updateCourseStatement(courseInfo.getId(), null, it.next(), contextInfo);
312             }
313         }
314 	}
315 
316 }