View Javadoc

1   /**
2    * Copyright 2012 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   *
15   */
16  package org.kuali.student.enrollment.class2.acal.service.impl;
17  
18  import org.apache.commons.lang.BooleanUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.math.NumberUtils;
21  import org.apache.commons.lang.time.DateFormatUtils;
22  import org.apache.log4j.Logger;
23  import org.joda.time.DateMidnight;
24  import org.joda.time.DateTimeConstants;
25  import org.kuali.rice.core.api.criteria.Predicate;
26  import org.kuali.rice.core.api.criteria.QueryByCriteria;
27  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
28  import org.kuali.rice.core.api.util.ConcreteKeyValue;
29  import org.kuali.rice.core.api.util.KeyValue;
30  import org.kuali.rice.krad.uif.UifConstants;
31  import org.kuali.rice.krad.uif.container.CollectionGroup;
32  import org.kuali.rice.krad.uif.control.SelectControl;
33  import org.kuali.rice.krad.uif.field.InputField;
34  import org.kuali.rice.krad.uif.util.ComponentFactory;
35  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
36  import org.kuali.rice.krad.uif.view.View;
37  import org.kuali.rice.krad.util.GlobalVariables;
38  import org.kuali.rice.krad.util.KRADConstants;
39  import org.kuali.student.common.uif.service.impl.KSViewHelperServiceImpl;
40  import org.kuali.student.enrollment.class2.acal.dto.AcademicTermWrapper;
41  import org.kuali.student.enrollment.class2.acal.dto.AcalEventWrapper;
42  import org.kuali.student.enrollment.class2.acal.dto.ExamPeriodWrapper;
43  import org.kuali.student.enrollment.class2.acal.dto.HolidayCalendarWrapper;
44  import org.kuali.student.enrollment.class2.acal.dto.HolidayWrapper;
45  import org.kuali.student.enrollment.class2.acal.dto.KeyDateWrapper;
46  import org.kuali.student.enrollment.class2.acal.dto.KeyDatesGroupWrapper;
47  import org.kuali.student.enrollment.class2.acal.dto.TimeSetWrapper;
48  import org.kuali.student.enrollment.class2.acal.form.AcademicCalendarForm;
49  import org.kuali.student.enrollment.class2.acal.service.AcademicCalendarViewHelperService;
50  import org.kuali.student.enrollment.class2.acal.util.AcalCommonUtils;
51  import org.kuali.student.enrollment.class2.acal.util.CalendarConstants;
52  import org.kuali.student.r2.common.dto.ContextInfo;
53  import org.kuali.student.r2.common.util.date.DateFormatters;
54  import org.kuali.student.r2.core.acal.dto.AcademicCalendarInfo;
55  import org.kuali.student.r2.core.acal.dto.AcalEventInfo;
56  import org.kuali.student.r2.core.acal.dto.ExamPeriodInfo;
57  import org.kuali.student.r2.core.acal.dto.HolidayCalendarInfo;
58  import org.kuali.student.r2.core.acal.dto.HolidayInfo;
59  import org.kuali.student.r2.core.acal.dto.KeyDateInfo;
60  import org.kuali.student.r2.core.acal.dto.TermInfo;
61  import org.kuali.student.r2.core.acal.service.AcademicCalendarService;
62  import org.kuali.student.r2.core.acal.service.TermCodeGenerator;
63  import org.kuali.student.r2.core.acal.service.impl.TermCodeGeneratorImpl;
64  import org.kuali.student.r2.core.atp.dto.AtpAtpRelationInfo;
65  import org.kuali.student.r2.core.atp.service.AtpService;
66  import org.kuali.student.r2.core.class1.state.dto.StateInfo;
67  import org.kuali.student.r2.core.class1.type.dto.TypeInfo;
68  import org.kuali.student.r2.core.class1.type.dto.TypeTypeRelationInfo;
69  import org.kuali.student.r2.core.class1.type.service.TypeService;
70  import org.kuali.student.r2.core.constants.AcademicCalendarServiceConstants;
71  import org.kuali.student.r2.core.constants.AtpServiceConstants;
72  import org.kuali.student.r2.core.constants.TypeServiceConstants;
73  
74  import javax.xml.namespace.QName;
75  import java.util.ArrayList;
76  import java.util.Calendar;
77  import java.util.Collections;
78  import java.util.Comparator;
79  import java.util.Date;
80  import java.util.HashMap;
81  import java.util.List;
82  import java.util.Map;
83  
84  import static org.kuali.rice.core.api.criteria.PredicateFactory.and;
85  import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
86  import static org.kuali.rice.core.api.criteria.PredicateFactory.equalIgnoreCase;
87  
88  
89  /**
90   * This is the helper class for AcademicCalendar Controller
91   *
92   * @author Kuali Student Team
93   */
94  public class AcademicCalendarViewHelperServiceImpl extends KSViewHelperServiceImpl implements AcademicCalendarViewHelperService {
95  
96      private final static Logger LOG = Logger.getLogger(AcademicCalendarViewHelperServiceImpl.class);
97  
98      private AcademicCalendarService acalService;
99      private TypeService typeService;
100     private AtpService atpService;
101     private TermCodeGenerator termCodeGenerator;
102 
103     public AcademicCalendarViewHelperServiceImpl getInstance(){
104         return this;
105     }
106 
107     /**
108      * This method builds an academic calendar for ui processing. Basically, it builds the wrappers
109      * around acal,events,terms,holidays,keydates etc.
110      *
111      * @param acalId academic calendar id
112      * @param acalForm AcademicCalendarForm
113      */
114     public void populateAcademicCalendar(String acalId, AcademicCalendarForm acalForm){
115 
116         if (LOG.isDebugEnabled()){
117             LOG.debug("Loading Academic calendar for the id " + acalId);
118         }
119 
120         try{
121 
122             AcademicCalendarInfo acalInfo = getAcalService().getAcademicCalendar(acalId, createContextInfo());
123 
124             acalForm.setAcademicCalendarInfo(acalInfo);
125             acalForm.setAdminOrgName(getAdminOrgNameById(acalInfo.getAdminOrgId()));
126             acalForm.setNewCalendar(false);
127             acalForm.setOfficialCalendar(StringUtils.equals(acalInfo.getStateKey(),AtpServiceConstants.ATP_OFFICIAL_STATE_KEY));
128 
129             //Events
130             List<AcalEventWrapper> events = populateEventWrappers(acalInfo.getId());
131             acalForm.setEvents(events);
132 
133             //Holiday calendars associated with acal.
134             List<HolidayCalendarWrapper> holidayCalendarWrapperList = populateHolidayCalendars(acalInfo.getHolidayCalendarIds());
135             acalForm.setHolidayCalendarList(holidayCalendarWrapperList);
136 
137             //Terms (which in turn builds keydate groups and keydates)
138             boolean calculateInstrDays = !holidayCalendarWrapperList.isEmpty();
139             List<AcademicTermWrapper> termWrappers = populateTermWrappers(acalId, false,true);
140             acalForm.setTermWrapperList(termWrappers);
141 
142             // set the meta info on the form
143             acalForm.setMeta(acalInfo.getMeta());
144 
145         }catch(Exception e){
146             if (LOG.isDebugEnabled()){
147                 LOG.debug("Error loading academic calendar [id=" + acalId + "] - " + e.getMessage());
148             }
149             throw convertServiceExceptionsToUI(e);
150         }
151 
152     }
153 
154     /**
155      * Builds the wrappers for all the holiday calendars associated with acal.
156      *
157      * @param holidayCalendarIds list of holiday calendars to populate
158      * @return list of wrappers for the holiday calendars
159      * @throws Exception
160      */
161     protected List<HolidayCalendarWrapper> populateHolidayCalendars(List<String> holidayCalendarIds) throws Exception {
162 
163         if (LOG.isDebugEnabled()){
164             LOG.debug("Loading all the holiday calendars associated with the Acal");
165         }
166 
167         List<HolidayCalendarWrapper> holidayCalendarWrapperList = new ArrayList<HolidayCalendarWrapper>();
168 
169         ContextInfo contextInfo = createContextInfo();
170         for (String hcId : holidayCalendarIds){
171 
172             HolidayCalendarWrapper holidayCalendarWrapper = new HolidayCalendarWrapper();
173             List<HolidayWrapper> holidays = new ArrayList<HolidayWrapper>();
174 
175             //need to retrieve HolidayCalendarInfo and all Holidays to form the HolidayCalendarWrapper.
176             HolidayCalendarInfo holidayCalendarInfo = getAcalService().getHolidayCalendar(hcId, contextInfo);
177             holidayCalendarWrapper.setHolidayCalendarInfo(holidayCalendarInfo);
178             holidayCalendarWrapper.setId(holidayCalendarInfo.getId());
179             holidayCalendarWrapper.setAdminOrgName(AcalCommonUtils.getAdminOrgNameById(holidayCalendarInfo.getAdminOrgId()));
180             StateInfo hcState = getAcalService().getHolidayCalendarState(holidayCalendarInfo.getStateKey(), contextInfo);
181             holidayCalendarWrapper.setStateName(hcState.getName());
182 
183             List<HolidayInfo> holidayInfoList = getAcalService().getHolidaysForHolidayCalendar(holidayCalendarInfo.getId(), contextInfo);
184             for(HolidayInfo holidayInfo : holidayInfoList){
185                 HolidayWrapper holiday = new HolidayWrapper(holidayInfo);
186                 TypeInfo typeInfo = getAcalService().getHolidayType(holidayInfo.getTypeKey(), contextInfo);
187                 holiday.setTypeName(typeInfo.getName());
188                 holidays.add(holiday);
189             }
190 
191             holidayCalendarWrapper.setHolidays(holidays);
192             holidayCalendarWrapperList.add(holidayCalendarWrapper);
193         }
194 
195         return holidayCalendarWrapperList;
196 
197     }
198 
199     /**
200      * Builds the wrapper for Events
201      *
202      * @param acalId
203      * @return
204      * @throws Exception
205      */
206     public List<AcalEventWrapper> populateEventWrappers(String acalId) throws Exception {
207 
208         if (LOG.isDebugEnabled()){
209             LOG.debug("Loading all the holiday calendars associated with the Acal");
210         }
211 
212         List<AcalEventInfo> eventInfos = getAcalService().getAcalEventsForAcademicCalendar(acalId, createContextInfo());
213         List<AcalEventWrapper> events = new ArrayList<AcalEventWrapper>();
214 
215         for (AcalEventInfo eventInfo: eventInfos) {
216             AcalEventWrapper event  = new AcalEventWrapper(eventInfo,false);
217             TypeInfo type = getTypeInfo(event.getEventTypeKey());
218             event.setEventTypeName(type.getName());
219             events.add(event);
220         }
221 
222         return events;
223     }
224 
225     /**
226      * Builds wrappers around the terms
227      *
228      * @param acalId
229      * @param isCopy
230      * @return
231      */
232     public List<AcademicTermWrapper> populateTermWrappers(String acalId, boolean isCopy, boolean calculateInstrDays){
233         ContextInfo contextInfo = createContextInfo();
234 
235         if (LOG.isDebugEnabled()){
236             LOG.debug("Loading all the terms associated with an acal [id=" + acalId + "]");
237         }
238 
239         List<AcademicTermWrapper> termWrappers = new ArrayList<AcademicTermWrapper>();
240 
241         try {
242             List<TermInfo> termInfos = getAcalService().getTermsForAcademicCalendar(acalId, contextInfo);
243             // we go through the terms once to process all parent and sub terms. This list is to process everything else
244             List<TermInfo> processedTerms = new ArrayList < TermInfo >();
245 
246             for (TermInfo termInfo : termInfos) {
247                 if(!processedTerms.contains(termInfo)){
248                     List<AtpAtpRelationInfo> atpRelations = getAtpService().getAtpAtpRelationsByTypeAndAtp(termInfo.getId(), AtpServiceConstants.ATP_ATP_RELATION_INCLUDES_TYPE_KEY, contextInfo);
249                     if (atpRelations != null && atpRelations.size() > 0) { // if you're a parent term
250                         AcademicTermWrapper termWrapper = populateTermWrapper(termInfo, isCopy, calculateInstrDays); // create the term wrapper for the parent term
251                         //add the parent term into the term wrapper list
252                         termWrappers.add(termWrapper);
253                         processedTerms.add(termInfo);
254 
255                         //add the sub terms into the term wrapper list
256                         for (AtpAtpRelationInfo parentTermRelations : atpRelations) {
257                             // we already have all the terms in the wrappers. We just need to set the parent child relationships
258                             for(TermInfo tInfo : termInfos){
259                                 // Find the subterms
260                                 if(parentTermRelations.getRelatedAtpId().equals(tInfo.getId())){
261                                     AcademicTermWrapper subTermWrapper = populateTermWrapper(tInfo, isCopy, calculateInstrDays);
262                                     subTermWrapper.setParentTerm(termInfo.getTypeKey());   // the name here is ambigious
263                                     subTermWrapper.setSubTerm(true);
264                                     termWrapper.setHasSubterm(true);
265                                     termWrapper.getSubterms().add(subTermWrapper);
266 
267                                     // Allow parent term info to be set to copied term for sorting.
268                                     subTermWrapper.setParentTermInfo(termInfo);
269                                     subTermWrapper.setParentTermName(termInfo.getName());
270 
271                                     termWrappers.add(subTermWrapper);
272                                     processedTerms.add(tInfo);  // this term has now been processed
273                                 }
274                             }
275                         }
276                     }
277                 }
278             }
279             // The previous loop deals with parents and children. Now we have to deal with term that aren't parents or children
280             for (TermInfo termInfo : termInfos) {
281                 if(!processedTerms.contains(termInfo)){
282                     AcademicTermWrapper termWrapper = populateTermWrapper(termInfo, isCopy,calculateInstrDays); // create the term wrapper for the parent term
283                     //add the parent term into the term wrapper list
284                     termWrappers.add(termWrapper);
285                     processedTerms.add(termInfo);
286                 }
287             }
288 
289             //sort term wrappers by start date
290             sortTermWrappers(termWrappers);
291 
292         } catch (Exception e) {
293             throw new RuntimeException(e);
294         }
295 
296         return termWrappers;
297     }
298 
299     public AcademicTermWrapper populateTermWrapper(TermInfo termInfo, boolean isCopy, boolean calculateInstrDays) throws Exception {
300 
301         if (LOG.isDebugEnabled()){
302             LOG.debug("Populating Term - " + termInfo.getId());
303         }
304 
305         TypeInfo type = getAcalService().getTermType(termInfo.getTypeKey(),createContextInfo());
306 
307         AcademicTermWrapper termWrapper = new AcademicTermWrapper(termInfo, isCopy);
308         termWrapper.setTypeInfo(type);
309         termWrapper.setTermNameForUI(type.getName());
310         if (isCopy){
311             termWrapper.setName(type.getName());
312         }
313 
314         //Populate examdates
315         List<ExamPeriodInfo> examPeriodInfos = getAcalService().getExamPeriodsForTerm(termInfo.getId(),createContextInfo());
316         if (examPeriodInfos != null && examPeriodInfos.size() > 0) {  //only one or none
317             for (ExamPeriodInfo examPeriodInfo : examPeriodInfos) {
318                 ExamPeriodWrapper examPeriodWrapper = new ExamPeriodWrapper(examPeriodInfo, isCopy);
319                 examPeriodWrapper.setExcludeSaturday(Boolean.parseBoolean(examPeriodInfo.getAttributeValue(AcademicCalendarServiceConstants.EXAM_PERIOD_EXCLUDE_SATURDAY_ATTR)));
320                 examPeriodWrapper.setExcludeSunday(Boolean.parseBoolean(examPeriodInfo.getAttributeValue(AcademicCalendarServiceConstants.EXAM_PERIOD_EXCLUDE_SUNDAY_ATTR)));
321                 termWrapper.getExamdates().add(examPeriodWrapper);
322             }
323         }
324         
325         //Populate keydates
326         List<KeyDateInfo> keydateList = getAcalService().getKeyDatesForTerm(termInfo.getId(),createContextInfo());
327         List<TypeInfo> keyDateTypes = getTypeService().getAllowedTypesForType(termInfo.getTypeKey(),createContextInfo());
328 
329         Map<String,KeyDatesGroupWrapper> keyDateGroup = new HashMap<String,KeyDatesGroupWrapper>();
330 
331         for (KeyDateInfo keyDateInfo : keydateList) {
332             KeyDateWrapper keyDateWrapper = new KeyDateWrapper(keyDateInfo,isCopy);
333             type = getTypeService().getType(keyDateInfo.getTypeKey(),createContextInfo());
334             keyDateWrapper.setTypeInfo(type);
335             keyDateWrapper.setKeyDateNameUI(type.getName());
336 
337             addKeyDateGroup(keyDateTypes,keyDateWrapper,keyDateGroup);
338         }
339 
340         for (KeyDatesGroupWrapper group : keyDateGroup.values()) {
341             if (!group.getKeydates().isEmpty()){
342                 termWrapper.getKeyDatesGroupWrappers().add(group);
343             }
344         }
345 
346         if (calculateInstrDays){
347             populateInstructionalDays(termWrapper);
348         }
349 
350         return termWrapper;
351     }
352 
353     /**
354      * Adds a keydate to a proper group.
355      *
356      * @param keyDateTypes
357      * @param keyDateWrapper
358      * @param keyDateGroup
359      */
360     protected void addKeyDateGroup(List<TypeInfo> keyDateTypes,KeyDateWrapper keyDateWrapper,Map<String,KeyDatesGroupWrapper> keyDateGroup){
361         if (LOG.isDebugEnabled()){
362             LOG.debug("Adding key date to a group");
363         }
364         for (TypeInfo keyDateType : keyDateTypes) {
365             try {
366                 List<TypeInfo> allowedTypes = getTypeService().getTypesForGroupType(keyDateType.getKey(), createContextInfo());
367                 for (TypeInfo allowedType : allowedTypes) {
368                     if (StringUtils.equals(allowedType.getKey(),keyDateWrapper.getKeyDateType())){
369                         KeyDatesGroupWrapper keyDatesGroup = keyDateGroup.get(keyDateType.getKey());
370                         if (keyDatesGroup == null){
371                             keyDatesGroup = new KeyDatesGroupWrapper(keyDateType.getKey(),keyDateType.getName());
372                             keyDateGroup.put(keyDateType.getKey(),keyDatesGroup);
373                         }
374                         keyDatesGroup.getKeydates().add(keyDateWrapper);
375                         break;
376                     }
377                 }
378             } catch (Exception e) {
379                 throw new RuntimeException(e);
380             }
381         }
382     }
383 
384     /**
385      * This method finds the latest Academic Calendar.
386      * It first tries to find the current year acal. If there is no match found, it looks for last year
387      *
388      * @return
389      * @throws Exception
390      */
391     public AcademicCalendarInfo getLatestAcademicCalendar() throws Exception {
392 
393         if (LOG.isDebugEnabled()){
394             LOG.debug("Finding the latest Academic calendar");
395         }
396 
397         int currentYear = Calendar.getInstance().get(Calendar.YEAR);
398         List<AcademicCalendarInfo> academicCalendarInfoList =
399                 getAcalService().getAcademicCalendarsByStartYear(currentYear, createContextInfo());
400         if ((null == academicCalendarInfoList) || academicCalendarInfoList.isEmpty()) {
401             academicCalendarInfoList =
402                     getAcalService().getAcademicCalendarsByStartYear((currentYear - 1), createContextInfo());
403         }
404 
405         if ((null == academicCalendarInfoList) || (academicCalendarInfoList.size() == 0)) {
406             return null;
407         }
408         else {
409             // If Calendars are found search through them to find the most recently created.
410             // The number of calendars should be small so naive search possible.
411             AcademicCalendarInfo newestCalendar =  academicCalendarInfoList.get(0);
412             for(AcademicCalendarInfo calendarTemp: academicCalendarInfoList){
413                 // Compare the time when the calendars are created and pick the higher one (most recent).
414                 if(calendarTemp.getMeta().getCreateTime().compareTo(newestCalendar.getMeta().getCreateTime())>0){
415                     newestCalendar = calendarTemp;
416                 }
417             }
418 
419             return newestCalendar;
420         }
421     }
422 
423     public void copyToCreateAcademicCalendar(AcademicCalendarForm form) {
424 
425         AcademicCalendarInfo orgAcalInfo = form.getCopyFromAcal();
426 
427         if (orgAcalInfo == null || StringUtils.isBlank(orgAcalInfo.getId())) {
428             throw new RuntimeException("ACal Info doesn't exists to copy.");
429         }
430 
431         // 1. copy over events
432         List<AcalEventInfo> orgEventInfoList = null;
433         try {
434             orgEventInfoList = getAcalService().getAcalEventsForAcademicCalendar(orgAcalInfo.getId(), createContextInfo());
435         } catch (Exception e) {
436             throw convertServiceExceptionsToUI(e);
437         }
438 
439         List<AcalEventWrapper> newEventList = new ArrayList<AcalEventWrapper>();
440         for (AcalEventInfo orgEventInfo : orgEventInfoList) {
441             AcalEventWrapper newEvent = new AcalEventWrapper(orgEventInfo, true);
442             try {
443                 TypeInfo type = getTypeInfo(orgEventInfo.getTypeKey());
444                 newEvent.setEventTypeName(type.getName());
445             } catch (Exception e) {
446                 throw convertServiceExceptionsToUI(e);
447             }
448             newEventList.add(newEvent);
449         }
450         form.setEvents(newEventList);
451 
452         // 2. copy over terms
453         List<AcademicTermWrapper> newTermList = populateTermWrappers(orgAcalInfo.getId(), true, false);
454         form.setTermWrapperList(newTermList);
455         form.setMeta(orgAcalInfo.getMeta());
456 
457         //clear exam period list for each term since they are not supposed to be copied
458         for (AcademicTermWrapper newTerm : newTermList) {
459             newTerm.getExamdates().clear();
460         }
461 
462 
463     }
464 
465     /**
466      * Performs validation on adding holiday calendar, key date groups, key date or event.
467      *
468      * @param view
469      * @param collectionGroup
470      * @param model
471      * @param addLine
472      * @return
473      */
474     protected boolean performAddLineValidation(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
475 
476         if (addLine instanceof HolidayCalendarWrapper) {
477             AcademicCalendarForm form = (AcademicCalendarForm) model;
478             for (HolidayCalendarWrapper holidayCalendarWrapper : form.getHolidayCalendarList()) {
479                 String holidayCalendarId = holidayCalendarWrapper.getId();
480                 if (StringUtils.equals(holidayCalendarWrapper.getId(), ((HolidayCalendarWrapper) addLine).getId())) {
481                     GlobalVariables.getMessageMap().putError("newCollectionLines['holidayCalendarList'].id",
482                             CalendarConstants.MessageKeys.ERROR_DUPLICATE_HCAL,
483                             holidayCalendarWrapper.getHolidayCalendarInfo().getName());
484                     return false;
485                 }
486             }
487         } else if (addLine instanceof KeyDatesGroupWrapper) {
488             AcademicCalendarForm form = (AcademicCalendarForm) model;
489             form.setAddLineValid(true);
490             form.setValidationJSONString("{}");
491             KeyDatesGroupWrapper keydateGroup = (KeyDatesGroupWrapper) addLine;
492             if(StringUtils.isEmpty(keydateGroup.getKeyDateGroupType())) {
493                 GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroup.getId(), CalendarConstants.MessageKeys.ERROR_KEY_DATE_GROUP_TYPE_REQUIRED);
494                 StringBuilder sb = new StringBuilder();
495                 sb.append("\"key_date_group_type\":\"Required\"");
496                 form.setValidationJSONString("{"+sb.toString()+"}");
497                 form.setAddLineValid(false);
498                 return false;
499             }
500         }
501         else if (addLine instanceof KeyDateWrapper) {
502             AcademicCalendarForm form = (AcademicCalendarForm) model;
503             form.setAddLineValid(true);
504             form.setValidationJSONString("{}");
505             KeyDateWrapper keydate = (KeyDateWrapper)addLine;
506 
507             //identify termWrapper for keydates
508             String selectedCollectionPath = form.getActionParamaterValue("selectedCollectionPath");
509             int selectedTermWrapperIndex = Integer.parseInt(selectedCollectionPath.substring(selectedCollectionPath.indexOf("[")+1, selectedCollectionPath.indexOf("]")));
510             AcademicTermWrapper termWrapper = form.getTermWrapperList().get(selectedTermWrapperIndex);
511 
512             // Key Dates start and end dates should be within the start and end dates of the term
513             if (!AcalCommonUtils.isDateWithinRange(termWrapper.getStartDate(), termWrapper.getEndDate(), keydate.getStartDate()) ||
514                     !AcalCommonUtils.isDateWithinRange(termWrapper.getStartDate(), termWrapper.getEndDate(), keydate.getEndDate())){
515                 GlobalVariables.getMessageMap().putWarningForSectionId(collectionGroup.getId(), CalendarConstants.MessageKeys.ERROR_INVALID_DATERANGE_KEYDATE,keydate.getKeyDateNameUI(),termWrapper.getName());
516             }
517 
518             String error = getValidDateTimeErrors(collectionGroup.getId(), keydate.getKeyDateType(), keydate, keydate.getKeyDateNameUI());
519             if (!StringUtils.isBlank(error)) {
520                 form.setValidationJSONString("{" + error.toString() + "}");
521                 form.setAddLineValid(false);
522                 return false;
523             }
524         }
525         else if (addLine instanceof AcalEventWrapper) {
526             AcalEventWrapper eventWrapper = (AcalEventWrapper)addLine;
527             if (!StringUtils.isBlank(getValidDateTimeErrors(collectionGroup.getId(), eventWrapper.getEventTypeKey(), eventWrapper, eventWrapper.getEventTypeName()))) {
528                 return false;
529             }
530         }
531         else if(addLine instanceof AcademicTermWrapper) {
532             //if tries to add a Subterm, the parent term has to exist in the Form
533             AcademicTermWrapper term = (AcademicTermWrapper) addLine;
534             AcademicCalendarForm acalForm = (AcademicCalendarForm) model;
535             acalForm.setValidationJSONString("{}");
536             if (term.getParentTerm() != null &&
537                     !StringUtils.isBlank(term.getParentTerm())){
538 
539                 AcademicTermWrapper parentTerm=null;
540                 for (AcademicTermWrapper termWrapper : acalForm.getTermWrapperList()){
541                     String termType = termWrapper.getTermType();
542                     if (StringUtils.isBlank(termType)){
543                         termType = termWrapper.getTermInfo().getTypeKey();
544                     }
545                     if (term.getParentTerm().equals(termType)){
546                         parentTerm = termWrapper;
547                         break;
548                     }
549                 }
550 
551                 if (parentTerm == null){
552                     return false;
553                 }
554 
555                 if (!AcalCommonUtils.isDateWithinRange(parentTerm.getStartDate(), parentTerm.getEndDate(), term.getStartDate()) ||
556                         !AcalCommonUtils.isDateWithinRange(parentTerm.getStartDate(), parentTerm.getEndDate(), term.getEndDate())){
557                     GlobalVariables.getMessageMap().putWarningForSectionId(collectionGroup.getId(), CalendarConstants.MessageKeys.ERROR_TERM_NOT_IN_TERM_RANGE,term.getName(),parentTerm.getName());
558                 }
559             }
560 
561             return true;
562         }
563         return super.performAddLineValidation(view, collectionGroup, model, addLine);
564     }
565 
566     /**
567      * This method is being called by KRAD to populate keydate types drop down. There would be no reference
568      * for this method in the code as it has it's reference at the AcademicTermPage.xml page
569      *
570      * @param field
571      * @param acalForm
572      */
573     @SuppressWarnings("unused")
574     public void populateKeyDateTypes(InputField field, AcademicCalendarForm acalForm) {
575 
576         boolean isAddLine = BooleanUtils.toBoolean((Boolean)field.getContext().get(UifConstants.ContextVariableNames.IS_ADD_LINE));
577         if (!isAddLine) {
578             return;
579         }
580 
581         List<KeyValue> keyValues = new ArrayList<KeyValue>();
582         keyValues.add(new ConcreteKeyValue("", "Select Keydate Type"));
583 
584         CollectionGroup collectionGroup = (CollectionGroup)field.getContext().get(UifConstants.ContextVariableNames.PARENT);
585         KeyDatesGroupWrapper groupWrapper = ObjectPropertyUtils.getPropertyValue(acalForm,collectionGroup.getBindingInfo().getBindByNamePrefix());
586 
587         if (StringUtils.isNotBlank(groupWrapper.getKeyDateGroupType())){
588             try {
589                 List<TypeInfo> types = getTypeService().getTypesForGroupType(groupWrapper.getKeyDateGroupType(),createContextInfo());
590                 for (TypeInfo type : types) {
591                     if (!groupWrapper.isKeyDateExists(type.getKey())){
592                         keyValues.add(new ConcreteKeyValue(type.getKey(), type.getName()));
593                     }
594                 }
595             } catch (Exception e) {
596                 throw new RuntimeException(e);
597             }
598         }
599 
600         ((SelectControl) field.getControl()).setOptions(keyValues);
601 
602     }
603 
604     /**
605      * This method is being called by KRAD to populate keydate group types drop down. There would be no reference
606      * for this method in the code as it has it's reference at the AcademicTermPage.xml page
607      *
608      * @param field
609      * @param acalForm
610      */
611     @SuppressWarnings("unused")
612     public void populateKeyDateGroupTypes(InputField field, AcademicCalendarForm acalForm) {
613 
614         boolean isAddLine = BooleanUtils.toBoolean((Boolean)field.getContext().get(UifConstants.ContextVariableNames.IS_ADD_LINE));
615         if (!isAddLine) {
616             return;
617         }
618 
619         List<KeyValue> keyValues = new ArrayList<KeyValue>();
620         keyValues.add(new ConcreteKeyValue("", "Select Keydate Group Type"));
621 
622         CollectionGroup collectionGroup = (CollectionGroup)field.getContext().get(UifConstants.ContextVariableNames.COLLECTION_GROUP);
623         AcademicTermWrapper termWrapper = ObjectPropertyUtils.getPropertyValue(acalForm,collectionGroup.getBindingInfo().getBindByNamePrefix());
624 
625         try {
626             List<TypeInfo> keyDateGroupTypes = getAcalService().getKeyDateTypesForTermType(termWrapper.getTermType(),createContextInfo());
627             for (TypeInfo keyDateGroupType : keyDateGroupTypes) {
628                 if (!termWrapper.isKeyDateGroupExists(keyDateGroupType.getKey())){
629                     keyValues.add(new ConcreteKeyValue(keyDateGroupType.getKey(),keyDateGroupType.getName()));
630                 }
631             }
632         } catch (Exception e) {
633             throw new RuntimeException(e);
634         }
635 
636         ((SelectControl) field.getControl()).setOptions(keyValues);
637 
638     }
639 
640     /**
641      * This method is called during calendar save. As there is inconsistency between ui and the services handling
642      * the allday and daterange, this method is like an adapter to convert the ui data to the data needed by services.
643      *
644      * In Services, it handles date range as point in time
645      * More info at https://wiki.kuali.org/display/STUDENT/Storing+and+Querying+Milestone+Dates
646      *
647      * @param acalForm
648      */
649     public void populateAcademicCalendarDefaults(AcademicCalendarForm acalForm){
650 
651         for (AcalEventWrapper eventWrapper : acalForm.getEvents()) {
652             // first setting AllDay, DateRange, etc.
653             getValidDateTimeErrors("acal-info-event", eventWrapper.getEventTypeKey(), eventWrapper, eventWrapper.getEventTypeName());
654 
655             eventWrapper.getAcalEventInfo().setStartDate(getStartDateWithUpdatedTime(eventWrapper,false));
656             setEventEndDate(eventWrapper);
657         }
658 
659         for (int index=0; index < acalForm.getTermWrapperList().size(); index++) {
660             AcademicTermWrapper academicTermWrapper = acalForm.getTermWrapperList().get(index);
661             String keyDateGroupSectionName="acal-term-keydatesgroup_line" + index;
662             for (KeyDatesGroupWrapper keyDatesGroupWrapper : academicTermWrapper.getKeyDatesGroupWrappers()){
663                 for(KeyDateWrapper keyDateWrapper : keyDatesGroupWrapper.getKeydates()){
664                     getValidDateTimeErrors(keyDateGroupSectionName, keyDateWrapper.getKeyDateType(), keyDateWrapper, keyDateWrapper.getKeyDateNameUI());
665 
666                     if(keyDateWrapper.getStartDate() != null){
667                         keyDateWrapper.getKeyDateInfo().setStartDate(getStartDateWithUpdatedTime(keyDateWrapper,false));
668                         setKeyDateEndDate(keyDateWrapper);
669                     }
670                 }
671             }
672         }
673     }
674 
675     /**
676      * Validates Academic Calendar
677      *
678      * @param acalForm
679      */
680     public void validateAcademicCalendar(AcademicCalendarForm acalForm){
681 
682         AcademicCalendarInfo acal = acalForm.getAcademicCalendarInfo();
683 
684         //Validate Acal Name for duplication
685         if (!isValidAcalName(acalForm.getAcademicCalendarInfo())){
686             GlobalVariables.getMessageMap().putError("academicCalendarInfo.name", CalendarConstants.MessageKeys.ERROR_DUPLICATE_NAME);
687         }
688 
689         if (!AcalCommonUtils.isValidDateRange(acal.getStartDate(), acal.getEndDate())){
690             GlobalVariables.getMessageMap().putErrorForSectionId("KS-AcademicCalendar-MetaSection", CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE,"Calendar", AcalCommonUtils.formatDate(acal.getStartDate()), AcalCommonUtils.formatDate(acal.getEndDate()));
691         }
692 
693         //Validate Events
694         for (AcalEventWrapper eventWrapper : acalForm.getEvents()) {
695             if (!AcalCommonUtils.isDateWithinRange(acal.getStartDate(), acal.getEndDate(), eventWrapper.getStartDate()) ||
696                 !AcalCommonUtils.isDateWithinRange(acal.getStartDate(), acal.getEndDate(), eventWrapper.getEndDate())){
697                 GlobalVariables.getMessageMap().putWarningForSectionId("acal-info-event", CalendarConstants.MessageKeys.ERROR_DATE_NOT_IN_ACAL_RANGE,eventWrapper.getEventTypeName());
698             }
699         }
700 
701         //Validate Holiday Calendar are in the date range of the Academic Calendar
702         // With holiday calendars we only want there to be Any overlap between the hcal and the acal
703         for (HolidayCalendarWrapper holidayCalendarWrapper : acalForm.getHolidayCalendarList())  {
704             if (!AcalCommonUtils.doDatesOverlap(acal.getStartDate(), acal.getEndDate(),
705                     holidayCalendarWrapper.getHolidayCalendarInfo().getStartDate(), holidayCalendarWrapper.getHolidayCalendarInfo().getEndDate())){
706                 GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, CalendarConstants.MessageKeys.ERROR_DATE_NOT_IN_ACAL_RANGE,"Added Holiday Calendar: " + holidayCalendarWrapper.getHolidayCalendarInfo().getName());
707             }
708         }
709 
710         //sort term wrappers by start date . We need to do this in the validate call becaues they are later sorted before
711         // the screen is rendered. When that happens the calendars are resorted and the warnding + error messages
712         // will be pointint at the wrong term.
713         sortTermWrappers(acalForm.getTermWrapperList());
714 
715         //get all the holidays for the academic calendar
716         List<HolidayInfo> holidayInfos = new ArrayList<HolidayInfo>();
717         for (HolidayCalendarWrapper holidayCalendarWrapper : acalForm.getHolidayCalendarList()) {
718             for (HolidayWrapper holidayWrapper : holidayCalendarWrapper.getHolidays()) {
719                 holidayInfos.add(holidayWrapper.getHolidayInfo());
720             }
721         }
722 
723         //Validate Terms keydates and exam period
724         for (int index=0; index < acalForm.getTermWrapperList().size(); index++) {
725             validateTerm(acalForm.getTermWrapperList(),index,acal);
726 
727             //in order not to modify the existing method signatures, place the exam period days validation here
728             AcademicTermWrapper termWrapperToValidate = acalForm.getTermWrapperList().get(index);
729             try {
730                 validateExamPeriodDays(termWrapperToValidate, holidayInfos, index);
731             } catch (Exception e) {
732                 throw new RuntimeException(e);
733             }
734         }
735 
736     }
737 
738     /**
739      * Make sure the user entered Acal name doesnt duplicate with the existing ones
740      *
741      * @param acal
742      * @return
743      */
744     private boolean isValidAcalName(AcademicCalendarInfo acal){
745 
746         QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();
747         List<Predicate> pList = new ArrayList<Predicate>();
748 
749         Predicate p = equal("atpType",AcademicCalendarServiceConstants.ACADEMIC_CALENDAR_TYPE_KEY);
750         pList.add(p);
751 
752         p = equalIgnoreCase("name", acal.getName());
753         pList.add(p);
754 
755         Predicate[] preds = new Predicate[pList.size()];
756         pList.toArray(preds);
757         qBuilder.setPredicates(and(preds));
758 
759         try {
760             List<AcademicCalendarInfo> acals = getAcalService().searchForAcademicCalendars(qBuilder.build(),createContextInfo());
761             boolean valid = acals.isEmpty();
762             //Make sure it's not the same Acal which is being edited by the user
763             if (!valid && StringUtils.isNotBlank(acal.getId())){
764                 for (AcademicCalendarInfo academicCalendarInfo : acals) {
765                     if (!StringUtils.equals(academicCalendarInfo.getId(),acal.getId())){
766                         valid = false;
767                         break;
768                     }
769                     valid = true;
770                 }
771             }
772 
773             return valid;
774         } catch (Exception e) {
775             throw new RuntimeException(e);
776         }
777     }
778 
779     // NOTE: edits here should not be needed if KRAD validation is working properly...
780     private String getValidDateTimeErrors(String collectionGroupId, String KeyDateType, TimeSetWrapper wrapper, String wrapperName) {
781         StringBuilder sb = new StringBuilder();
782 
783         // The Key Date Type should not be null
784         if(StringUtils.isEmpty(KeyDateType)) {
785             GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_KEY_DATE_TYPE_REQUIRED);
786             sb.append("\"key_date_type\":\"Required\"");
787         }
788 
789         // Start Date not null, Start Time null, End Date null, End Time not null - illegal
790         if (wrapper.getStartDate()!=null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
791                 wrapper.getEndDate()==null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
792             GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_TIME, wrapperName);
793             sb.append("\"key_date_end_date\":\"Required\"");
794         }
795         // Start Date null, Start Time not null, different combinations of End Date and End Time - illegal
796         else if (wrapper.getStartDate()==null && (wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime()))){
797             GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_TIME, wrapperName);
798             sb.append("\"key_date_start_date\":\"Required\"");
799         }
800         // Start Date null, Start Time null, End Date null, End Time not null - illegal
801         else if (wrapper.getStartDate()==null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
802                 wrapper.getEndDate()==null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
803             GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_TIME, wrapperName);
804             sb.append("\"key_date_start_date\":\"Required\"");
805         }
806 
807         // Start Date and End Date could be null but put a warning
808         if (wrapper.getStartDate()==null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
809                 wrapper.getEndDate()==null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
810             GlobalVariables.getMessageMap().putWarningForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_KEY_DATE_START_DATE_REQUIRED, wrapperName);
811 //            GlobalVariables.getMessageMap().putError(lineName+".startDate", CalendarConstants.MessageKeys.ERROR_KEY_DATE_START_DATE_REQUIRED, wrapperName);
812         }
813 
814         if (wrapper.getStartDate()!=null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
815                 wrapper.getEndDate()==null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
816             wrapper.setAllDay(true);
817             wrapper.setDateRange(false);
818         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime())) &&
819                 wrapper.getEndDate()==null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
820             wrapper.setAllDay(false);
821             wrapper.setDateRange(false);
822         } else if (wrapper.getStartDate()==null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
823                 wrapper.getEndDate()!=null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
824             wrapper.setStartDate(wrapper.getEndDate());
825             wrapper.setEndDate(null);
826             wrapper.setAllDay(true);
827             wrapper.setDateRange(false);
828         } else if (wrapper.getStartDate()==null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
829                 wrapper.getEndDate()!=null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
830             wrapper.setStartDate(wrapper.getEndDate());
831             wrapper.setStartTime(wrapper.getEndTime());
832             wrapper.setEndDate(null);
833             wrapper.setEndTime(null);
834             wrapper.setAllDay(false);
835             wrapper.setDateRange(false);
836         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
837                 wrapper.getEndDate()!=null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
838             wrapper.setAllDay(true);
839             wrapper.setDateRange(true);
840         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime())) &&
841                 wrapper.getEndDate()!=null && (wrapper.getEndTime()==null || StringUtils.isBlank(wrapper.getEndTime()))){
842             wrapper.setAllDay(false);
843             wrapper.setDateRange(true);
844             timeSetWrapperEndDate(wrapper);
845         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()==null || StringUtils.isBlank(wrapper.getStartTime())) &&
846                 wrapper.getEndDate()!=null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
847             wrapper.setAllDay(false);
848             wrapper.setDateRange(true);
849             getStartDateWithUpdatedTime(wrapper, false);
850         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime())) &&
851                 wrapper.getEndDate()!=null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
852             wrapper.setAllDay(false);
853             wrapper.setDateRange(true);
854         } else if (wrapper.getStartDate()!=null && (wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime())) &&
855                 wrapper.getEndDate()==null && (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))){
856             wrapper.setEndDate(wrapper.getStartDate());
857             wrapper.setAllDay(false);
858             wrapper.setDateRange(true);
859         }
860 
861         // Start Date can't be later than End Date
862         if (wrapper.getStartDate()!=null && wrapper.getEndDate()!=null){
863             if ((wrapper.getStartTime()!=null && !StringUtils.isBlank(wrapper.getStartTime())) &&
864                     (wrapper.getEndTime()!=null && !StringUtils.isBlank(wrapper.getEndTime()))) {
865                 Date startDate = getStartDateWithUpdatedTime(wrapper, false);
866                 Date endDate =  timeSetWrapperEndDate(wrapper);
867                 if (!AcalCommonUtils.isValidDateRange(startDate, endDate)) {
868                     GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE, wrapperName, DateFormatUtils.format(startDate, DateFormatters.MONTH_DAY_YEAR_TIME_DATE_FORMAT), DateFormatUtils.format(endDate, DateFormatters.MONTH_DAY_YEAR_TIME_DATE_FORMAT));
869                     sb.append("\"key_date_start_date\":\"Invalid\"");
870                 }
871             } else {
872                 if (!AcalCommonUtils.isValidDateRange(wrapper.getStartDate(), wrapper.getEndDate())) {
873                     GlobalVariables.getMessageMap().putErrorForSectionId(collectionGroupId, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE, wrapperName, AcalCommonUtils.formatDate(wrapper.getStartDate()), AcalCommonUtils.formatDate(wrapper.getEndDate()));
874                     sb.append("\"key_date_start_date\":\"Invalid\"");
875                 }
876             }
877         }
878 
879         return sb.toString();
880     }
881 
882     /**
883      * Validates the term at the given index
884      *
885      * @param termWrapper list of terms in an academic calendar
886      * @param termToValidateIndex index of the term to be validated
887      * @param acal ACal dto needed to compare the start and end date
888      */
889     public void validateTerm(List<AcademicTermWrapper> termWrapper,int termToValidateIndex,AcademicCalendarInfo acal) {
890 
891         AcademicTermWrapper termWrapperToValidate = termWrapper.get(termToValidateIndex);
892         String termSectionName="term_section_line"+termToValidateIndex;
893         String keyDateGroupSectionName="acal-term-keydatesgroup_line"+termToValidateIndex;
894 
895 
896         int index2 = 0;
897         //Validate duplicate term name
898         for (AcademicTermWrapper wrapper : termWrapper) {
899             index2++;
900             if (wrapper != termWrapperToValidate){
901                 if (StringUtils.equalsIgnoreCase(wrapper.getName(),termWrapperToValidate.getName())){
902                     GlobalVariables.getMessageMap().putErrorForSectionId(termSectionName, CalendarConstants.MessageKeys.ERROR_DUPLICATE_TERM_NAME,""+ NumberUtils.min(new int[]{termToValidateIndex,index2}),""+NumberUtils.max(new int[]{termToValidateIndex,index2}));
903                 }
904             }
905         }
906 
907         if (!AcalCommonUtils.isValidDateRange(termWrapperToValidate.getStartDate(), termWrapperToValidate.getEndDate())){
908             GlobalVariables.getMessageMap().putErrorForSectionId(termSectionName, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE,termWrapperToValidate.getName(), AcalCommonUtils.formatDate(termWrapperToValidate.getStartDate()), AcalCommonUtils.formatDate(termWrapperToValidate.getEndDate()));
909         }
910 
911         if (!AcalCommonUtils.isDateWithinRange(acal.getStartDate(), acal.getEndDate(), termWrapperToValidate.getStartDate()) ||
912             !AcalCommonUtils.isDateWithinRange(acal.getStartDate(), acal.getEndDate(), termWrapperToValidate.getEndDate())){
913             GlobalVariables.getMessageMap().putWarningForSectionId(termSectionName, CalendarConstants.MessageKeys.ERROR_TERM_NOT_IN_ACAL_RANGE,termWrapperToValidate.getName());
914         }
915         if(termWrapperToValidate.isSubTerm()){
916             if(termWrapperToValidate.getParentTermInfo()!= null){
917                 if (!AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getParentTermInfo().getStartDate(), termWrapperToValidate.getParentTermInfo().getEndDate(), termWrapperToValidate.getStartDate()) ||
918                         !AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getParentTermInfo().getStartDate(), termWrapperToValidate.getParentTermInfo().getEndDate(), termWrapperToValidate.getEndDate())){
919                     GlobalVariables.getMessageMap().putWarningForSectionId(termSectionName, CalendarConstants.MessageKeys.ERROR_TERM_NOT_IN_TERM_RANGE,termWrapperToValidate.getName(),termWrapperToValidate.getParentTermInfo().getName());
920                 }
921             }else{
922                 // Find term manually if calendar hasn't already been saved.
923                 AcademicTermWrapper parentTerm=null;
924                 for (AcademicTermWrapper term :termWrapper){
925                     String termType = term.getTermType();
926                     if (StringUtils.isBlank(termType)){
927                         termType = term.getTermInfo().getTypeKey();
928                     }
929                     if (termWrapperToValidate.getParentTerm().equals(termType)){
930                         parentTerm =term;
931                         break;
932                     }
933                 }
934 
935                 if (!AcalCommonUtils.isDateWithinRange(parentTerm.getStartDate(), parentTerm.getEndDate(), termWrapperToValidate.getStartDate()) ||
936                         !AcalCommonUtils.isDateWithinRange(parentTerm.getStartDate(), parentTerm.getEndDate(), termWrapperToValidate.getEndDate())){
937                     GlobalVariables.getMessageMap().putWarningForSectionId(termSectionName, CalendarConstants.MessageKeys.ERROR_TERM_NOT_IN_TERM_RANGE,termWrapperToValidate.getName(),parentTerm.getName());
938                 }
939             }
940         }
941 
942         for (KeyDatesGroupWrapper keyDatesGroupWrapper : termWrapperToValidate.getKeyDatesGroupWrappers()){
943             for(KeyDateWrapper keyDateWrapper : keyDatesGroupWrapper.getKeydates()){
944                 // Start and End Dates of the key date entry should be within the start and end dates of the term.
945                 if (!AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getStartDate(), termWrapperToValidate.getEndDate(), keyDateWrapper.getStartDate()) ||
946                         !AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getStartDate(), termWrapperToValidate.getEndDate(), keyDateWrapper.getEndDate())){
947                     GlobalVariables.getMessageMap().putWarningForSectionId(keyDateGroupSectionName, CalendarConstants.MessageKeys.ERROR_INVALID_DATERANGE_KEYDATE,keyDateWrapper.getKeyDateNameUI(),termWrapperToValidate.getName());
948                 }
949             }
950         }
951 
952         //Validate exam dates
953         validateExamPeriod(termWrapperToValidate, termToValidateIndex);
954     }
955 
956     /**
957      * Calculates and populates the instructional days for a term
958      *
959      * @param termWrapper
960      * @throws Exception
961      */
962     public void populateInstructionalDays(AcademicTermWrapper termWrapper) {
963         if (termWrapper.getKeyDatesGroupWrappers() != null){
964             for (KeyDatesGroupWrapper keyDatesGroupWrapper : termWrapper.getKeyDatesGroupWrappers()) {
965                  if (keyDatesGroupWrapper.getKeydates() != null){
966                      for (KeyDateWrapper keydate : keyDatesGroupWrapper.getKeydates()) {
967                          if (StringUtils.equals(keydate.getKeyDateType(),AtpServiceConstants.MILESTONE_INSTRUCTIONAL_PERIOD_TYPE_KEY) &&
968                              termWrapper.getTermInfo() != null && StringUtils.isNotBlank(termWrapper.getTermInfo().getId())){
969                              try{
970                                  int instructionalDays = getAcalService().getInstructionalDaysForTerm(termWrapper.getTermInfo().getId(),createContextInfo());
971                                  termWrapper.setInstructionalDays(instructionalDays);
972                              }catch(Exception e){  // Calculating instructional days should not block the normal operation
973                                 GlobalVariables.getMessageMap().putInfo(KRADConstants.GLOBAL_ERRORS, CalendarConstants.MessageKeys.ERROR_CALCULATING_INSTRUCTIONAL_DAYS,termWrapper.getTermNameForUI(),e.getMessage());
974                              }
975                              break;
976                          }
977                      }
978                  }
979 
980             }
981         }
982     }
983 
984     protected Date getStartDateWithUpdatedTime(TimeSetWrapper timeSetWrapper, boolean isSaveAction){
985         //If start time not blank, set that with the date. If it's empty, just update with default
986         if (!timeSetWrapper.isAllDay()){
987             if (StringUtils.isNotBlank(timeSetWrapper.getStartTime())){
988                 String startTime = timeSetWrapper.getStartTime();
989                 String startTimeApPm = timeSetWrapper.getStartTimeAmPm();
990                 //On save to DB, have to replace 12AM to 00AM insead of DB considers as 12PM
991                 if (isSaveAction && StringUtils.startsWith(startTime,"12:") && StringUtils.equalsIgnoreCase(startTimeApPm,"am")){
992                     startTime = StringUtils.replace(startTime,"12:","00:");
993                 }
994                 return AcalCommonUtils.getDateWithTime(timeSetWrapper.getStartDate(), startTime, startTimeApPm);
995             }else{
996                 return null; // should never get here.
997             }
998         }else{
999             return timeSetWrapper.getStartDate();
1000         }
1001 
1002     }
1003 
1004     protected void setEventEndDate(AcalEventWrapper eventWrapper) {
1005         eventWrapper.getAcalEventInfo().setIsDateRange(eventWrapper.isDateRange());
1006         Date endDateToInfo = timeSetWrapperEndDate(eventWrapper);
1007         eventWrapper.getAcalEventInfo().setEndDate(endDateToInfo);
1008     }
1009 
1010     protected void setKeyDateEndDate(KeyDateWrapper keyDateWrapper) {
1011         keyDateWrapper.getKeyDateInfo().setIsDateRange(keyDateWrapper.isDateRange());
1012         Date endDateToInfo = timeSetWrapperEndDate(keyDateWrapper);
1013         keyDateWrapper.getKeyDateInfo().setEndDate(endDateToInfo);
1014     }
1015 
1016     protected Date timeSetWrapperEndDate(TimeSetWrapper timeSetWrapper) {
1017         Date endDateToInfo;
1018 
1019         if (timeSetWrapper.isAllDay()) {
1020             if (timeSetWrapper.isDateRange()) {
1021                 endDateToInfo = AcalCommonUtils.getDateWithTime(timeSetWrapper.getEndDate(), CalendarConstants.DEFAULT_END_TIME, "PM");
1022             } else {
1023                 endDateToInfo = null;
1024                 timeSetWrapper.setEndDate(null);
1025             }
1026 
1027             // set the UI time & am/pm fields to null in case they just had values:
1028             timeSetWrapper.setStartTime(null);
1029             timeSetWrapper.setStartTimeAmPm("AM");
1030             timeSetWrapper.setEndTime(null);
1031             timeSetWrapper.setEndTimeAmPm("AM");
1032         }
1033         else {
1034             if (timeSetWrapper.isDateRange()){
1035                 Date endDate = timeSetWrapper.getEndDate();
1036                 String endTime = timeSetWrapper.getEndTime();
1037                 String endTimeAmPm = timeSetWrapper.getEndTimeAmPm();
1038 
1039                 if (StringUtils.isBlank(endTime)){
1040                     endTime = CalendarConstants.DEFAULT_END_TIME;
1041                     endTimeAmPm = "PM";
1042                  }
1043                  timeSetWrapper.setEndTime(endTime);
1044                  timeSetWrapper.setEndTimeAmPm(endTimeAmPm);
1045 
1046                  endDateToInfo = AcalCommonUtils.getDateWithTime(endDate, endTime, endTimeAmPm);
1047              } else {
1048                  timeSetWrapper.setEndDate(null);
1049                  timeSetWrapper.setEndTime(null);
1050                  timeSetWrapper.setEndTimeAmPm("AM");
1051                  endDateToInfo = null;
1052              }
1053          }
1054 
1055         return endDateToInfo;
1056     }
1057 
1058     /**
1059      * Process before adding a term, key date group, holiday calendar or event
1060      *
1061      * @param view
1062      * @param collectionGroup
1063      * @param model
1064      * @param addLine
1065      */
1066     protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
1067 
1068         if (addLine instanceof AcademicTermWrapper){
1069             AcademicTermWrapper newLine = (AcademicTermWrapper)addLine;
1070             AcademicCalendarForm acalForm = (AcademicCalendarForm) model;
1071             //need to handle Term vs subTerm in different way
1072             try {
1073                 TypeInfo termType = getAcalService().getTermType(newLine.getTermType(), createContextInfo());
1074                 // check if term is subterm vs parent term
1075                 getParentTermType(newLine);
1076 
1077                 if (newLine.getParentTerm() == null || StringUtils.isBlank(newLine.getParentTerm())){ //try to add a term
1078                     newLine.setTermNameForUI(termType.getName());
1079                     newLine.setName(termType.getName() + " " + DateFormatters.DEFULT_YEAR_FORMATTER.format(newLine.getStartDate()));
1080                     newLine.setTypeInfo(termType);
1081                     newLine.setSubTerm(false);
1082                 } else { //try to add a subterm
1083                     newLine.setTermNameForUI(termType.getName());
1084                     newLine.setName(termType.getName() + " " + DateFormatters.DEFULT_YEAR_FORMATTER.format(newLine.getStartDate()));
1085                     newLine.setTypeInfo(termType);
1086                     newLine.setSubTerm(true);
1087                     AcademicTermWrapper parentTermWrapper = getParentTermInForm(newLine.getParentTerm(), acalForm.getTermWrapperList());
1088                     if(parentTermWrapper != null){
1089                         populateParentTermToSubterm(parentTermWrapper, newLine);
1090                     }
1091                     parentTermWrapper.setHasSubterm(true);
1092                     parentTermWrapper.getSubterms().add(newLine);
1093                 }
1094             } catch (Exception e) {
1095                 throw new RuntimeException(e);
1096             }
1097         }else if (addLine instanceof KeyDatesGroupWrapper){
1098             KeyDatesGroupWrapper group = (KeyDatesGroupWrapper)addLine;
1099             if(StringUtils.isNotEmpty(group.getKeyDateGroupType())) {
1100                 try {
1101                     TypeInfo termType = getTypeInfo(group.getKeyDateGroupType());
1102                     group.setKeyDateGroupNameUI(termType.getName());
1103                     group.setTypeInfo(termType);
1104                 } catch (Exception e) {
1105                     throw new RuntimeException(e);
1106                 }
1107             }
1108         }else if (addLine instanceof HolidayCalendarInfo) {
1109             HolidayCalendarInfo inputLine = (HolidayCalendarInfo)addLine;
1110             try {
1111                 System.out.println("HC id =" +inputLine.getId());
1112 
1113                 HolidayCalendarInfo exists = getAcalService().getHolidayCalendar(inputLine.getId(), createContextInfo());
1114 
1115                 inputLine.setName(exists.getName());
1116                 inputLine.setId(exists.getId());
1117                 inputLine.setTypeKey(exists.getTypeKey());
1118                 inputLine.setAdminOrgId(exists.getAdminOrgId());
1119                 inputLine.setStartDate(exists.getStartDate());
1120                 inputLine.setEndDate(exists.getEndDate());
1121             }catch (Exception e) {
1122                 throw new RuntimeException(e);
1123             }
1124 
1125         }else if (addLine instanceof HolidayCalendarWrapper){
1126             HolidayCalendarWrapper inputLine = (HolidayCalendarWrapper)addLine;
1127             List<HolidayWrapper> holidays = new ArrayList<HolidayWrapper>();
1128             try {
1129                 String holidayCalendarId = inputLine.getId();
1130                 if (!StringUtils.isEmpty(holidayCalendarId)) {
1131                     HolidayCalendarInfo hcInfo = getAcalService().getHolidayCalendar(inputLine.getId(), createContextInfo());
1132                     inputLine.setHolidayCalendarInfo(hcInfo);
1133                     inputLine.setAdminOrgName(AcalCommonUtils.getAdminOrgNameById(hcInfo.getAdminOrgId()));
1134                     StateInfo hcState = getAcalService().getHolidayCalendarState(hcInfo.getStateKey(), createContextInfo());
1135                     inputLine.setStateName(hcState.getName());
1136                     List<HolidayInfo> holidayInfoList = getAcalService().getHolidaysForHolidayCalendar(hcInfo.getId(), createContextInfo());
1137                     for(HolidayInfo holidayInfo : holidayInfoList){
1138                         HolidayWrapper holiday = new HolidayWrapper(holidayInfo);
1139                         TypeInfo typeInfo = getAcalService().getHolidayType(holidayInfo.getTypeKey(), createContextInfo());
1140                         holiday.setTypeName(typeInfo.getName());
1141                         holidays.add(holiday);
1142                     }
1143                     inputLine.setHolidays(holidays);
1144                 }
1145             }catch (Exception e){
1146                 throw new RuntimeException(e);
1147             }
1148 
1149         } else if (addLine instanceof AcalEventWrapper){
1150             AcalEventWrapper acalEventWrapper = (AcalEventWrapper)addLine;
1151             try {
1152                 if (!StringUtils.isBlank(acalEventWrapper.getEventTypeKey())) {
1153                     TypeInfo type = getTypeService().getType(acalEventWrapper.getEventTypeKey(), createContextInfo());
1154                     acalEventWrapper.setEventTypeName(type.getName());
1155                 }
1156             }catch (Exception e) {
1157                 throw new RuntimeException(e);
1158             }
1159         } else if (addLine instanceof KeyDateWrapper){
1160             KeyDateWrapper keydate = (KeyDateWrapper)addLine;
1161             try {
1162                 if(StringUtils.isNotEmpty(keydate.getKeyDateType())) {
1163                     TypeInfo type = getTypeService().getType(keydate.getKeyDateType(),createContextInfo());
1164                     keydate.setKeyDateNameUI(type.getName());
1165                     keydate.setTypeInfo(type);
1166                 }
1167             } catch (Exception e) {
1168                 throw new RuntimeException(e);
1169             }
1170         } else if (addLine instanceof HolidayWrapper){
1171             HolidayWrapper holiday = (HolidayWrapper)addLine;
1172             try {
1173                 holiday.setTypeName(getTypeInfo(holiday.getTypeKey()).getName());
1174             } catch (Exception e) {
1175                 throw new RuntimeException(e);
1176             }
1177             if (!AcalCommonUtils.isValidDateRange(holiday.getStartDate(), holiday.getEndDate())){
1178                 GlobalVariables.getMessageMap().putWarningForSectionId("KS-HolidayCalendar-HolidaySection", CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE,holiday.getTypeName(), AcalCommonUtils.formatDate(holiday.getStartDate()), AcalCommonUtils.formatDate(holiday.getEndDate()));
1179             }
1180         } else {
1181             super.processBeforeAddLine(view, collectionGroup, model, addLine);
1182         }
1183     }
1184 
1185     private void getParentTermType(AcademicTermWrapper childTerm) {
1186         try {
1187             ContextInfo context = createContextInfo();
1188             // check if child term is subterm or term and if it is (list is not empty) then add all parent terms to types
1189             List<TypeTypeRelationInfo> typeTypeRelationInfos = getTypeService().getTypeTypeRelationsByRelatedTypeAndType(childTerm.getTermType(), TypeServiceConstants.TYPE_TYPE_RELATION_CONTAINS_TYPE_KEY, context);
1190             //JIRA FIX : KSENROLL-8730 - Added NULL check
1191             if (null!=typeTypeRelationInfos && !typeTypeRelationInfos.isEmpty()) {
1192                 int firstTypeRelationInfo = 0;
1193                 TypeInfo parentTerm = getTypeService().getType(typeTypeRelationInfos.get(firstTypeRelationInfo).getOwnerTypeKey(), context);
1194                 childTerm.setParentTerm(parentTerm.getKey());
1195                 childTerm.setParentTermName(parentTerm.getName());
1196             }
1197         } catch (Exception e) {
1198             throw new RuntimeException(e);
1199         }
1200     }
1201 
1202     private AcademicTermWrapper getParentTermInForm(String parentTermType, List<AcademicTermWrapper> termWrapperList){
1203         for (AcademicTermWrapper termWrapper : termWrapperList){
1204             String termType = termWrapper.getTermType();
1205             if (StringUtils.isBlank(termType)){
1206                 termType = termWrapper.getTermInfo().getTypeKey();
1207             }
1208             if (parentTermType.equals(termType)){
1209                 return termWrapper;
1210             }
1211         }
1212         return null;
1213     }
1214 
1215     private void populateParentTermToSubterm(AcademicTermWrapper parentTermWrapper, AcademicTermWrapper newLine){
1216         List<KeyDatesGroupWrapper> newKeyDatesGroupWrappers = new ArrayList<KeyDatesGroupWrapper>();
1217         for(KeyDatesGroupWrapper keyDatesGroupWrapper : parentTermWrapper.getKeyDatesGroupWrappers()){
1218             KeyDatesGroupWrapper newKeyDatesGroup =
1219                     new KeyDatesGroupWrapper(keyDatesGroupWrapper.getKeyDateGroupType(),
1220                                              keyDatesGroupWrapper.getKeyDateGroupNameUI());
1221             List<KeyDateWrapper> newKeyDates = newKeyDatesGroup.getKeydates();
1222             for(KeyDateWrapper keyDateWrapper: keyDatesGroupWrapper.getKeydates()){
1223                 KeyDateWrapper newKeyDateWrapper = new KeyDateWrapper();
1224                 newKeyDateWrapper.setKeyDateType(keyDateWrapper.getKeyDateType());
1225                 newKeyDateWrapper.setKeyDateNameUI(keyDateWrapper.getKeyDateNameUI());
1226                 newKeyDateWrapper.setAllDay(keyDateWrapper.isAllDay());
1227                 newKeyDateWrapper.setDateRange(keyDateWrapper.isDateRange());
1228                 newKeyDates.add(newKeyDateWrapper);
1229             }
1230             newKeyDatesGroupWrappers.add(newKeyDatesGroup);
1231         }
1232         newLine.setKeyDatesGroupWrappers(newKeyDatesGroupWrappers);
1233 
1234         newLine.setParentTermInfo(parentTermWrapper.getTermInfo());
1235     }
1236 
1237     public AcademicCalendarService getAcalService() {
1238            if(acalService == null) {
1239              acalService = (AcademicCalendarService) GlobalResourceLoader.getService(new QName(AcademicCalendarServiceConstants.NAMESPACE, AcademicCalendarServiceConstants.SERVICE_NAME_LOCAL_PART));
1240         }
1241         return this.acalService;
1242     }
1243 
1244     public TypeService getTypeService() {
1245         if(typeService == null) {
1246              typeService = (TypeService) GlobalResourceLoader.getService(new QName(TypeServiceConstants.NAMESPACE, TypeServiceConstants.SERVICE_NAME_LOCAL_PART));
1247         }
1248         return this.typeService;
1249     }
1250 
1251     public AtpService getAtpService() {
1252         if(atpService == null) {
1253             atpService = (AtpService) GlobalResourceLoader.getService(new QName(AtpServiceConstants.NAMESPACE, AtpServiceConstants.SERVICE_NAME_LOCAL_PART));
1254         }
1255         return this.atpService;
1256     }
1257     public TermCodeGenerator getTermCodeGenerator() {
1258         if(termCodeGenerator==null){
1259             //TODO: Change this to get term code generator from the service calls instead of directly (KSENROLL-7233).
1260             termCodeGenerator = new TermCodeGeneratorImpl(); //(TermCodeGenerator) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/termcodegen","termCodeGenerator"));
1261         }
1262         return termCodeGenerator;
1263     }
1264 
1265     public void setTermCodeGenerator(TermCodeGenerator termCodeGenerator) {
1266         this.termCodeGenerator = termCodeGenerator;
1267     }
1268 
1269     protected String getAdminOrgNameById(String id){
1270         String adminOrgName = null;
1271         Map<String, String> allAcalOrgs = new HashMap<String, String>();
1272         allAcalOrgs.put("102", "Registrar's Office");
1273         allAcalOrgs.put("34", "Medical School");
1274 
1275         if(allAcalOrgs.containsKey(id)){
1276             adminOrgName = allAcalOrgs.get(id);
1277         }
1278 
1279         return adminOrgName;
1280     }
1281 
1282     /**
1283      * Generated a preview of the term code using the start date and type
1284      * Called by and AttributeQuery
1285      *
1286      * @param startDate - Start date of the term in either MM/dd/yyyy or MM-dd-yyyy format
1287      * @param typeKey - The type of term
1288      * @return The term code wrapped in a blank academic term wrapper
1289      */
1290     public AcademicTermWrapper termInfoAjaxQuery(String startDate, String typeKey){
1291         AcademicTermWrapper temp = new AcademicTermWrapper();
1292         String termCode;
1293         try{
1294             // Parse the date string
1295             Date date;
1296             try{
1297                 // Date format MM/dd/yyyy
1298                 date = DateFormatters.MONTH_DAY_YEAR_DATE_FORMATTER.parse(startDate);
1299             }catch(IllegalArgumentException e){
1300                 // Date format MM-dd-yyyy
1301                 date = DateFormatters.DEFAULT_DATE_FORMATTER.parse(startDate);
1302             }
1303 
1304             // Find what the created term code would be from the term code generator
1305             TermInfo term = new TermInfo();
1306             term.setTypeKey(typeKey);
1307             term.setStartDate(date);
1308             termCode = this.getTermCodeGenerator().generateTermCode(term);
1309 
1310         }catch (Exception e){
1311             // If code can not be determined from start date and term type key return empty code
1312             LOG.error("Unable to find term code using start date = " + startDate +" and type key = "+ typeKey);
1313             termCode="";
1314         }
1315 
1316         // Set term info code to the found code, wrap it, and return
1317         temp.getTermInfo().setCode(termCode);
1318         return temp;
1319     }
1320 
1321     /**
1322      * Sort the given AcademicTermWrapper list based on the start date
1323      *
1324      * @param termWrappers - AcademicTermWrapper list
1325      *
1326      */
1327     public void sortTermWrappers(List<AcademicTermWrapper> termWrappers) {
1328         //Sort the termWrappers by start date
1329         if (termWrappers != null & !termWrappers.isEmpty()) {
1330             Collections.sort(termWrappers, new Comparator<AcademicTermWrapper>() {
1331                 @Override
1332                 public int compare(AcademicTermWrapper termWrapper1, AcademicTermWrapper termWrapper2) {
1333                     int ret = 0;
1334                     if (!termWrapper1.isSubTerm() && !termWrapper2.isSubTerm()) { // term comp term
1335                         ret = termWrapper1.getStartDate().compareTo(termWrapper2.getStartDate());
1336                     }
1337                     if (!termWrapper1.isSubTerm() && termWrapper2.isSubTerm()) { // term comp subterm
1338                         if (termWrapper2.getParentTerm().compareTo(termWrapper1.getTermType()) == 0) { // term is  parent
1339                             ret = -1; // term > direct subterm
1340                         } else {      // term comp subterm.parent
1341                             ret = termWrapper1.getStartDate().compareTo(termWrapper2.getParentTermInfo().getStartDate());
1342                         }
1343                     }
1344                     if (termWrapper1.isSubTerm() && !termWrapper2.isSubTerm()) { // subterm comp term
1345                         if (termWrapper1.getParentTerm().compareTo(termWrapper2.getTermType()) == 0) { // term is  parent
1346                             ret = 1; // direct subterm < parent term
1347                         } else {      // subterm.parent comp term
1348                             ret = termWrapper1.getParentTermInfo().getStartDate().compareTo(termWrapper2.getStartDate());
1349                         }
1350                     }
1351                     if (termWrapper1.isSubTerm() && termWrapper2.isSubTerm()) { // subterm comp subterm
1352                         if (termWrapper1.getParentTerm().compareTo(termWrapper2.getParentTerm()) == 0) { // same parent
1353                             ret = termWrapper1.getStartDate().compareTo(termWrapper2.getStartDate());
1354                         } else {
1355                             ret = termWrapper1.getParentTermInfo().getStartDate().compareTo(termWrapper2.getParentTermInfo().getStartDate());
1356                         }
1357                     }
1358                     return ret;
1359                 }
1360             });
1361         }
1362     }
1363 
1364     /**
1365      * Validates the term at the given index
1366      *
1367      * @param termWrapperToValidate a term in an academic calendar
1368      * @param termToValidateIndex index of the term to be validated
1369      */
1370     public void validateExamPeriod (AcademicTermWrapper termWrapperToValidate, int termToValidateIndex) {
1371         String finalExamSectionName="acal-term-examdates_line"+termToValidateIndex;
1372         if (termWrapperToValidate.getExamdates() != null && termWrapperToValidate.getExamdates().size() > 0) {
1373             for (ExamPeriodWrapper examWrapper : termWrapperToValidate.getExamdates()){
1374                 // startDate must be before endDate
1375                 if (!AcalCommonUtils.isValidDateRange(examWrapper.getStartDate(), examWrapper.getEndDate())){
1376                     GlobalVariables.getMessageMap().putErrorForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_INVALID_DATE_RANGE, examWrapper.getExamPeriodNameUI(), AcalCommonUtils.formatDate(examWrapper.getStartDate()), AcalCommonUtils.formatDate(examWrapper.getEndDate()));
1377                 }
1378                 // Warning message when Start and End Dates of the exam period not within the term period.
1379                 if (!AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getStartDate(), termWrapperToValidate.getEndDate(), examWrapper.getStartDate()) ||
1380                         !AcalCommonUtils.isDateWithinRange(termWrapperToValidate.getStartDate(), termWrapperToValidate.getEndDate(), examWrapper.getEndDate())){
1381                     GlobalVariables.getMessageMap().putWarningForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_TERM_NOT_IN_TERM_RANGE,examWrapper.getExamPeriodNameUI(),termWrapperToValidate.getName());
1382                 }
1383                 // Both or neither dates should be filled
1384                 if ( examWrapper.getStartDate()!= null && !examWrapper.getStartDate().equals("") && (examWrapper.getEndDate() == null || examWrapper.getEndDate().equals(""))) {
1385                     GlobalVariables.getMessageMap().putErrorForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_KEY_DATE_END_DATE_REQUIRED, examWrapper.getExamPeriodNameUI());
1386                 }
1387                 if ( examWrapper.getEndDate()!= null && !examWrapper.getEndDate().equals("") && (examWrapper.getStartDate() == null || examWrapper.getStartDate().equals(""))) {
1388                     GlobalVariables.getMessageMap().putErrorForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_KEY_DATE_START_DATE_REQUIRED, examWrapper.getExamPeriodNameUI());
1389                 }
1390                 // Warn if both dates are empty
1391                 if( (examWrapper.getStartDate()== null || examWrapper.getStartDate().equals("")) && (examWrapper.getEndDate()== null || examWrapper.getEndDate().equals(""))) {
1392                     GlobalVariables.getMessageMap().putWarningForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_EMPTY_DATES, examWrapper.getExamPeriodNameUI());
1393                 }
1394             }
1395         }
1396     }
1397 
1398     /**
1399      * Validates the exam period days given the term that it is associated with
1400      *
1401      * @param termWrapperToValidate term wrapper that the exam period to be validated is associated with
1402      * @param holidayInfos list of holidayinfos of the academic calendar
1403      * @param termIndex index of the term to be validated
1404      */
1405     private void validateExamPeriodDays(AcademicTermWrapper termWrapperToValidate, List<HolidayInfo> holidayInfos, int termIndex) throws Exception {
1406         //trap null parameters
1407         if (termWrapperToValidate == null) {
1408             throw new Exception("term wrapper is null");
1409         }
1410 
1411         String finalExamSectionName="acal-term-examdates_line"+termIndex;
1412         SelectControl select = (SelectControl) ComponentFactory.getNewComponentInstance("KSFE-FinalExam-ExamDaysDropdown");
1413         int maxday = 0;
1414         for(KeyValue value : select.getOptions()){
1415             maxday = Math.max(Integer.valueOf(value.getKey()), maxday);
1416         }
1417 
1418         if (termWrapperToValidate.getExamdates()!=null && !termWrapperToValidate.getExamdates().isEmpty()) {
1419             for (ExamPeriodWrapper examPeriodWrapper : termWrapperToValidate.getExamdates()){
1420                 if (getDaysForExamPeriod(examPeriodWrapper, holidayInfos, createContextInfo()) < maxday) {
1421                     GlobalVariables.getMessageMap().putErrorForSectionId(finalExamSectionName, CalendarConstants.MessageKeys.ERROR_EXAM_PERIOD_DAYS_VALIDATION);
1422                 }
1423             }
1424         }
1425 
1426     }
1427 
1428     /**
1429      * Calculate and returns the valid number of final exam period days based on the excludeSaturday/excludeSunday setting.
1430      * Also, overlapping non-instructional holidays will be subtracted as well.
1431      *
1432      * @param examPeriodWrapper exam period wrapper
1433      * @param holidayInfos list of holidayinfos of the academic calendar
1434      */
1435     private int getDaysForExamPeriod(ExamPeriodWrapper examPeriodWrapper, List<HolidayInfo> holidayInfos, ContextInfo contextInfo) throws Exception {
1436         //trap null parameters
1437         if (examPeriodWrapper == null){
1438             throw new Exception("Exam Period wrapper is null");
1439         }
1440 
1441         int examPeriodDays = 0;
1442         boolean excludeSaturday = examPeriodWrapper.isExcludeSaturday();
1443         boolean excludeSunday = examPeriodWrapper.isExcludeSunday();
1444 
1445         DateMidnight currentDateExamPeriod = new DateMidnight(examPeriodWrapper.getStartDate().getTime());
1446         DateMidnight endDateExamPeriod = new DateMidnight(examPeriodWrapper.getEndDate().getTime());
1447 
1448         // go from start to end and count exam period days
1449         while (currentDateExamPeriod.compareTo(endDateExamPeriod) <= 0) {
1450             // if it is Saturday or Sunday and the exam period set exclude Saturday or Sunday attr
1451             // do not count that day
1452             if(!(((currentDateExamPeriod.getDayOfWeek() == DateTimeConstants.SATURDAY) && excludeSaturday)
1453                     || ((currentDateExamPeriod.getDayOfWeek() == DateTimeConstants.SUNDAY) && excludeSunday))){
1454                 ++examPeriodDays;
1455             }
1456 
1457             currentDateExamPeriod = currentDateExamPeriod.plusDays(1);
1458         }
1459 
1460         //if there is a holiday calendar for the academic calendar where the exam period is in,
1461         //check if there are holidays overlapping with the exam period
1462         if (holidayInfos != null && !holidayInfos.isEmpty()) {
1463             List<DateMidnight> holidayDatesToSubtract = new ArrayList<DateMidnight>();
1464             for (HolidayInfo holidayInfo : holidayInfos) {
1465                 Boolean isInstDay = holidayInfo.getIsInstructionalDay();
1466                 Boolean isDateRange = holidayInfo.getIsDateRange();
1467                 Date holStartDate = holidayInfo.getStartDate();
1468                 Date holEndDate = holidayInfo.getEndDate();
1469 
1470                 // If's it's not a range then the start and end dates are the same
1471                 if(!isDateRange){
1472                     holEndDate = holStartDate;
1473                 }
1474 
1475                 // if holiday is an instructional day, it doesn't need to be subtracted from the exam period
1476                 if(!isInstDay) {
1477                     DateMidnight currentDate = new DateMidnight(holStartDate.getTime());
1478                     DateMidnight stopDate = new DateMidnight(holEndDate.getTime());
1479                     while (currentDate.compareTo(stopDate) <= 0) {
1480                         if (doDatesOverlap(examPeriodWrapper.getStartDate(), examPeriodWrapper.getEndDate(), currentDate.toDate(), currentDate.toDate())) {
1481                             //if holiday is on Saturday or Sunday and excludeSaturday/excludeSunday is set,
1482                             //the holiday doesn't need to be subtracted again because the Saturday/Sunday has already been excluded
1483                             if(!(((currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) && excludeSaturday)
1484                                     || ((currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) && excludeSunday))){
1485                                 if (!holidayDatesToSubtract.contains(currentDate)) {
1486                                     holidayDatesToSubtract.add(currentDate);
1487                                     --examPeriodDays;
1488                                 }
1489                             }
1490                         }
1491                         currentDate = currentDate.plusDays(1);
1492                     }
1493                 }
1494             }
1495         }
1496 
1497         return examPeriodDays;
1498     }
1499 
1500     private boolean doDatesOverlap(Date periodStartDate, Date periodEndDate, Date subStart, Date subEnd){
1501         boolean bRet = false;
1502 
1503         int compStart = subStart.compareTo(periodEndDate);
1504         int compEnd = subEnd.compareTo(periodStartDate);
1505         if (compStart <= 0 && compEnd >= 0) {
1506             bRet = true;
1507         }
1508 
1509         return bRet;
1510     }
1511 }