1
2
3
4
5 package org.kuali.student.enrollment.class2.courseofferingset.service.impl;
6
7 import org.apache.commons.lang.StringUtils;
8 import org.apache.log4j.Logger;
9 import org.kuali.student.enrollment.class2.courseofferingset.service.facade.RolloverAssist;
10 import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
11 import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
12 import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultInfo;
13 import org.kuali.student.enrollment.courseofferingset.dto.SocRolloverResultItemInfo;
14 import org.kuali.student.enrollment.courseofferingset.service.CourseOfferingSetService;
15 import org.kuali.student.r2.common.dto.AttributeInfo;
16 import org.kuali.student.r2.common.dto.ContextInfo;
17 import org.kuali.student.r2.common.dto.ValidationResultInfo;
18 import org.kuali.student.r2.common.exceptions.AlreadyExistsException;
19 import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
20 import org.kuali.student.r2.common.exceptions.InvalidParameterException;
21 import org.kuali.student.r2.common.exceptions.OperationFailedException;
22 import org.kuali.student.r2.common.util.RichTextHelper;
23 import org.kuali.student.r2.common.util.constants.CourseOfferingSetServiceConstants;
24 import org.kuali.student.r2.core.acal.service.AcademicCalendarService;
25 import org.kuali.student.r2.lum.course.service.CourseService;
26
27 import java.util.ArrayList;
28 import java.util.Date;
29 import java.util.List;
30
31
32
33
34
35 public class CourseOfferingRolloverRunner implements Runnable {
36
37 final static Logger logger = Logger.getLogger(CourseOfferingRolloverRunner.class);
38 private CourseOfferingService coService;
39 private CourseOfferingSetService socService;
40 private CourseService courseService;
41 private AcademicCalendarService acalService;
42 private ContextInfo context;
43 private SocRolloverResultInfo result;
44 private RolloverAssist rolloverAssist;
45
46 public RolloverAssist getRolloverAssist() {
47 return rolloverAssist;
48 }
49
50 public void setRolloverAssist(RolloverAssist rolloverAssist) {
51 this.rolloverAssist = rolloverAssist;
52 }
53
54 public CourseOfferingService getCoService() {
55 return coService;
56 }
57
58 public void setCoService(CourseOfferingService coService) {
59 this.coService = coService;
60 }
61
62 public CourseOfferingSetService getSocService() {
63 return socService;
64 }
65
66 public void setSocService(CourseOfferingSetService socService) {
67 this.socService = socService;
68 }
69
70 public CourseService getCourseService() {
71 return courseService;
72 }
73
74 public void setCourseService(CourseService courseService) {
75 this.courseService = courseService;
76 }
77
78 public AcademicCalendarService getAcalService() {
79 return acalService;
80 }
81
82 public void setAcalService(AcademicCalendarService acalService) {
83 this.acalService = acalService;
84 }
85
86 public ContextInfo getContext() {
87 return context;
88 }
89
90 public void setContext(ContextInfo context) {
91 this.context = context;
92 }
93
94 public SocRolloverResultInfo getResult() {
95 return result;
96 }
97
98 public void setResult(SocRolloverResultInfo result) {
99 this.result = result;
100 }
101
102 private void loadOptionKeys() {
103 this.skipIfAlreadyExists = getBooleanOption(CourseOfferingSetServiceConstants.SKIP_IF_ALREADY_EXISTS_OPTION_KEY, false);
104 this.logSuccesses = getBooleanOption(CourseOfferingSetServiceConstants.LOG_SUCCESSES_OPTION_KEY, false);
105 this.progressFrequency = getIntOption(CourseOfferingSetServiceConstants.LOG_FREQUENCY_OPTION_KEY_PREFIX, 10);
106 this.haltErrorsMax = getIntOption(CourseOfferingSetServiceConstants.HALT_ERRORS_MAX_OPTION_KEY_PREFIX, -1);
107
108 }
109
110 private boolean skipIfAlreadyExists = false;
111 private boolean logSuccesses = false;
112 private int progressFrequency = 100;
113 private int haltErrorsMax = -1;
114
115 private boolean getBooleanOption(String key, boolean defValue) {
116 for (String optionKey : this.result.getOptionKeys()) {
117 if (optionKey.equals(key)) {
118 return true;
119 }
120 }
121
122 return defValue;
123 }
124
125 private int getIntOption(String keyPrefix, int defValue) {
126 for (String optionKey : this.result.getOptionKeys()) {
127 if (optionKey.startsWith(keyPrefix)) {
128 return Integer.parseInt(optionKey);
129 }
130 }
131
132 return defValue;
133 }
134
135
136
137
138 @Override
139 public void run() {
140 try {
141 runInternal();
142 } catch (Exception ex) {
143 try {
144 this.result = socService.getSocRolloverResult(result.getId(), context);
145 this.result.setStateKey(CourseOfferingSetServiceConstants.ABORTED_RESULT_STATE_KEY);
146 this.result.setDateCompleted(new Date());
147 this.result.setMessage(new RichTextHelper().fromPlain("Got an unexpected exception running rollover:\n"
148 + ex.toString()));
149 this.socService.updateSocRolloverResult(result.getId(), result, context);
150 } catch (Exception ex1) {
151 logger.fatal(result, ex);
152 throw new RuntimeException(ex1);
153 }
154 }
155 }
156
157 private String _computeDiffInSeconds(Date start, Date end) {
158 long diffInMillis = end.getTime() - start.getTime();
159 int seconds = (int)(diffInMillis / 1000);
160 int fraction = (int)(diffInMillis % 1000);
161 String fractionStr = "" + fraction;
162 while (fractionStr.length() < 3) {
163 fractionStr = "0" + fractionStr;
164 }
165 return seconds + "." + fractionStr + "s";
166 }
167
168 private void _removeRolloverAssistIdFromContext(ContextInfo contextInfo) {
169 int index = 0;
170 for (AttributeInfo attr: contextInfo.getAttributes()) {
171 if (attr.getKey().equals(CourseOfferingSetServiceConstants.ROLLOVER_ASSIST_ID_DYNATTR_KEY)) {
172 contextInfo.getAttributes().remove(index);
173 break;
174 }
175 index++;
176 }
177 }
178 private void runInternal() throws Exception {
179 if (this.context == null) {
180 throw new NullPointerException("context not set");
181 }
182 loadOptionKeys();
183
184 String resultId = result.getId();
185 result = this.socService.getSocRolloverResult(resultId, context);
186 result.setStateKey(CourseOfferingSetServiceConstants.RUNNING_RESULT_STATE_KEY);
187 this.socService.updateSocRolloverResult(result.getId(), result, context);
188
189 if (!skipIfAlreadyExists) {
190 List<String> targetCoIds = socService.getCourseOfferingIdsBySoc(this.result.getTargetSocId(), context);
191 if (!targetCoIds.isEmpty()) {
192 throw new OperationFailedException(targetCoIds.size() + " course offerings already exist in the target soc");
193 }
194 }
195
196 List<String> sourceCoIds = socService.getCourseOfferingIdsBySoc(this.result.getSourceSocId(), context);
197 result = this.socService.getSocRolloverResult(result.getId(), context);
198 result.setItemsProcessed(0);
199 result.setItemsExpected(sourceCoIds.size());
200 this.socService.updateSocRolloverResult(result.getId(), result, context);
201
202
203 int sourceCoIdsHandled = 0;
204 int aoRolledOver = 0;
205 int errors = 0;
206 List<SocRolloverResultItemInfo> items = new ArrayList<SocRolloverResultItemInfo>();
207 int count = 1;
208 Date origStart = new Date();
209
210 String rolloverAssistId = rolloverAssist.getRolloverId();
211 AttributeInfo attr = new AttributeInfo();
212 attr.setKey(CourseOfferingSetServiceConstants.ROLLOVER_ASSIST_ID_DYNATTR_KEY);
213 attr.setValue(rolloverAssistId);
214 context.getAttributes().add(attr);
215 Date start = origStart;
216 for (String sourceCoId : sourceCoIds) {
217
218 try {
219 SocRolloverResultItemInfo item = rolloverOneCourseOfferingReturningItem(sourceCoId);
220 Date end = new Date();
221 String timeInSeconds = _computeDiffInSeconds(start, end);
222 start = end;
223 logger.info("(" + count + ") Processing: " + sourceCoId + " (" + timeInSeconds + ")");
224
225 items.add(item);
226 reportProgressIfModulo(items, sourceCoIdsHandled);
227 if (!CourseOfferingSetServiceConstants.SUCCESSFUL_RESULT_ITEM_STATES.contains(item.getStateKey())) {
228 errors++;
229 if (this.haltErrorsMax != -1) {
230 if (errors > this.haltErrorsMax) {
231 throw new OperationFailedException("Too many errors, exceeded the halt threshold: " + errors
232 + " out of " + sourceCoIdsHandled + " course offerings rolled over");
233 }
234 }
235 }
236 else {
237 String aoCountStr = item.getAttributeValue(CourseOfferingSetServiceConstants.ACTIVITY_OFFERINGS_CREATED_SOC_ITEM_DYNAMIC_ATTRIBUTE);
238 int aoCount = Integer.parseInt(aoCountStr);
239 aoRolledOver += aoCount;
240 }
241 } catch (Exception ex) {
242
243 logger.fatal("failed while processing the " + sourceCoIdsHandled + "th course offering " + sourceCoId, ex);
244 throw ex;
245 }
246 sourceCoIdsHandled++;
247 count++;
248 }
249 Date end = new Date();
250
251 String totalTime = _computeTotalTimeString(origStart, end);
252
253 logger.info("======= Finished processing rollover ======= (" + totalTime + ")");
254 _removeRolloverAssistIdFromContext(context);
255 reportProgress(items, sourceCoIdsHandled - errors);
256
257 result = socService.getSocRolloverResult(result.getId(), context);
258 result.setDateCompleted(new Date());
259 result.setCourseOfferingsCreated(sourceCoIdsHandled - errors);
260 result.setCourseOfferingsSkipped(errors);
261 result.setActivityOfferingsCreated(aoRolledOver);
262 result.setActivityOfferingsSkipped(0);
263 result.setStateKey(CourseOfferingSetServiceConstants.FINISHED_RESULT_STATE_KEY);
264 this.socService.updateSocRolloverResult(result.getId(), result, context);
265 }
266
267 private String _computeTotalTimeString(Date start, Date end) {
268 long diffInMillis = end.getTime() - start.getTime();
269 int seconds = (int)(diffInMillis / 1000);
270 int fraction = (int)(diffInMillis % 1000);
271 String fractionStr = "" + fraction;
272 while (fractionStr.length() < 3) {
273 fractionStr = "0" + fractionStr;
274 }
275 int minutes = seconds / 60;
276 seconds = seconds % 60;
277 int hours = minutes / 60;
278 minutes = hours % 60;
279 StringBuilder result = new StringBuilder();
280 if (hours > 0) {
281 result.append(hours);
282 result.append("h, ");
283 }
284 if (hours > 0 || minutes > 0) {
285 result.append(minutes);
286 result.append("m, ");
287 }
288 result.append(seconds);
289 result.append(".");
290 result.append(fractionStr);
291 result.append("s");
292 String str = result.toString();
293 return str;
294 }
295
296 private void reportProgressIfModulo(List<SocRolloverResultItemInfo> items, int i) throws Exception {
297 int modulo = i % progressFrequency;
298 if (modulo != 0) {
299 return;
300 }
301 this.reportProgress(items, i);
302 }
303
304 private void reportProgress(List<SocRolloverResultItemInfo> items, int i) throws Exception {
305 this.socService.updateSocRolloverProgress(result.getId(), i, context);
306 if (!this.logSuccesses) {
307 stripSuccesses(items);
308 }
309 if (!items.isEmpty()) {
310 Integer count = this.socService.createSocRolloverResultItems(result.getId(),
311 CourseOfferingSetServiceConstants.CREATE_RESULT_ITEM_TYPE_KEY,
312 items,
313 context);
314 }
315 items.clear();
316 }
317
318 private void stripSuccesses(List<SocRolloverResultItemInfo> items) {
319 List<SocRolloverResultItemInfo> list = new ArrayList<SocRolloverResultItemInfo>();
320 for (SocRolloverResultItemInfo item : items) {
321 if (!CourseOfferingSetServiceConstants.SUCCESSFUL_RESULT_ITEM_STATES.contains(item.getStateKey())) {
322 list.add(item);
323 }
324 }
325 if (items.size() != list.size()) {
326 items.clear();
327 items.addAll(list);
328 }
329 }
330
331 private SocRolloverResultItemInfo rolloverOneCourseOfferingReturningItem(String sourceCoId) throws Exception {
332 String error = null;
333 try {
334 SocRolloverResultItemInfo item = this.coService.rolloverCourseOffering(sourceCoId,
335 this.result.getTargetTermId(),
336 this.result.getOptionKeys(),
337 context);
338 item.setSocRolloverResultId(result.getId());
339 return item;
340 } catch (AlreadyExistsException ex) {
341 error = ex.getMessage();
342 } catch (DataValidationErrorException ex) {
343 boolean firstTime = true;
344
345
346
347 StringBuilder errorBuffer = new StringBuilder("Validation error(s): ");
348 if (!StringUtils.isBlank(ex.getMessage())){
349 errorBuffer.append(ex.getMessage());
350 firstTime = false;
351 }
352 for (ValidationResultInfo info: ex.getValidationResults()) {
353 if (firstTime) {
354 firstTime = false;
355 } else {
356 errorBuffer.append(", ");
357 }
358
359 errorBuffer.append(info.getElement() + " has bad data: " + info.getInvalidData());
360 }
361 error = errorBuffer.toString();
362 } catch (InvalidParameterException ex) {
363 error = ex.getMessage();
364 } catch (Exception ex) {
365
366
367 error = "Unexpected error rolling over course";
368 String mesg = ex.getMessage();
369 if (mesg != null) {
370 error += ": (" + mesg + ")";
371 }
372 logger.warn("Unexpected error rolling over course", ex);
373 }
374
375 SocRolloverResultItemInfo item = new SocRolloverResultItemInfo();
376 item.setSocRolloverResultId(result.getId());
377 item.setSourceCourseOfferingId(sourceCoId);
378 item.setTypeKey(CourseOfferingSetServiceConstants.CREATE_RESULT_ITEM_TYPE_KEY);
379 item.setStateKey(CourseOfferingSetServiceConstants.ERROR_RESULT_ITEM_STATE_KEY);
380 item.setTargetCourseOfferingId(null);
381 item.setMessage(new RichTextHelper().fromPlain(error));
382 return item;
383 }
384 }