View Javadoc

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