View Javadoc

1   /*
2    * To change this template, choose Tools | Templates
3    * and open the template in the editor.
4    */
5   package org.kuali.student.enrollment.class2.courseofferingset.service.impl;
6   
7   import org.apache.commons.lang.StringUtils;
8   import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
9   import org.kuali.student.enrollment.class2.courseofferingset.dao.SocDao;
10  import org.kuali.student.enrollment.class2.courseofferingset.service.facade.RolloverAssist;
11  import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
12  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
13  import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
14  import org.kuali.student.enrollment.courseofferingset.dto.SocInfo;
15  import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultInfo;
16  import org.kuali.student.enrollment.courseofferingset.service.CourseOfferingSetService;
17  import org.kuali.student.enrollment.courseofferingset.service.CourseOfferingSetServiceBusinessLogic;
18  import org.kuali.student.r2.common.dto.ContextInfo;
19  import org.kuali.student.r2.common.dto.StatusInfo;
20  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
21  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
22  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
23  import org.kuali.student.r2.common.exceptions.MissingParameterException;
24  import org.kuali.student.r2.common.exceptions.OperationFailedException;
25  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
26  import org.kuali.student.r2.common.exceptions.ReadOnlyException;
27  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
28  import org.kuali.student.r2.common.util.constants.CourseOfferingSetServiceConstants;
29  import org.kuali.student.r2.common.util.constants.LuiServiceConstants;
30  import org.kuali.student.r2.core.acal.dto.TermInfo;
31  import org.kuali.student.r2.core.acal.service.AcademicCalendarService;
32  import org.kuali.student.r2.core.constants.AtpServiceConstants;
33  import org.kuali.student.r2.core.scheduling.service.SchedulingService;
34  import org.kuali.student.r2.lum.course.service.CourseService;
35  
36  import javax.xml.namespace.QName;
37  import java.util.ArrayList;
38  import java.util.Date;
39  import java.util.List;
40  
41  public class CourseOfferingSetServiceBusinessLogicImpl implements CourseOfferingSetServiceBusinessLogic {
42  
43      private CourseOfferingService coService;
44      private CourseService courseService;
45      private AcademicCalendarService acalService;
46      private CourseOfferingSetService socService;
47      private RolloverAssist rolloverAssist;
48      private SocDao socDao;
49  
50      private SchedulingService schedulingService;
51  
52      public SocDao getSocDao() {
53          return socDao;
54      }
55  
56      public void setSocDao(SocDao socDao) {
57          this.socDao = socDao;
58      }
59  
60      public CourseOfferingSetService getSocService() {
61          return socService;
62      }
63  
64      public void setSocService(CourseOfferingSetService socService) {
65          this.socService = socService;
66      }
67      
68      public CourseOfferingService getCoService() {
69          return coService;
70      }
71  
72      public void setCoService(CourseOfferingService coService) {
73          this.coService = coService;
74      }
75  
76      public CourseService getCourseService() {
77          return courseService;
78      }
79  
80      public void setCourseService(CourseService courseService) {
81          this.courseService = courseService;
82      }
83  
84      public AcademicCalendarService getAcalService() {
85          return acalService;
86      }
87  
88      public void setAcalService(AcademicCalendarService acalService) {
89          this.acalService = acalService;
90      }
91  
92      public RolloverAssist getRolloverAssist() {
93          return rolloverAssist;
94      }
95  
96      public void setRolloverAssist(RolloverAssist rolloverAssist) {
97          this.rolloverAssist = rolloverAssist;
98      }
99  
100     private CourseOfferingSetService _getSocService() {
101         // If it hasn't been set by Spring, then look it up by GlobalResourceLoader
102         if (socService == null) {
103             socService = (CourseOfferingSetService) GlobalResourceLoader.getService(new QName(CourseOfferingSetServiceConstants.NAMESPACE,
104                                                                                     CourseOfferingSetServiceConstants.SERVICE_NAME_LOCAL_PART));
105         }
106         return socService;
107     }
108 
109     public SchedulingService getSchedulingService() {
110         return schedulingService;
111     }
112 
113     public void setSchedulingService(SchedulingService schedulingService) {
114         this.schedulingService = schedulingService;
115     }
116 
117     private SocInfo _findTargetSoc(String targetTermId) {
118         try {
119             List<String> socIds = this._getSocService().getSocIdsByTerm(targetTermId, new ContextInfo());
120             if (socIds != null) {
121                 if (socIds.isEmpty()) {
122                     return null;
123                 }
124                 List<SocInfo> targetSocs = this._getSocService().getSocsByIds(socIds, new ContextInfo());
125                 for (SocInfo soc: targetSocs) {
126                     if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.MAIN_SOC_TYPE_KEY)) {
127                         return soc;
128                     }
129                 }
130             }
131             return null;
132         } catch (Exception e) {
133             return null;
134         }
135     }
136 
137     private boolean _hasOfferingsInTargetTerm(TermInfo targetTerm) {
138         if (socDao == null) {
139             return false; // Mostly to satisfy Norm's mock impls
140         }
141         Long count = socDao.countLuisByTypeForTermId(LuiServiceConstants.COURSE_OFFERING_TYPE_KEY, targetTerm.getId());
142         return count > 0;
143     }
144 
145     /**
146      *
147      * Checks if all terms and subterms are offical.
148      *
149      * @param targetTerm
150      * @param contextInfo
151      * @return If all terms and subterms are offical, return null; else return the termId of the first unoffical term.
152      * @throws PermissionDeniedException
153      * @throws MissingParameterException
154      * @throws InvalidParameterException
155      * @throws OperationFailedException
156      * @throws DoesNotExistException
157      */
158     private String _verifyTermsOfficial(TermInfo targetTerm, ContextInfo contextInfo) throws PermissionDeniedException, MissingParameterException, InvalidParameterException, OperationFailedException, DoesNotExistException {
159         if (!targetTerm.getStateKey().equals(AtpServiceConstants.ATP_OFFICIAL_STATE_KEY)) {
160             return targetTerm.getId();
161         }
162         List<TermInfo> childTerms = acalService.getIncludedTermsInTerm(targetTerm.getId(), contextInfo);
163         for (TermInfo termInfo: childTerms) {
164             if (!termInfo.getStateKey().equals(AtpServiceConstants.ATP_OFFICIAL_STATE_KEY)) {
165                 return termInfo.getId();
166             }
167         }
168         return null;
169     }
170 
171     @Override
172     public SocInfo rolloverSoc(String sourceSocId, String targetTermId, List<String> optionKeys, ContextInfo context)
173             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
174             PermissionDeniedException {
175         // validate the target term
176         TermInfo targetTerm = this.acalService.getTerm(targetTermId, context);
177         if (targetTerm == null) {
178             throw new DoesNotExistException("target term id (" + targetTermId + ") does not exist");
179         }
180         // first create the new soc
181         SocInfo sourceSoc = this._getSocService().getSoc(sourceSocId, context);
182         if (sourceSoc.getTermId().equals(targetTermId)) {
183             throw new InvalidParameterException("The term of the source soc and the target term must be different");
184         }
185         String termId = null;
186         if ((termId = _verifyTermsOfficial(targetTerm, context)) != null) {
187             throw new OperationFailedException("Target (sub)term (id=" + termId + ") does not have official state.  Can't rollover");
188         } else {
189             // That way, rolloverCourseOffering can do less validation
190             if (!optionKeys.contains(CourseOfferingSetServiceConstants.TARGET_TERM_VALIDATED_OPTION_KEY)) {
191                 optionKeys = new ArrayList<String>(optionKeys); // create a shallow copy so original is unmodified
192                 optionKeys.add(CourseOfferingSetServiceConstants.TARGET_TERM_VALIDATED_OPTION_KEY);
193             }
194         }
195         // DanS says if there are any offerings in the target term, we shouldn't perform rollover.  The implication
196         // is that any courses that were copied from canonical prior to a rollover would prevent a rollover from
197         // happening. DanS has said this is fine (as of 5/20/2012)
198         if (_hasOfferingsInTargetTerm(targetTerm)) {
199             throw new OperationFailedException("Can't rollover if course offerings exist in target term");
200         }
201 
202         // Reuse SOC in target term
203         SocInfo targetSoc = _findTargetSoc(targetTermId);
204         boolean foundTargetSoc = true;
205         if (targetSoc == null) {
206             // Did not find target SOC, make a new one
207             targetSoc = new SocInfo(sourceSoc);
208             foundTargetSoc = false;
209             targetSoc.setId(null);
210             targetSoc.getAttributes().clear();
211             targetSoc.setTermId(targetTermId);
212             targetSoc.setStateKey(CourseOfferingSetServiceConstants.DRAFT_SOC_STATE_KEY);
213             try {
214                 targetSoc = this._getSocService().createSoc(targetSoc.getTermId(), targetSoc.getTypeKey(), targetSoc, context);
215             } catch (DataValidationErrorException ex) {
216                 throw new OperationFailedException("Unexpected", ex);
217             } catch (ReadOnlyException ex) {
218                 throw new OperationFailedException("Unexpected", ex);
219             }
220         } else { // There is already a target SOC, so re-use it?
221             // TODO: if foundTargetSoc is true, should we do more cleanup?
222             if (!targetSoc.getStateKey().equals(CourseOfferingSetServiceConstants.DRAFT_SOC_STATE_KEY))
223             // Make it draft in the new term            
224             // Persist the draft state
225             this._getSocService().changeSocState(targetSoc.getId(), CourseOfferingSetServiceConstants.DRAFT_SOC_STATE_KEY, context);
226         }
227 
228 
229         // then build the result so we can track stuff
230         SocRolloverResultInfo result = new SocRolloverResultInfo();
231         result.setTypeKey(CourseOfferingSetServiceConstants.ROLLOVER_RESULT_TYPE_KEY);
232         result.setStateKey(CourseOfferingSetServiceConstants.SUBMITTED_RESULT_STATE_KEY);
233         result.setSourceSocId(sourceSocId);
234         result.setTargetTermId(targetTermId);
235         result.setOptionKeys(optionKeys);
236         result.setTargetSocId(targetSoc.getId());
237         Date now = new Date();
238         result.setDateInitiated(now);
239         // Although it's not completed, as long as the SocRolloverResultInfo is either in the submitted or running
240         // state, the date completed field represents the current time.  It also prevents NPEs when computing
241         // duration.
242         result.setDateCompleted(now);
243         try {
244             result = this._getSocService().createSocRolloverResult(result.getTypeKey(), result, context);
245         } catch (DataValidationErrorException ex) {
246             throw new OperationFailedException("Unexpected", ex);
247         } catch (ReadOnlyException ex) {
248             throw new OperationFailedException("Unexpected", ex);
249         }
250         // create the runner so we can kick it off in another thread
251         final CourseOfferingRolloverRunner runner = new CourseOfferingRolloverRunner();
252         runner.setContext(context);
253         runner.setCoService(coService);
254         runner.setCourseService(courseService);
255         runner.setAcalService(acalService);
256         runner.setSocService(this._getSocService());
257         runner.setRolloverAssist(rolloverAssist); // KSENROLL-8062 Add colo to rollover
258         runner.setResult(result);
259 
260         if (optionKeys.contains(CourseOfferingSetServiceConstants.RUN_SYNCHRONOUSLY_OPTION_KEY)) {
261             //Run this thread synchronously
262             runner.run();
263         } else {
264             //Try to run this after the transaction completes
265             KSThreadRunnerAfterTransactionSynchronization.runAfterTransactionCompletes(runner);
266         }
267         return targetSoc;
268     }
269 
270     @Override
271     public SocRolloverResultInfo reverseRollover(String rolloverResultId, List<String> optionKeys, ContextInfo context)
272             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
273             PermissionDeniedException {// validate the target term
274         SocRolloverResultInfo rolloverResult = this._getSocService().getSocRolloverResult(rolloverResultId, context);
275         if (optionKeys.contains(CourseOfferingSetServiceConstants.REVERSE_JUST_CREATES_OPTION_KEY)) {
276             if (!rolloverResult.getOptionKeys().contains(CourseOfferingSetServiceConstants.LOG_SUCCESSES_OPTION_KEY)) {
277                 throw new InvalidParameterException(
278                         "You cannot reverse just the creates if the original rollover did not log the course offerings that it successfully created");
279             }
280         }
281         SocRolloverResultInfo reverseResult = new SocRolloverResultInfo(rolloverResult);
282         reverseResult.setTypeKey(CourseOfferingSetServiceConstants.ROLLOVER_RESULT_TYPE_KEY);
283         reverseResult.setStateKey(CourseOfferingSetServiceConstants.SUBMITTED_RESULT_STATE_KEY);
284         reverseResult.setOptionKeys(optionKeys);
285 
286         try {
287             reverseResult = this._getSocService().createSocRolloverResult(reverseResult.getTypeKey(), reverseResult, context);
288         } catch (DataValidationErrorException ex) {
289             throw new OperationFailedException("Unexpected", ex);
290         } catch (ReadOnlyException ex) {
291             throw new OperationFailedException("Unexpected", ex);
292         }
293         // create the runner so we can kick it off in another thread
294         CourseOfferingReverseRolloverRunner runner = new CourseOfferingReverseRolloverRunner();
295         runner.setContext(context);
296         runner.setCoService(coService);
297         runner.setSocService(this._getSocService());
298         runner.setCourseService(courseService);
299         runner.setAcalService(acalService);
300         runner.setRolloverResult(rolloverResult);
301         runner.setReverseResult(reverseResult);
302         if (optionKeys.contains(CourseOfferingSetServiceConstants.RUN_SYNCHRONOUSLY_OPTION_KEY)) {
303             runner.run();
304         } else {
305             Thread thread = new Thread(runner);
306             thread.start();
307         }
308         return reverseResult;
309     }
310 
311     @Override
312     public List<String> getCourseOfferingIdsBySoc(String socId, ContextInfo context)
313             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
314             PermissionDeniedException {
315         // THIS IS BASICALLY A SWITCH STATEMENT BASED ON THE TYPE OF THE SOC
316         SocInfo soc = this._getSocService().getSoc(socId, context);
317         // main
318         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.MAIN_SOC_TYPE_KEY)) {
319             return coService.getCourseOfferingIdsByTerm(soc.getTermId(), Boolean.TRUE, context);
320         }
321         // subject area
322         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.SUBJECT_AREA_SOC_TYPE_KEY)) {
323             return coService.getCourseOfferingIdsByTermAndSubjectArea(soc.getTermId(), soc.getSubjectArea(), context);
324         }
325         // units content owner
326         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.UNITS_CONTENT_OWNER_SOC_TYPE_KEY)) {
327             return coService.getCourseOfferingIdsByTermAndUnitsContentOwner(soc.getTermId(), soc.getUnitsContentOwnerId(), context);
328         }
329         throw new OperationFailedException(soc.getTypeKey() + " is an unsupported type for this implementation");
330 //        List<String> list = new ArrayList<String>();
331 //        for (CourseOfferingInfo info : courseOfferingMap.values()) {
332 //            if (socId.equals(info.getSocId())) {
333 //                list.add(info.getId());
334 //            }
335 //        }
336 //        return list;
337     }
338 
339     @Override
340     public Integer deleteCourseOfferingsBySoc(String socId, ContextInfo context)
341             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
342             PermissionDeniedException {
343         // TODO: add bulk ops to CourseOfferingService so this can call them 
344         // to delete all for a term or delete all for a subject area intead of doing it one by one
345         List<String> ids = this.getCourseOfferingIdsBySoc(socId, context);
346         for (String id : ids) {
347             this.coService.deleteCourseOfferingCascaded(id, context);
348          }
349         return ids.size();
350     }
351 
352     @Override
353     public Boolean isCourseOfferingInSoc(String socId, String courseOfferingId, ContextInfo context)
354             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
355             PermissionDeniedException {
356 
357         SocInfo soc = this._getSocService().getSoc(socId, context);
358         CourseOfferingInfo co = this.coService.getCourseOffering(courseOfferingId, context);
359         // main
360         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.MAIN_SOC_TYPE_KEY)) {
361             if (co.getTermId().equals(soc.getTermId())) {
362                 return true;
363             }
364             // TODO: handle sub-terms  before returning false
365             return false;
366         }
367         // subject area
368         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.SUBJECT_AREA_SOC_TYPE_KEY)) {
369             if (co.getTermId().equals(soc.getTermId())) {
370                 if (co.getSubjectArea().equals(soc.getSubjectArea())) {
371                     return true;
372                 }
373             }
374             // TODO: handle sub-terms  before returning false
375             return false;
376         }
377         // units content owner
378         if (soc.getTypeKey().equals(CourseOfferingSetServiceConstants.UNITS_CONTENT_OWNER_SOC_TYPE_KEY)) {
379             if (co.getTermId().equals(soc.getTermId())) {
380                 if (co.getUnitsContentOwnerOrgIds().equals(soc.getUnitsContentOwnerId())) {
381                     return true;
382                 }
383             }
384             // TODO: handle sub-terms before returning false
385             return false;
386         }
387         // else get all of them and check if in the list
388         List<String> ids = this.getCourseOfferingIdsBySoc(socId, context);
389         return ids.contains(courseOfferingId);
390     }
391 
392     @Override
393     public List<String> getPublishedCourseOfferingIdsBySoc(String socId, ContextInfo context)
394             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
395             PermissionDeniedException {
396         List<String> list = new ArrayList<String>();
397         List<String> list2 = this.getCourseOfferingIdsBySoc(socId, context);
398         for (CourseOfferingInfo info : this.coService.getCourseOfferingsByIds(list2, context)) {
399             // TODO: add the published course offering state to the constants 
400 //            if (info.getStateKey().equals(CourseOfferingServiceConstants.PUBLISHED_STATE_KEY) {
401             list.add(info.getId());
402 //            }
403         }
404         return list;
405     }
406 
407     @Override
408     public List<String> getUnpublishedCourseOfferingIdsBySoc(String socId, ContextInfo context)
409             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
410             PermissionDeniedException {
411         List<String> list = new ArrayList<String>();
412         List<String> list2 = this.getCourseOfferingIdsBySoc(socId, context);
413         for (CourseOfferingInfo info : this.coService.getCourseOfferingsByIds(list2, context)) {
414             // TODO: add the published course offering state to the constants 
415 //            if (info.getStateKey().equals(CourseOfferingServiceConstants.PUBLISHED_STATE_KEY) {
416             list.add(info.getId());
417 //            }
418         }
419         return list;
420     }
421 
422     @Override
423     public List<String> getUnpublishedActivityOfferingIdsBySoc(String socId, ContextInfo context)
424             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
425             PermissionDeniedException {
426         List<String> list = new ArrayList<String>();
427         List<String> list2 = this.getCourseOfferingIdsBySoc(socId, context);
428         for (String coId : list2) {
429             for (ActivityOfferingInfo ao : this.coService.getActivityOfferingsByCourseOffering(coId, context)) {
430                 // TODO: add the published course offering state to the constants 
431 //            if (!ao.getStateKey().equals(CourseOfferingServiceConstants.PUBLISHED_STATE_KEY) {
432                 list.add(ao.getId());
433 //            }
434             }
435         }
436         return list;
437     }
438 
439     @Override
440     public List<String> getUnscheduledActivityOfferingIdsBySoc(String socId, ContextInfo context)
441             throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException,
442             PermissionDeniedException {
443         List<String> list = new ArrayList<String>();
444         List<String> list2 = this.getCourseOfferingIdsBySoc(socId, context);
445         for (String coId : list2) {
446             for (ActivityOfferingInfo ao : this.coService.getActivityOfferingsByCourseOffering(coId, context)) {
447                 // TODO: add the published course offering state to the constants 
448 //            if (!ao.getStateKey().equals(CourseOfferingServiceConstants.SCHEDULED_STATE_KEY) {
449                 list.add(ao.getId());
450 //            }
451             }
452         }
453         return list;
454     }
455 
456     @Override
457     public StatusInfo startScheduleSoc(String socId, List<String> optionKeys,  ContextInfo context) throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException, PermissionDeniedException {
458         //  Validate SOC. Ensure there is a valid Soc for the given id and make sure the state and scheduling state are correct.
459         SocInfo socInfo = this._getSocService().getSoc(socId, context);
460         if ( ! StringUtils.equals(socInfo.getStateKey(), CourseOfferingSetServiceConstants.LOCKED_SOC_STATE_KEY)) {
461             throw new OperationFailedException(String.format("SOC state [%s] was invalid for mass schduling.", socInfo.getStateKey()));
462         }
463 
464         if (! StringUtils.equals(socInfo.getSchedulingStateKey(), CourseOfferingSetServiceConstants.SOC_SCHEDULING_STATE_IN_PROGRESS)) {
465             throw new OperationFailedException(String.format("SOC scheduling state [%s] was invalid for mass scheduling.", socInfo.getStateKey()));
466         }
467 
468         final CourseOfferingSetSchedulingRunner schedulingRunner = new CourseOfferingSetSchedulingRunner(socInfo.getId());
469         schedulingRunner.setContextInfo(context);
470         schedulingRunner.setCoService(coService);
471         schedulingRunner.setSchedulingService(schedulingService);
472         schedulingRunner.setSocService(this._getSocService());
473 
474         //Try to run this after the transaction completes
475         KSThreadRunnerAfterTransactionSynchronization.runAfterTransactionCompletes(schedulingRunner);
476 
477         StatusInfo status = new StatusInfo();
478         status.setMessage("Scheduling runner started successfully");
479         return status;
480     }
481 }