View Javadoc

1   package org.kuali.student.lum.program.server;
2   
3   import java.util.Date;
4   import java.util.List;
5   
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.AttributeInfo;
9   import org.kuali.student.r2.common.exceptions.DoesNotExistException;
10  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
11  import org.kuali.student.r2.common.exceptions.MissingParameterException;
12  import org.kuali.student.r2.common.exceptions.OperationFailedException;
13  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
14  import org.kuali.student.r2.common.util.ContextUtils;
15  
16  import org.kuali.student.r2.core.atp.dto.AtpInfo;
17  import org.kuali.student.r2.core.atp.service.AtpService;
18  import org.kuali.student.r1.core.statement.dto.StatementTreeViewInfo;
19  import org.kuali.student.r2.core.versionmanagement.dto.VersionDisplayInfo;
20  import org.kuali.student.lum.common.server.StatementUtil;
21  import org.kuali.student.r2.lum.program.dto.MajorDisciplineInfo;
22  import org.kuali.student.r2.lum.program.dto.ProgramRequirementInfo;
23  import org.kuali.student.r2.lum.program.dto.ProgramVariationInfo;
24  import org.kuali.student.r2.lum.program.service.ProgramService;
25  import org.kuali.student.r2.common.util.constants.ProgramServiceConstants;
26  import org.springframework.transaction.annotation.Transactional;
27  
28  /**
29   * This class is called whenever the state of a major discipline changes.
30   * <p>
31   * We have a separate class because the operations need to be marked with the @Transactional annotation.
32   * <p>
33   * THIS CLASS IS DUPLICATED FOR MAJOR DISCIPLINE, CORE, AND CREDENTIAL PROGRAMS SINCE THERE ARE
34   * DIFFERENT SERVICE METHODS FOR EACH OF THESE TYPES (THOUGH THEY ARE SIMILAR)
35   * <P>
36   * 
37   * @author Kuali Rice Team (kuali-rice@googlegroups.com)
38   */
39  @Transactional(noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class})
40  public class MajorDisciplineStateChangeServiceImpl implements StateChangeService{
41  
42      /**
43       * The program service - injected by spring.
44       */
45      private ProgramService programService;
46      private AtpService atpService;
47      
48      /**
49       * This method is called by workflow when the state changes.
50       * 
51       * @param majorDisciplineId
52       * @param state
53       * @return
54       * @throws Exception
55       */
56      public void changeState(String majorDisciplineId, String newState) throws Exception {
57          // This method will be called from workflow.
58          // Since we cannot activate a program from the workflow we do not need to add endEntryTerm and endEnrollTerm
59          changeState(null, null, null, majorDisciplineId, newState);
60      }
61  
62      /**
63       * This method is called from the UI (servlet) when state changes.
64       * 
65       * @param endEntryTerm
66       * @param endEnrollTerm
67       * @param programType
68       * @param majorDisciplineId
69       * @param newState
70       * @return
71       * @throws Exception
72       */
73      public void changeState(String endEntryTerm, String endEnrollTerm, String endInstAdmitTerm, String majorDisciplineId, String newState) throws Exception {
74  
75          // New state must not be null
76          if (newState == null){
77              throw new InvalidParameterException("new state cannot be null");
78  		}
79  
80          // The version selected in the UI
81          MajorDisciplineInfo selectedVersion = programService.getMajorDiscipline(majorDisciplineId,ContextUtils.getContextInfo());
82  
83          // If we are activating this version we need to mark the previous version superseded,
84          // update the previous version end terms, and make the selected version current.
85          if (newState.equals(DtoConstants.STATE_ACTIVE)) {
86  
87              // Update previous versions to superseded and set end terms on previous current version.
88          	updatePreviousVersions(selectedVersion, endEntryTerm, endEnrollTerm, endInstAdmitTerm);
89  
90              // Update state of all associated objects for current version
91              // NOTE: we must update state BEFORE making the version current
92              updateMajorDisciplineInfoState(selectedVersion, newState);
93  
94              // Make this the current version
95              makeCurrent(selectedVersion);
96          } else {
97  
98              // Update state of all associated objects for current version
99              updateMajorDisciplineInfoState(selectedVersion, newState);
100         }
101 
102       
103 
104     }
105 
106 
107     /**
108      * This method will update the state of this object and all associated objects.
109      * <p>
110      * It is needed because we need to make separate web service calls to update the state of these objects.
111      * 
112      * @param majorDisciplineInfo
113      * @param newState
114      */
115     private void updateMajorDisciplineInfoState(MajorDisciplineInfo majorDisciplineInfo, String newState) throws Exception {
116         // Update the statement tree
117         List<String> programRequirementIds = majorDisciplineInfo.getProgramRequirements();
118         updateRequirementsState(programRequirementIds, newState);
119 
120         
121         // Update any variations 
122         List<ProgramVariationInfo> variationList = majorDisciplineInfo.getVariations();
123         updateVariationsRequirementsState(variationList, newState);
124         
125         
126         // Update major discipline
127         majorDisciplineInfo.setStateKey(newState);
128         programService.updateMajorDiscipline(null, majorDisciplineInfo,ContextUtils.getContextInfo());
129     }
130 
131     /**
132      * This method will make this version of the major discipline the current one.
133      * 
134      * @param majorDisciplineInfo
135      */
136     private void makeCurrent(MajorDisciplineInfo majorDisciplineInfo) throws Exception {
137 
138         // Check if this is the current version before trying to make it current
139         // (the web service will error if you try to make a version current that is already current)
140         VersionDisplayInfo currentVersion = null;
141         currentVersion = programService.getCurrentVersion(ProgramServiceConstants.PROGRAM_NAMESPACE_MAJOR_DISCIPLINE_URI, majorDisciplineInfo.getVersion().getVersionIndId(),ContextUtils.getContextInfo());
142 
143         // If this is not the current version, then make it current
144         if (!currentVersion.getSequenceNumber().equals(majorDisciplineInfo.getVersion().getSequenceNumber())) {
145             programService.setCurrentMajorDisciplineVersion(majorDisciplineInfo.getId(), null,ContextUtils.getContextInfo());
146         }
147     }
148 
149     /**
150      * This method finds all previous versions of program and sets all previous ACTIVE,APPROVED,DRAFT versions to SUPERSEDED and
151      * sets new end terms for previous current version.
152  
153      * @param majorDisciplineInfo The version of major discipline program being activated
154      * @param endEntryTerm The new end entry term to set on previous active version
155      * @param endEnrollTerm The new end enroll term to set on previous active version
156      * @throws Exception
157      */
158     private void updatePreviousVersions (MajorDisciplineInfo selectedVersion, String endEntryTerm, String endEnrollTerm, String endInstAdmitTerm) throws Exception {
159     	// Get the current version of major discipline given the selected version
160     	MajorDisciplineInfo currentVersion = getCurrentVersion(selectedVersion);
161     	
162     	boolean isSelectedVersionCurrent = selectedVersion.getId().equals(currentVersion.getId());
163     	
164 
165         // Fix for KSLAB-2382
166         // When a version needs to be marked as superseded, the user is prompted with a screen to enter end terms.
167         // The screen will place the end terms in the data object and we'll see them here when the changeState method is called.
168         // if the end terms are null, we must not have been prompted with the screen, so we do not need to mark previous
169         // versions superseded.   
170     	// Another way to prevent this bug would be to ask if we are trying to activate a newly created program and,
171     	// if so, we should not try to supersede anything (because there should be nothing to supersede). 
172         if (endEntryTerm == null && endEnrollTerm == null && endInstAdmitTerm == null ){
173             return;
174         }
175     	//Set the end terms on the current version of major discipline and update it's state to superseded
176     	setEndTerms(currentVersion, endEntryTerm, endEnrollTerm, endInstAdmitTerm);
177     	updateMajorDisciplineInfoState(currentVersion, DtoConstants.STATE_SUPERSEDED);
178 
179 		// Loop through all previous active or approved programs and set the state to superseded.
180 		// We should only need to evaluated versions with sequence number
181 		// higher than previous active program
182 
183 		List<VersionDisplayInfo> versions = programService.getVersions(ProgramServiceConstants.PROGRAM_NAMESPACE_MAJOR_DISCIPLINE_URI, 
184 				selectedVersion.getVersion().getVersionIndId(),ContextUtils.getContextInfo());
185 		Long startSeq = new Long(1);
186 
187 		if (!isSelectedVersionCurrent) {
188 						
189 			startSeq = currentVersion.getVersion().getSequenceNumber() + 1;
190 		}
191 
192 		for (VersionDisplayInfo versionInfo : versions) {
193 			boolean isVersionNewerThanCurrentVersion = versionInfo.getSequenceNumber() >= startSeq;
194 			boolean isVersionSelectedVersion = false;
195 
196 			isSelectedVersionCurrent = versionInfo.getSequenceNumber().equals(selectedVersion.getVersion().getSequenceNumber());  
197 			boolean updateState = isVersionNewerThanCurrentVersion && !isVersionSelectedVersion;
198 			if (updateState) {
199 				MajorDisciplineInfo otherProgram = programService.getMajorDiscipline(versionInfo.getId(),ContextUtils.getContextInfo());
200 				if (otherProgram.getStateKey().equals(DtoConstants.STATE_APPROVED) ||
201 					otherProgram.getStateKey().equals(DtoConstants.STATE_ACTIVE)){
202 			        updateMajorDisciplineInfoState(otherProgram, DtoConstants.STATE_SUPERSEDED);
203 				}		
204 			}
205 		}    	
206 
207     }
208     
209 	/**
210 	 * Get the current version of program given the selected version of program
211 	 * 
212 	 * @param verIndId
213 	 */
214 	protected MajorDisciplineInfo getCurrentVersion(MajorDisciplineInfo majorDisciplineInfo)
215 			throws Exception {
216 		// Get version independent id of program
217 		String verIndId = majorDisciplineInfo.getVersion().getVersionIndId();
218 
219 		// Get id of current version of program given the version independent id
220 		VersionDisplayInfo curVerDisplayInfo = null;
221 		curVerDisplayInfo =  programService.getCurrentVersion(ProgramServiceConstants.PROGRAM_NAMESPACE_MAJOR_DISCIPLINE_URI, verIndId,ContextUtils.getContextInfo());
222 
223 		String curVerId = curVerDisplayInfo.getId();
224 
225 		// Return the current version of the course
226 		MajorDisciplineInfo currentVersion = programService.getMajorDiscipline(curVerId,ContextUtils.getContextInfo());
227 
228 		return currentVersion;
229 	}
230     
231     /**
232      * This method updates the end terms for the major discipline passed into it.
233      * <p>
234      * You must still call updateState() to save the object using the web service.
235      * 
236      * @param majorDisciplineInfo
237      * @param endEntryTerm
238      * @param endEnrollTerm
239      * @param endInstAdmitTerm 
240      * @throws OperationFailedException 
241      * @throws MissingParameterException 
242      * @throws InvalidParameterException 
243      * @throws DoesNotExistException 
244      * @throws PermissionDeniedException 
245      */
246     private void setEndTerms(MajorDisciplineInfo majorDisciplineInfo, String endEntryTerm, String endEnrollTerm, String endInstAdmitTerm) throws InvalidParameterException, MissingParameterException, OperationFailedException, DoesNotExistException, PermissionDeniedException {
247         
248     	//Set the end terms on the major discipline
249     	majorDisciplineInfo.setEndProgramEntryTerm(endEntryTerm);
250         majorDisciplineInfo.setEndTerm(endEnrollTerm);
251         majorDisciplineInfo.getAttributes().add(new AttributeInfo("endInstAdmitTerm", endInstAdmitTerm));
252         
253         
254         //Check if there are variations to process
255         if(!majorDisciplineInfo.getVariations().isEmpty()){
256         	
257         	//Find the major's end term atps and obtain their date information
258             ContextInfo contextInfo = ContextUtils.getContextInfo();
259             AtpInfo majorEndEntryTermAtp = atpService.getAtp(endEntryTerm,contextInfo);
260    			Date majorEndEntryTermEndDate = majorEndEntryTermAtp.getEndDate();
261    			AtpInfo majorEndEnrollTermAtp = atpService.getAtp(endEnrollTerm,contextInfo);
262    			Date majorEndEnrollTermEndDate = majorEndEnrollTermAtp.getEndDate();
263        		AtpInfo majorEndInstAdmitTermAtp = atpService.getAtp(endInstAdmitTerm,contextInfo);
264        		Date majorEndInstAdmitTermEndDate = majorEndInstAdmitTermAtp.getEndDate();
265     
266        		//Loop through the variations
267 	        for(ProgramVariationInfo variation:majorDisciplineInfo.getVariations()){
268 	        	//compare dates to get the older of the two end terms
269 	    		if(variation.getEndProgramEntryTerm() != null){
270 	    			AtpInfo variationEndEntryTermAtp = atpService.getAtp(variation.getEndProgramEntryTerm(),contextInfo);
271 	    			Date variationEndEntryTermEndDate = variationEndEntryTermAtp.getEndDate();
272 	    			if(majorEndEnrollTermEndDate.compareTo(variationEndEntryTermEndDate)<=0){
273 		    			variation.setEndProgramEntryTerm(endEntryTerm);
274 	    			}
275 	    		}else{
276 	    			variation.setEndProgramEntryTerm(endEntryTerm);
277 	    		}
278 	    		//compare dates to get the older of the two end terms
279 	    		if(variation.getEndTerm() != null){
280 	    			AtpInfo variationEndTermAtp = atpService.getAtp(variation.getEndTerm(),contextInfo);
281 	    			Date variationEndTermEndDate = variationEndTermAtp.getEndDate();
282 	    			if(majorEndEntryTermEndDate.compareTo(variationEndTermEndDate)<=0){
283 		    			variation.setEndTerm(endEnrollTerm);
284 	    			}
285 	    		}else{
286 	    			variation.setEndTerm(endEnrollTerm);
287 	    		}
288 	    		//compare dates to get the older of the two end terms
289 	    		//TODO KSCM
290 //	    		if(variation.getAttributeInfoValue(variation.getAttributes(),"endInstAdmitTerm") != null){
291 //	    			AtpInfo variationEndInstAdmitAtp = atpService.getAtp(variation.getAttributeInfoValue(variation.getAttributes(),"endInstAdmitTerm"));
292 //	    			Date variationEndInstAdmitEndDate = variationEndInstAdmitAtp.getEndDate();
293 //	    			if(majorEndInstAdmitTermEndDate.compareTo(variationEndInstAdmitEndDate)<=0){
294 //	    				variation.getAttributes().add(new AttributeInfo("endInstAdmitTerm", endInstAdmitTerm));
295 //	    			}
296 //	    		}else{
297 //	    			variation.getAttributes().add(new AttributeInfo("endInstAdmitTerm", endInstAdmitTerm));
298 //	    		}
299 	    		
300 	        }
301         }
302     }
303 
304     /**
305      * This method will update the requirement state.
306      * <p>
307      * Note that it uses StatementUtil to update the statement tree.
308      * 
309      * @param majorDisciplineInfo
310      * @param newState
311      * @throws Exception
312      */
313     public void updateRequirementsState(List<String> programRequirementIds, String newState) throws Exception {
314     
315         for (String programRequirementId : programRequirementIds) {
316 
317             // Get program requirement from the program service
318             ProgramRequirementInfo programRequirementInfo = null;
319             programRequirementInfo = programService.getProgramRequirement(programRequirementId, ContextUtils.getContextInfo());
320 
321             // Look in the requirement for the statement tree
322             StatementTreeViewInfo statementTree = programRequirementInfo.getStatement();
323 
324             // And recursively update the entire tree with the new state
325             StatementUtil.updateStatementTreeViewInfoState(newState, statementTree);
326 
327             // Update the state of the requirement object
328             programRequirementInfo.setStateKey(newState);
329 
330             // The write the requirement back to the program service
331             programService.updateProgramRequirement(programRequirementId, programRequirementId, programRequirementInfo, ContextUtils.getContextInfo());
332 
333         }
334     }
335 
336     /**
337      * This method will update the requirements of each variation.
338      * <p>
339      * We need to do this here as opposed to in the assemblers, since we need
340      * to make calls out to a separate web service.  Also, this needs to be done here
341      * because changing state no longer calls the save function.
342      * <p>
343      * Note that core and credential programs do not have variations so
344      * this method isn't necessary.
345      * <p>
346      * 
347      * @param majorDisciplineInfo
348      * @param newState
349      * @throws Exception
350      */
351     public void updateVariationsRequirementsState(List<ProgramVariationInfo> variationList, String newState) throws Exception {
352 
353         // Iterate over all variations
354         for (ProgramVariationInfo variation : variationList) {
355      
356             // Get the requirements 
357             List<String> programRequirementIds = variation.getProgramRequirements();
358             
359             // Call the method that will update the requirements state for the program
360             // This will also update the statement tree
361             updateRequirementsState(programRequirementIds, newState);
362          }
363     }
364 
365     /**
366      * This method is used by Spring to inject the program service into this bean.
367      * 
368      * @param programService
369      */
370     public void setProgramService(ProgramService programService) {
371         this.programService = programService;
372     }
373     
374     public void setAtpService(AtpService atpService) {
375 		this.atpService = atpService;
376 	}
377 }