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