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.courseoffering.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.kim.api.identity.Person;
20  import org.kuali.rice.kim.api.identity.PersonService;
21  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
22  import org.kuali.rice.kim.impl.KIMPropertyConstants;
23  import org.kuali.student.enrollment.class2.courseoffering.dto.CourseOfferingListSectionWrapper;
24  import org.kuali.student.enrollment.class2.scheduleofclasses.dto.ActivityOfferingDisplayWrapper;
25  import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
26  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingCrossListingInfo;
27  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
28  import org.kuali.student.enrollment.courseoffering.dto.FormatOfferingInfo;
29  import org.kuali.student.enrollment.courseoffering.dto.OfferingInstructorInfo;
30  import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
31  import org.kuali.student.r2.common.dto.ContextInfo;
32  import org.kuali.student.r2.common.dto.StatusInfo;
33  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
34  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
35  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
36  import org.kuali.student.r2.common.exceptions.MissingParameterException;
37  import org.kuali.student.r2.common.exceptions.OperationFailedException;
38  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
39  import org.kuali.student.r2.common.exceptions.ReadOnlyException;
40  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
41  import org.kuali.student.r2.common.util.ContextUtils;
42  import org.kuali.student.r2.common.util.constants.CourseOfferingServiceConstants;
43  import org.kuali.student.r2.common.util.constants.LuiServiceConstants;
44  import org.kuali.student.r2.core.acal.dto.TermInfo;
45  import org.kuali.student.r2.core.acal.service.AcademicCalendarService;
46  import org.kuali.student.r2.core.class1.search.ActivityOfferingSearchServiceImpl;
47  import org.kuali.student.r2.core.class1.search.CourseOfferingManagementSearchImpl;
48  import org.kuali.student.r2.core.class1.type.dto.TypeTypeRelationInfo;
49  import org.kuali.student.r2.core.class1.type.service.TypeService;
50  import org.kuali.student.r2.core.constants.TypeServiceConstants;
51  import org.kuali.student.r2.core.scheduling.dto.ScheduleRequestSetInfo;
52  import org.kuali.student.r2.core.scheduling.service.SchedulingService;
53  import org.kuali.student.r2.core.search.dto.SearchParamInfo;
54  import org.kuali.student.r2.core.search.dto.SearchRequestInfo;
55  import org.kuali.student.r2.core.search.dto.SearchResultCellInfo;
56  import org.kuali.student.r2.core.search.dto.SearchResultInfo;
57  import org.kuali.student.r2.core.search.dto.SearchResultRowInfo;
58  import org.kuali.student.r2.core.search.service.SearchService;
59  import org.kuali.student.r2.lum.clu.service.CluService;
60  import org.kuali.student.r2.lum.course.dto.ActivityInfo;
61  import org.kuali.student.r2.lum.course.dto.CourseInfo;
62  import org.kuali.student.r2.lum.course.dto.FormatInfo;
63  import org.kuali.student.r2.lum.course.service.CourseService;
64  
65  import java.io.Serializable;
66  import java.util.ArrayList;
67  import java.util.Arrays;
68  import java.util.Calendar;
69  import java.util.Collection;
70  import java.util.Collections;
71  import java.util.Comparator;
72  import java.util.Date;
73  import java.util.HashMap;
74  import java.util.List;
75  import java.util.Map;
76  
77  /**
78   * This class provides utility methods for Course Offering related ui
79   *
80   * @author Kuali Student Team
81   */
82  public class CourseOfferingViewHelperUtil {
83  
84      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CourseOfferingViewHelperUtil.class);
85  
86      public static List<Person> getInstructorByPersonId(String personId){
87          Map<String, String> searchCriteria = new HashMap<String, String>();
88          searchCriteria.put(KIMPropertyConstants.Person.PRINCIPAL_ID, personId);
89          List<Person> lstPerson = getPersonService().findPeople(searchCriteria);
90          return lstPerson;
91      }
92  
93      public static PersonService getPersonService() {
94          return KimApiServiceLocator.getPersonService();
95      }
96  
97      public static String trimTrailing0(String creditValue){
98          if (creditValue.indexOf(".0") > 0) {
99              return creditValue.substring(0, creditValue.length( )- 2);
100         } else {
101             return creditValue;
102         }
103     }
104 
105     public static OfferingInstructorInfo findDisplayInstructor(List<OfferingInstructorInfo> instructors) {
106         OfferingInstructorInfo result = null;
107 
108         if(instructors != null && !instructors.isEmpty()) {
109 
110             // Build the display name for the Instructor
111             Collection<OfferingInstructorInfo> highestInstEffortInstructors = new ArrayList<OfferingInstructorInfo>();
112             float highestInstEffortComparison = 0f;
113 
114             for (OfferingInstructorInfo instructor : instructors) {
115                 if(instructor.getPercentageEffort() != null){
116                     // if this instructor has a higher percent effort than any previous instructors,
117                     // clear the list we are keeping track of and set the new comparison number to this instructor's percentage effort
118                     if(instructor.getPercentageEffort() > highestInstEffortComparison) {
119                         highestInstEffortInstructors.clear();
120                         highestInstEffortComparison = instructor.getPercentageEffort();
121                         highestInstEffortInstructors.add(instructor);
122                     }
123                     // if this instructor's percent effort is tied with the comparison number,
124                     // add this instructor to the list of highest effort instructors
125                     else if (instructor.getPercentageEffort() == highestInstEffortComparison) {
126                         highestInstEffortInstructors.add(instructor);
127                     }
128                 }
129             }
130 
131             if(highestInstEffortInstructors.size() == 1) {
132                 result = highestInstEffortInstructors.iterator().next();
133             }
134             else {
135                 List<String> names = new ArrayList<String>(highestInstEffortInstructors.size());
136                 Map<String, OfferingInstructorInfo> nameMap = new HashMap<String, OfferingInstructorInfo>(highestInstEffortInstructors.size());
137                 for(OfferingInstructorInfo oiInfo : highestInstEffortInstructors) {
138                     names.add(oiInfo.getPersonName());
139                     nameMap.put(oiInfo.getPersonName(), oiInfo);
140                 }
141 
142                 Collections.sort(names);
143                 int firstName = 0;
144                 result = nameMap.get(names.get(firstName));
145             }
146         }
147 
148         return result;
149     }
150 
151     public static   CourseOfferingListSectionWrapper convertCourseOffering2ListSectionWrapper(CourseOfferingInfo coInfo){
152         CourseOfferingListSectionWrapper coWrapper = new CourseOfferingListSectionWrapper();
153         coWrapper.setSubjectArea(coInfo.getSubjectArea());
154         coWrapper.setCourseOfferingCode(coInfo.getCourseOfferingCode());
155         coWrapper.setCourseOfferingCreditOptionKey(coInfo.getCreditOptionId());
156         coWrapper.setCourseOfferingGradingOptionKey(coInfo.getGradingOptionId());
157         coWrapper.setCourseOfferingStateKey(coInfo.getStateKey());
158         coWrapper.setCourseOfferingDesc(coInfo.getCourseOfferingTitle());
159         coWrapper.setCourseOfferingId(coInfo.getId());
160         return coWrapper;
161     }
162 
163     public static void updateCourseOfferingStateFromActivityOfferingStateChange(CourseOfferingInfo coInfo, ContextInfo context) throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException, DataValidationErrorException, VersionMismatchException, ReadOnlyException {
164        updateCourseOfferingStateFromActivityOfferingStateChange(convertCourseOffering2ListSectionWrapper(coInfo),context);
165     }
166 
167     /**
168      * Evaluates whether to update the state of a Course Offering (and possibly its Format Offerings) based on
169      * the state of its Activity Offerings
170      *
171      * This is a utility method that combines logic for updating related objects when the state of one or more
172      * Activity Offerings is changed.
173      *
174      */
175     public static void updateCourseOfferingStateFromActivityOfferingStateChange(CourseOfferingListSectionWrapper coInfo, ContextInfo context) throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException, DataValidationErrorException, VersionMismatchException, ReadOnlyException {
176 
177         List<FormatOfferingInfo> formatOfferings = CourseOfferingManagementUtil.getCourseOfferingService().getFormatOfferingsByCourseOffering(coInfo.getCourseOfferingId(), context);
178 
179         String oldFoState, newFoState;
180         // Verify each FO, CO state with AO state consistence
181 
182         for (FormatOfferingInfo fo : formatOfferings) {
183             oldFoState = fo.getStateKey();
184             List<ActivityOfferingInfo> activityOfferings = CourseOfferingManagementUtil.getCourseOfferingService().getActivityOfferingsByFormatOffering(fo.getId(), context);
185             newFoState = getNewFoState(activityOfferings);
186 
187             if (newFoState != null && !StringUtils.equals(oldFoState, newFoState)) {
188                 fo.setStateKey(newFoState);
189                 StatusInfo statusInfo = CourseOfferingManagementUtil.getCourseOfferingService().changeFormatOfferingState(fo.getId(), newFoState, context);
190                 if (!statusInfo.getIsSuccess()){
191                      throw new RuntimeException(statusInfo.getMessage());
192                 }
193             }
194         }
195     }
196 
197     // if all of the AO states are Draft or Approved, the FO that owns the AO's cannot be Offered.  
198     // If no FO is Offered, then the CO which owns the FO's cannot be Offered.  
199     // It is possible for a CO with multiple FO's to have an Offered FO and one or more FO's in a non-Offered state;
200     // in that case, since at least one of the FO's is Offered, the CO which owns it is Offered.
201 
202     // If all of the AO's owned by an FO are deleted, that FO's state will change to Draft.  
203     // As above, if all of the FO's owned by a CO are Draft, the CO's state reverts to Draft;
204     // however, if the CO owns multiple FO's and only one of the FO's reverts to Draft, the CO state doesn't change if any of the other FO's is not in Draft state.
205     public static String getNewFoState(List<ActivityOfferingInfo> activityOfferings) {
206         boolean draftState= false;
207         boolean plannedState= false;
208         boolean offeredState= false;
209         boolean cancelledState= false;
210         boolean suspendedState= false;
211 
212         if(activityOfferings == null || activityOfferings.size() == 0) {
213             return LuiServiceConstants.LUI_FO_STATE_DRAFT_KEY;
214         }
215 
216         for (ActivityOfferingInfo ao : activityOfferings) {
217             if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_APPROVED_KEY, ao.getStateKey()) ||
218                     StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_SUBMITTED_KEY, ao.getStateKey()) ) {
219                 plannedState = true;
220             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_OFFERED_KEY, ao.getStateKey())) {
221                 offeredState = true;
222             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_CANCELED_KEY, ao.getStateKey())) {
223                 cancelledState = true;
224             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_DRAFT_KEY, ao.getStateKey())) {
225                 draftState = true;
226             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_SUSPENDED_KEY, ao.getStateKey())) {
227                 suspendedState = true;
228             }
229         }
230 
231         // if ALL the AOs within this FO are in a draft state (or if there are no AOs), and the current state of the FO is Planned, update the FO state to Draft
232         if (offeredState) {
233             return LuiServiceConstants.LUI_FO_STATE_OFFERED_KEY;
234         }  else if (plannedState) {
235             return LuiServiceConstants.LUI_FO_STATE_PLANNED_KEY;
236         }  else if (draftState) {
237             return LuiServiceConstants.LUI_FO_STATE_DRAFT_KEY;
238         }   else if (suspendedState) {
239             return LuiServiceConstants.LUI_FO_STATE_SUSPENDED_KEY;
240         }
241 
242         // no offered, planned, and draft state
243         if (cancelledState)  {
244             return LuiServiceConstants.LUI_FO_STATE_CANCELED_KEY;
245         }
246         // If all AOs are suspended
247         return null;
248     }
249 
250     public static String createTheCrossListedCos(CourseOfferingInfo coToShow){
251         if (coToShow != null && coToShow.getCrossListings() != null && coToShow.getCrossListings().size() > 0) {
252             // Always include an option for Course
253             //JIRA FIX : KSENROLL-8731 - Replaced StringBuffer with StringBuilder
254             StringBuilder crossListedCodes = new StringBuilder();
255 
256             for (CourseOfferingCrossListingInfo courseInfo : coToShow.getCrossListings()) {
257                 crossListedCodes.append(courseInfo.getCode());
258                 crossListedCodes.append(" ");
259             }
260             return crossListedCodes.toString();
261         }
262         return null;
263     }
264 
265     /**
266      * Builds a string of course and activity codes for display.
267      * @param ao
268      * @param context
269      */
270     public static String createColocatedDisplayData(ActivityOfferingInfo ao, ContextInfo context) throws InvalidParameterException, MissingParameterException, PermissionDeniedException,
271             OperationFailedException, DoesNotExistException {
272 
273         //JIRA FIX : KSENROLL-8731 - Replaced StringBuffer with StringBuilder
274         StringBuilder sb = new StringBuilder(" ");
275         List<ScheduleRequestSetInfo> scheduleRequestSets = CourseOfferingManagementUtil.getSchedulingService()
276                 .getScheduleRequestSetsByRefObject(CourseOfferingServiceConstants.REF_OBJECT_URI_ACTIVITY_OFFERING, ao.getId(), context);
277         for(ScheduleRequestSetInfo srs : scheduleRequestSets) {
278             List<ActivityOfferingInfo> aoList = CourseOfferingManagementUtil.getCourseOfferingService().getActivityOfferingsByIds(srs.getRefObjectIds(), context);
279             for(ActivityOfferingInfo aoInfo : aoList) {
280                 sb.append(aoInfo.getCourseOfferingCode() + " " + aoInfo.getActivityCode() + ActivityOfferingDisplayWrapper.BR);
281             }
282         }
283         return sb.toString();
284     }
285 
286     /**
287      * This method loads the wrapper details for the joint courses
288      *
289      * @param courseName CourseOfferingCreateWrapper
290      * @throws Exception throws one of the services exceptions
291      */
292     public static List<CourseInfo> getMatchingCoursesFromClu(String courseName) {
293 
294         CourseInfo returnCourseInfo;
295         String courseId;
296         List<SearchParamInfo> searchParams = new ArrayList<SearchParamInfo>();
297         List<CourseInfo> courseInfoList = new ArrayList<CourseInfo>();
298 
299         SearchParamInfo qpv1 = new SearchParamInfo();
300         qpv1.setKey("lu.queryParam.startsWith.cluCode");
301         qpv1.getValues().add(courseName.toUpperCase());
302         searchParams.add(qpv1);
303 
304         SearchParamInfo qpv2 = new SearchParamInfo();
305         qpv2.setKey("lu.queryParam.cluState");
306         qpv2.setValues(Arrays.asList("Active"));
307         searchParams.add(qpv2);
308 
309         SearchRequestInfo searchRequest = new SearchRequestInfo();
310         searchRequest.setParams(searchParams);
311         searchRequest.setSearchKey("lu.search.cluByCodeAndState");
312 
313         try {
314             SearchResultInfo searchResult = CourseOfferingManagementUtil.getCluService().search(searchRequest, ContextUtils.getContextInfo());
315             if (searchResult.getRows().size() > 0) {
316                 for (SearchResultRowInfo row : searchResult.getRows()) {
317                     List<SearchResultCellInfo> srCells = row.getCells();
318                     if (srCells != null && srCells.size() > 0) {
319                         for (SearchResultCellInfo cell : srCells) {
320                             if ("lu.resultColumn.cluId".equals(cell.getKey())) {
321                                 courseId = cell.getValue();
322                                 returnCourseInfo = CourseOfferingManagementUtil.getCourseService().getCourse(courseId, ContextUtils.getContextInfo());
323                                 courseInfoList.add(returnCourseInfo);
324                             }
325                         }
326                     }
327                 }
328             }
329         } catch (Exception e) {
330             throw new RuntimeException(e);
331         }
332 
333         return courseInfoList;
334     }
335 
336     /**
337      * Adds correct AO types to the format offering
338      * (Code fix for KSENROLL-6071, KSNEROLL-6074)
339      * @param fo The FO to fix up
340      * @param course Which course this is pertinent to
341      * @param context
342      * @throws PermissionDeniedException
343      * @throws MissingParameterException
344      * @throws InvalidParameterException
345      * @throws OperationFailedException
346      * @throws DoesNotExistException
347      */
348     public static void addActivityOfferingTypesToFormatOffering(FormatOfferingInfo fo, CourseInfo course,
349                                                            TypeService typeService, ContextInfo context)
350             throws PermissionDeniedException, MissingParameterException, InvalidParameterException, OperationFailedException,
351             DoesNotExistException {
352         if (fo.getActivityOfferingTypeKeys() != null && !fo.getActivityOfferingTypeKeys().isEmpty()) {
353             // Only bother with this if there are no AO type keys
354             return;
355         }
356         List<FormatInfo> formats = course.getFormats();
357         FormatInfo format = null;
358         for (FormatInfo f: formats) {
359             if (f.getId().equals(fo.getFormatId())) {
360                 // Find correct format
361                 format = f;
362                 break;
363             }
364         }
365         // Get the activity types
366         List<String> activityTypes = new ArrayList<String>();
367         for (ActivityInfo activityInfo: format.getActivities()) {
368             activityTypes.add(activityInfo.getTypeKey());
369         }
370         // Use type service to find corresponding AO types--assumes 1-1 mapping of Activity types to AO types
371         List<String> aoTypeKeys = new ArrayList<String>();
372         for (String activityType: activityTypes) {
373             List<TypeTypeRelationInfo> typeTypeRels =
374                     typeService.getTypeTypeRelationsByOwnerAndType(activityType,
375                             TypeServiceConstants.TYPE_TYPE_RELATION_ALLOWED_TYPE_KEY,
376                             context);
377             if (typeTypeRels.size() != 1) {
378                 // Ref data currently only has a 1-1 mapping between Activity types (CLU) and AO types (LUI)
379                 // The UI screens only support this.  Should there be a many-to-1 relation between AO types and Activity
380                 // types (as they were originally envisioned), then this exception will be thrown.
381                 throw new UnsupportedOperationException("Can't handle Activity Type -> AO Type that isn't 1-1.  Search for this message in Java code");
382             } else {
383                 String aoType = typeTypeRels.get(0).getRelatedTypeKey();
384                 aoTypeKeys.add(aoType);
385             }
386         }
387         // Finally, set the ao types for this fo
388         fo.setActivityOfferingTypeKeys(aoTypeKeys);
389     }
390 
391     /**
392      * Returns the day-of-the-year represented by the supplied term.
393      *
394      * @param termInfo  we will pull the term start and end dates from this. Both are required.
395      * @param academicCalendarService
396      * @param contextInfo
397      * @return int of the day-of-the-year that represents the term
398      * @throws DoesNotExistException
399      * @throws InvalidParameterException
400      * @throws MissingParameterException
401      * @throws OperationFailedException
402      * @throws PermissionDeniedException
403      */
404     public static int calculateTermDayOfYear( TermInfo termInfo, AcademicCalendarService academicCalendarService, ContextInfo contextInfo )
405         throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException, PermissionDeniedException {
406 
407         int termDayOfYear = 0; // default to 1st day of the year
408 
409         // Use the term start and end dates since they are required.
410         if(termInfo.getStartDate() != null
411            && termInfo.getEndDate() != null )
412         {
413             Date termClassStartDate = termInfo.getStartDate();
414             Date termClassEndDate = termInfo.getEndDate();
415             Date avgDate = new Date( termClassStartDate.getTime() + ( (termClassEndDate.getTime() - termClassStartDate.getTime()) /2 ) );
416             Calendar cal = Calendar.getInstance();
417             cal.setTime( avgDate) ;
418             termDayOfYear = cal.get( Calendar.DAY_OF_YEAR );
419         }
420 
421         return termDayOfYear;
422     }
423 
424     public static List<CourseOfferingInfo> loadCourseOfferings(SearchRequestInfo searchRequest) throws InvalidParameterException, MissingParameterException, PermissionDeniedException, OperationFailedException {
425         List<CourseOfferingInfo> result = new ArrayList<CourseOfferingInfo>();
426         ContextInfo contextInfo = new ContextInfo();
427         SearchResultInfo searchResult = CourseOfferingManagementUtil.getSearchService().search(searchRequest, contextInfo);
428 
429         for( SearchResultRowInfo row : searchResult.getRows() ) {
430             CourseOfferingInfo courseOfferingInfo = new CourseOfferingInfo();
431             for( SearchResultCellInfo cellInfo : row.getCells() ) {
432                 String value = StringUtils.defaultIfEmpty( cellInfo.getValue(), StringUtils.EMPTY );
433                 if( CourseOfferingManagementSearchImpl.SearchResultColumns.CODE.equals( cellInfo.getKey() ) ) {
434                     courseOfferingInfo.setCourseOfferingCode( value );
435                 }
436                 else if( CourseOfferingManagementSearchImpl.SearchResultColumns.CO_ID.equals( cellInfo.getKey() ) ) {
437                     courseOfferingInfo.setId( value );
438                 }
439             }
440             result.add( courseOfferingInfo );
441         }
442         return result;
443     }
444 
445     public static List<ActivityOfferingInfo> loadActivityOfferings(SearchRequestInfo searchRequest) throws InvalidParameterException, MissingParameterException, PermissionDeniedException, OperationFailedException {
446         List<ActivityOfferingInfo> result = new ArrayList<ActivityOfferingInfo>();
447         ContextInfo contextInfo = new ContextInfo();
448         SearchResultInfo searchResult = CourseOfferingManagementUtil.getSearchService().search(searchRequest, contextInfo);
449         List<SearchResultRowInfo> rows = searchResult.getRows();
450         if (!rows.isEmpty()) {
451             for (SearchResultRowInfo row: rows) {
452                 List<SearchResultCellInfo> cells = row.getCells();
453                 String aoId = null;
454                 String aoCode = null;
455                 String aoType = null;
456                 for (SearchResultCellInfo cell: cells) {
457                     if (cell.getKey().equals(ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_ID)) {
458                         aoId = cell.getValue();
459                     } else if (cell.getKey().equals(ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_CODE)) {
460                         aoCode = cell.getValue();
461                     } else if (cell.getKey().equals(ActivityOfferingSearchServiceImpl.SearchResultColumns.AO_TYPE)) {
462                         aoType = cell.getValue();
463                     } else {
464                         throw new OperationFailedException("Query for AO id, code, and type returned too many columns.");
465                     }
466                 }
467                 ActivityOfferingInfo aoLimitedInfo = new ActivityOfferingInfo();
468                 aoLimitedInfo.setActivityCode(aoCode);
469                 aoLimitedInfo.setId(aoId);
470                 aoLimitedInfo.setTypeKey(aoType);
471                 result.add(aoLimitedInfo);
472             }
473         }
474 
475         if(result.size() > 1) {
476             Collections.sort(result, new ActivityOfferingComparator());
477         }
478 
479         return result;
480     }
481 
482     private static class ActivityOfferingComparator implements Comparator<ActivityOfferingInfo>, Serializable {
483         @Override
484         public int compare(ActivityOfferingInfo o1, ActivityOfferingInfo o2) {
485             String value1 = o1.getActivityCode();
486             String value2 = o2.getActivityCode();
487 
488             int result = value1.compareToIgnoreCase(value2);
489             return result;
490         }
491     }
492 }