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.log4j.Logger;
8   import java.util.ArrayList;
9   import java.util.List;
10  
11  import org.kuali.student.enrollment.acal.dto.TermInfo;
12  import org.kuali.student.enrollment.acal.service.AcademicCalendarService;
13  import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
14  import org.kuali.student.enrollment.courseoffering.dto.FormatOfferingInfo;
15  import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
16  import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultInfo;
17  import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultItemInfo;
18  import org.kuali.student.enrollment.courseofferingset.service.CourseOfferingSetService;
19  import org.kuali.student.r2.lum.course.dto.CourseInfo;
20  import org.kuali.student.r2.lum.course.service.CourseService;
21  import org.kuali.student.r2.common.dto.ContextInfo;
22  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
23  import org.kuali.student.r2.common.exceptions.OperationFailedException;
24  import org.kuali.student.r2.common.util.RichTextHelper;
25  import org.kuali.student.r2.common.util.constants.CourseOfferingSetServiceConstants;
26  
27  /**
28   *
29   * @author nwright
30   */
31  public class CourseOfferingReverseRolloverRunner implements Runnable {
32  
33      final static Logger logger = Logger.getLogger(CourseOfferingRolloverRunner.class);
34      private CourseOfferingService coService;
35      private CourseOfferingSetService socService;
36      private CourseService courseService;
37      private AcademicCalendarService acalService;
38      private ContextInfo context;
39      private SocRolloverResultInfo rolloverResult;
40      private SocRolloverResultInfo reverseResult;
41  
42      public CourseOfferingService getCoService() {
43          return coService;
44      }
45  
46      public void setCoService(CourseOfferingService coService) {
47          this.coService = coService;
48      }
49  
50      public CourseOfferingSetService getSocService() {
51          return socService;
52      }
53  
54      public void setSocService(CourseOfferingSetService socService) {
55          this.socService = socService;
56      }
57  
58      public CourseService getCourseService() {
59          return courseService;
60      }
61  
62      public void setCourseService(CourseService courseService) {
63          this.courseService = courseService;
64      }
65  
66      public AcademicCalendarService getAcalService() {
67          return acalService;
68      }
69  
70      public void setAcalService(AcademicCalendarService acalService) {
71          this.acalService = acalService;
72      }
73  
74      public ContextInfo getContext() {
75          return context;
76      }
77  
78      public void setContext(ContextInfo context) {
79          this.context = context;
80      }
81  
82      public SocRolloverResultInfo getReverseResult() {
83          return reverseResult;
84      }
85  
86      public void setReverseResult(SocRolloverResultInfo reverseResult) {
87          this.reverseResult = reverseResult;
88      }
89  
90      public SocRolloverResultInfo getRolloverResult() {
91          return rolloverResult;
92      }
93  
94      public void setRolloverResult(SocRolloverResultInfo rolloverResult) {
95          this.rolloverResult = rolloverResult;
96      }
97  
98      private void loadOptions() {
99          this.justCreates = getBooleanOption(CourseOfferingSetServiceConstants.REVERSE_JUST_CREATES_OPTION_KEY, false);
100         this.logSuccesses = getBooleanOption(CourseOfferingSetServiceConstants.LOG_SUCCESSES_OPTION_KEY, false);
101         this.progressFrequency = getIntOption(CourseOfferingSetServiceConstants.LOG_FREQUENCY_OPTION_KEY_PREFIX, 10);
102         this.haltErrorsMax = getIntOption(CourseOfferingSetServiceConstants.HALT_ERRORS_MAX_OPTION_KEY_PREFIX, 10);
103 
104     }
105     // TODO: implement these options
106     private boolean justCreates = false;
107     private boolean logSuccesses = false; // implemented
108     private int progressFrequency = 100; // implemented
109     private int haltErrorsMax = -1; // implemented
110 
111     private boolean getBooleanOption(String key, boolean defValue) {
112         for (String optionKey : this.reverseResult.getOptionKeys()) {
113             if (optionKey.equals(key)) {
114                 return true;
115             }
116         }
117         // default
118         return defValue;
119     }
120 
121     private int getIntOption(String keyPrefix, int defValue) {
122         for (String optionKey : this.reverseResult.getOptionKeys()) {
123             if (optionKey.startsWith(keyPrefix)) {
124                 return Integer.parseInt(optionKey);
125             }
126         }
127         // default
128         return defValue;
129     }
130 
131     ////
132     //// implement the run method
133     ////  
134     @Override
135     public void run() {
136         try {
137             runInternal();
138         } catch (Exception ex) {
139             try {
140                 reverseResult = this.socService.getSocRolloverResult(reverseResult.getId(), context);
141                 this.reverseResult.setStateKey(CourseOfferingSetServiceConstants.ABORTED_RESULT_STATE_KEY);
142                 this.reverseResult.setMessage(new RichTextHelper().fromPlain("Got an unexpected exception running rolloever:\n" +
143                         ex.toString()));
144                 this.socService.updateSocRolloverResult(reverseResult.getId(), reverseResult, context);
145             } catch (Exception ex1) {
146                 logger.fatal(reverseResult, ex);
147                 throw new RuntimeException(ex1);
148             }
149         }
150     }
151 
152     private void runInternal() throws Exception {
153         this.loadOptions();
154         // mark running
155         reverseResult.setStateKey(CourseOfferingSetServiceConstants.RUNNING_RESULT_STATE_KEY);
156         this.socService.updateSocRolloverResult(reverseResult.getId(), reverseResult, context);
157         // Check if there are any course in the target soc
158         List<String> targetCoIds = socService.getCourseOfferingIdsBySoc(this.reverseResult.getTargetSocId(), context);
159         reverseResult.setItemsProcessed(0);
160         reverseResult.setItemsExpected(targetCoIds.size());
161         this.socService.updateSocRolloverResult(reverseResult.getId(), reverseResult, context);
162 
163         // Start processing
164         int i = 0;
165         int errors = 0;
166         List<SocRolloverResultItemInfo> items = new ArrayList<SocRolloverResultItemInfo>();
167         for (String targetCoId : targetCoIds) {
168             logger.info("Processing" + targetCoId);
169             try {
170                 SocRolloverResultItemInfo item = reverseOneCourseOffering(targetCoId);
171                 items.add(item);
172                 reportProgressIfModulo(items, i);
173                 if (!CourseOfferingSetServiceConstants.SUCCESSFUL_RESULT_ITEM_STATES.contains(item.getStateKey())) {
174                     errors++;
175                     if (this.haltErrorsMax != -1) {
176                         if (errors > this.haltErrorsMax) {
177                             throw new OperationFailedException("Too many errors, exceeded the halt threshold: " + errors +
178                                     " out of " + i + " course offerings rolled over");
179                         }
180                     }
181                 }
182             } catch (Exception ex) {
183                 // log some conetxt for the exception
184                 logger.fatal("failed while processing the " + i + "th course offering " + targetCoId, ex);
185                 throw ex;
186             }
187             i++;
188         }
189         reportProgress(items, i);
190         // mark finished
191         reverseResult = this.socService.getSocRolloverResult(reverseResult.getId(), context);
192         reverseResult.setStateKey(CourseOfferingSetServiceConstants.FINISHED_RESULT_STATE_KEY);
193         this.socService.updateSocRolloverResult(reverseResult.getId(), reverseResult, context);
194     }
195 
196     private void reportProgressIfModulo(List<SocRolloverResultItemInfo> items, int i) throws Exception {
197         int modulo = i % progressFrequency;
198         if (modulo != 0) {
199             return;
200         }
201         this.reportProgress(items, i);
202     }
203 
204     private void reportProgress(List<SocRolloverResultItemInfo> items, int i) throws Exception {
205         this.socService.updateSocRolloverProgress(reverseResult.getId(), i, context);
206         if (!this.logSuccesses) {
207             stripSuccesses(items);
208         }
209         if (!items.isEmpty()) {
210             this.socService.createSocRolloverResultItems(reverseResult.getId(),
211                     CourseOfferingSetServiceConstants.DELETE_RESULT_ITEM_TYPE_KEY,
212                     items,
213                     context);
214         }
215         items.clear();
216     }
217 
218     private void stripSuccesses(List<SocRolloverResultItemInfo> items) {
219         List<SocRolloverResultItemInfo> list = new ArrayList<SocRolloverResultItemInfo>();
220         for (SocRolloverResultItemInfo item : items) {
221             if (!CourseOfferingSetServiceConstants.SUCCESSFUL_RESULT_ITEM_STATES.contains(item.getStateKey())) {
222                 list.add(item);
223             }
224         }
225         if (items.size() != list.size()) {
226             items.clear();
227             items.addAll(list);
228         }
229     }
230 
231     //
232     // TODO: push this logic into the course offering service 
233     private SocRolloverResultItemInfo reverseOneCourseOffering(String coId) throws Exception {
234         if (this.justCreates) {
235             if (!this.wasCreatedInRollover(coId)) {
236                 SocRolloverResultItemInfo item = new SocRolloverResultItemInfo();
237                 item.setSocRolloverResultId(reverseResult.getId());
238                 item.setTypeKey(CourseOfferingSetServiceConstants.DELETE_RESULT_ITEM_TYPE_KEY);
239                 item.setStateKey(CourseOfferingSetServiceConstants.INFO_RESULT_ITEM_STATE_KEY);
240                 item.setSourceCourseOfferingId(coId);
241                 item.setTargetCourseOfferingId(coId);
242                 item.setMessage(new RichTextHelper().fromPlain(
243                         "Skipped because course offering was not created in the original rollover"));
244                 return item;
245             }
246         }
247         // TODO: add a cascading delete for course offferings
248         for (FormatOfferingInfo fo : this.coService.getFormatOfferingsByCourseOffering(coId, context)) {
249             for (ActivityOfferingInfo ao : this.coService.getActivityOfferingsByFormatOffering(fo.getId(), context)) {
250                 coService.deleteActivityOffering(ao.getId(), context);
251             }
252             coService.deleteFormatOffering(fo.getId(), context);
253         }
254         this.coService.deleteCourseOffering(coId, context);
255         //
256         SocRolloverResultItemInfo item = new SocRolloverResultItemInfo();
257         item.setSocRolloverResultId(reverseResult.getId());
258         item.setTypeKey(CourseOfferingSetServiceConstants.DELETE_RESULT_ITEM_TYPE_KEY);
259         item.setStateKey(CourseOfferingSetServiceConstants.DELETED_RESULT_ITEM_STATE_KEY);
260         item.setSourceCourseOfferingId(coId);
261         item.setTargetCourseOfferingId(coId);
262         return item;
263     }
264 
265     private boolean wasCreatedInRollover(String coId) throws Exception {
266         List<SocRolloverResultItemInfo> list = this.socService.getSocRolloverResultItemsByResultIdAndTargetCourseOfferingId(
267                 rolloverResult.getId(), coId, context);
268         if (list.isEmpty()) {
269             return false;
270         }
271         for (SocRolloverResultItemInfo item : list) {
272             if (coId.equals(item.getTargetCourseOfferingId())) {
273                 if (CourseOfferingSetServiceConstants.SUCCESSFUL_RESULT_ITEM_STATES.contains(item.getStateKey())) {
274                     if (item.getTypeKey().equals(CourseOfferingSetServiceConstants.CREATE_RESULT_ITEM_TYPE_KEY)) {
275                         return true;
276                     }
277                 }
278             }
279         }
280         return false;
281     }
282     // deal with R1 exception converting them to R2 exceptions
283 
284     private CourseInfo getCourse(String courseId) throws Exception {
285         CourseInfo course = null;
286         try {
287             course = courseService.getCourse(courseId, context);
288         } catch (org.kuali.student.r2.common.exceptions.DoesNotExistException e) {
289             throw new DoesNotExistException("The course does not exist. course: " + courseId, e);
290         } catch (Exception e) {
291             throw new OperationFailedException("unxpected trying to get course " + courseId, e);
292         }
293         return course;
294     }
295 }