001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sections/tags/sakai_2-4-1/sections-impl/sakai/model/src/java/org/sakaiproject/component/section/sakai/CourseSectionImpl.java $
003: * $Id: CourseSectionImpl.java 21967 2007-02-27 19:51:10Z jholtzman@berkeley.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006 The Regents of the University of California and The Regents of the University of Michigan
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.component.section.sakai;
021:
022: import java.io.Serializable;
023: import java.sql.Time;
024: import java.text.SimpleDateFormat;
025: import java.util.ArrayList;
026: import java.util.Iterator;
027: import java.util.List;
028:
029: import org.apache.commons.lang.StringUtils;
030: import org.apache.commons.lang.builder.EqualsBuilder;
031: import org.apache.commons.lang.builder.HashCodeBuilder;
032: import org.apache.commons.lang.builder.ToStringBuilder;
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.sakaiproject.section.api.coursemanagement.Course;
036: import org.sakaiproject.section.api.coursemanagement.CourseSection;
037: import org.sakaiproject.section.api.coursemanagement.Meeting;
038: import org.sakaiproject.entity.api.ResourceProperties;
039: import org.sakaiproject.site.api.Group;
040:
041: public class CourseSectionImpl implements CourseSection,
042: Comparable<CourseSection>, Serializable {
043:
044: private static final long serialVersionUID = 1L;
045: private static final String TIME_FORMAT_LONG = "h:mm a";
046: private static final Log log = LogFactory
047: .getLog(CourseSectionImpl.class);
048: public static final String SEP_CHARACTER = ",";
049: public static final String CATEGORY = "sections_category";
050: public static final String END_TIME = "sections_end_time";
051: public static final String START_TIME = "sections_start_time";
052: public static final String LOCATION = "sections_location";
053: public static final String MAX_ENROLLMENTS = "sections_max_enrollments";
054: public static final String MONDAY = "sections_monday";
055: public static final String TUESDAY = "sections_tuesday";
056: public static final String WEDNESDAY = "sections_wednesday";
057: public static final String THURSDAY = "sections_thursday";
058: public static final String FRIDAY = "sections_friday";
059: public static final String SATURDAY = "sections_saturday";
060: public static final String SUNDAY = "sections_sunday";
061: public static final String EID = "sections_eid";
062:
063: // Fields from Site Group
064: protected String description;
065:
066: // Fields from CourseSection
067: protected String uuid;
068: protected Course course;
069: protected String category;
070: protected Integer maxEnrollments;
071: protected List<Meeting> meetings;
072: protected String title;
073: protected String eid;
074:
075: // Transient holder for the framework group being decorated.
076: private transient Group group;
077:
078: /**
079: * Convenience constructor to create a CourseSection with a single meeting.
080: *
081: * @param course
082: * @param title
083: * @param uuid
084: * @param category
085: * @param maxEnrollments
086: * @param location
087: * @param startTime
088: * @param endTime
089: * @param monday
090: * @param tuesday
091: * @param wednesday
092: * @param thursday
093: * @param friday
094: * @param saturday
095: * @param sunday
096: */
097: public CourseSectionImpl(Course course, String title, String uuid,
098: String category, Integer maxEnrollments, String location,
099: Time startTime, Time endTime, boolean monday,
100: boolean tuesday, boolean wednesday, boolean thursday,
101: boolean friday, boolean saturday, boolean sunday) {
102:
103: this .course = course;
104: this .title = title;
105: this .uuid = uuid;
106: this .category = category;
107: this .maxEnrollments = maxEnrollments;
108: this .meetings = new ArrayList<Meeting>();
109: this .meetings.add(new MeetingImpl(location, startTime, endTime,
110: monday, tuesday, wednesday, thursday, friday, saturday,
111: sunday));
112: }
113:
114: public CourseSectionImpl(Group group) {
115: this .meetings = new ArrayList<Meeting>();
116: // We always start with a single empty meeting
117: meetings.add(new MeetingImpl());
118: this .group = group;
119: ResourceProperties props = group.getProperties();
120: this .uuid = group.getReference();
121: this .title = group.getTitle();
122: this .description = group.getContainingSite().getTitle() + ", "
123: + this .title;
124: this .course = new CourseImpl(group.getContainingSite());
125: this .category = props.getProperty(CourseSectionImpl.CATEGORY);
126: String str = props.getProperty(MAX_ENROLLMENTS);
127: if (StringUtils.trimToNull(str) != null) {
128: try {
129: this .maxEnrollments = Integer.valueOf(str);
130: } catch (NumberFormatException nfe) {
131: if (log.isDebugEnabled())
132: log.debug("can not parse integer property for "
133: + CourseSectionImpl.MAX_ENROLLMENTS);
134: }
135: }
136:
137: // Get the EID from the group. If the EID property exists, use it. If it doesn't
138: // exist, but the group has a provider ID, copy the provider ID to the EID field.
139: String groupEid = StringUtils.trimToNull(props
140: .getProperty(CourseSectionImpl.EID));
141: if (groupEid == null) {
142: // Try the provider ID
143: String providerId = StringUtils.trimToNull(group
144: .getProviderGroupId());
145: if (providerId != null) {
146: // There is a provider id, so update the group and this section
147: props.addProperty(CourseSectionImpl.EID, providerId);
148: this .eid = providerId;
149: }
150: } else {
151: this .eid = groupEid;
152: }
153:
154: // Parse the meetings for this group. Use a field that can't be null, such as "monday" (which must be T/F)
155: long numMeetings = 0;
156: String mondays = props.getProperty(CourseSectionImpl.MONDAY);
157: if (mondays != null) {
158: try {
159: numMeetings = mondays
160: .split(CourseSectionImpl.SEP_CHARACTER).length;
161: if (log.isDebugEnabled())
162: log.debug("Found " + numMeetings
163: + " meetings in group " + group);
164: } catch (Exception e) {
165: log
166: .warn("Could not parse the number of meetings for group "
167: + group);
168: }
169: }
170:
171: // If we have legitimate meetings, remove the placeholder
172: if (numMeetings > 0) {
173: meetings.clear();
174: if (log.isDebugEnabled())
175: log.debug("Constructing a CourseSectionImpl with "
176: + numMeetings + " meetings");
177: } else {
178: if (log.isDebugEnabled())
179: log
180: .debug("Constructing a CourseSectionImpl with one default meeting");
181: }
182:
183: // Iterate through the meeting properties and add the meetings to the group
184: for (int i = 0; i < numMeetings; i++) {
185: String location = getIndexedStringProperty(i, props
186: .getProperty(CourseSectionImpl.LOCATION));
187: Time endTime = CourseSectionImpl
188: .convertStringToTime(getIndexedStringProperty(i,
189: props.getProperty(END_TIME)));
190: Time startTime = CourseSectionImpl
191: .convertStringToTime(getIndexedStringProperty(i,
192: props.getProperty(START_TIME)));
193:
194: boolean monday = false;
195: boolean tuesday = false;
196: boolean wednesday = false;
197: boolean thursday = false;
198: boolean friday = false;
199: boolean saturday = false;
200: boolean sunday = false;
201:
202: try {
203: monday = getIndexedBooleanProperty(i, props
204: .getProperty(CourseSectionImpl.MONDAY));
205: } catch (Exception e) {
206: if (log.isDebugEnabled())
207: log.debug("can not parse boolean property for " + i
208: + " member of complex string "
209: + CourseSectionImpl.MONDAY);
210: }
211: try {
212: tuesday = getIndexedBooleanProperty(i, props
213: .getProperty(CourseSectionImpl.TUESDAY));
214: } catch (Exception e) {
215: if (log.isDebugEnabled())
216: log.debug("can not parse boolean property for " + i
217: + " member of complex string "
218: + CourseSectionImpl.TUESDAY);
219: }
220: try {
221: wednesday = getIndexedBooleanProperty(i, props
222: .getProperty(CourseSectionImpl.WEDNESDAY));
223: } catch (Exception e) {
224: if (log.isDebugEnabled())
225: log.debug("can not parse boolean property for " + i
226: + " member of complex string "
227: + CourseSectionImpl.WEDNESDAY);
228: }
229: try {
230: thursday = getIndexedBooleanProperty(i, props
231: .getProperty(CourseSectionImpl.THURSDAY));
232: } catch (Exception e) {
233: if (log.isDebugEnabled())
234: log.debug("can not parse boolean property for " + i
235: + " member of complex string "
236: + CourseSectionImpl.THURSDAY);
237: }
238: try {
239: friday = getIndexedBooleanProperty(i, props
240: .getProperty(CourseSectionImpl.FRIDAY));
241: } catch (Exception e) {
242: if (log.isDebugEnabled())
243: log.debug("can not parse boolean property for " + i
244: + " member of complex string "
245: + CourseSectionImpl.FRIDAY);
246: }
247: try {
248: saturday = getIndexedBooleanProperty(i, props
249: .getProperty(CourseSectionImpl.SATURDAY));
250: } catch (Exception e) {
251: if (log.isDebugEnabled())
252: log.debug("can not parse boolean property for " + i
253: + " member of complex string "
254: + CourseSectionImpl.SATURDAY);
255: }
256: try {
257: sunday = getIndexedBooleanProperty(i, props
258: .getProperty(CourseSectionImpl.SUNDAY));
259: } catch (Exception e) {
260: if (log.isDebugEnabled())
261: log.debug("can not parse boolean property for " + i
262: + " member of complex string "
263: + CourseSectionImpl.SUNDAY);
264: }
265:
266: // Now that we've parsed the group, add the meeting to the list
267: meetings.add(new MeetingImpl(location, startTime, endTime,
268: monday, tuesday, wednesday, thursday, friday,
269: saturday, sunday));
270: }
271:
272: }
273:
274: private boolean getIndexedBooleanProperty(int index,
275: String complexString) {
276: String[] sa = StringUtils.splitPreserveAllTokens(complexString,
277: CourseSectionImpl.SEP_CHARACTER);
278: if (sa == null) {
279: return false;
280: }
281: if (index >= sa.length) {
282: log.warn("Can not get " + index + " index from string "
283: + complexString);
284: return false;
285: }
286: return Boolean.parseBoolean(sa[index]);
287: }
288:
289: private String getIndexedStringProperty(int index,
290: String complexString) {
291: if (complexString == null) {
292: return null;
293: }
294: String[] sa = StringUtils.splitPreserveAllTokens(complexString,
295: CourseSectionImpl.SEP_CHARACTER);
296: if (index >= sa.length) {
297: log.warn("Can not get " + index + " index from string "
298: + complexString);
299: return null;
300: }
301: return sa[index];
302: }
303:
304: public static final String convertTimeToString(Time time) {
305: if (time == null) {
306: return null;
307: }
308: SimpleDateFormat sdf = new SimpleDateFormat(
309: CourseSectionImpl.TIME_FORMAT_LONG);
310: return sdf.format(time);
311: }
312:
313: public static final Time convertStringToTime(String str) {
314: if (StringUtils.trimToNull(str) == null) {
315: return null;
316: }
317: try {
318: SimpleDateFormat sdf = new SimpleDateFormat(
319: CourseSectionImpl.TIME_FORMAT_LONG);
320: return new Time(sdf.parse(str).getTime());
321: } catch (Exception e) {
322: if (log.isDebugEnabled())
323: log.debug("Unable to parse " + str);
324: return null;
325: }
326: }
327:
328: /**
329: * Decorates the framework's section (group) with metadata from this CourseSection.
330: *
331: * @param group The framework group
332: */
333: public void decorateGroup(Group group) {
334: ResourceProperties props = group.getProperties();
335: group.setTitle(title);
336: group.setDescription(description);
337: props.addProperty(CourseSectionImpl.CATEGORY, category);
338: if (maxEnrollments == null) {
339: props.removeProperty(CourseSectionImpl.MAX_ENROLLMENTS);
340: } else {
341: props.addProperty(CourseSectionImpl.MAX_ENROLLMENTS,
342: maxEnrollments.toString());
343: }
344:
345: // If we have a non-null eid, update the group.
346: if (StringUtils.trimToNull(eid) != null) {
347: props.addProperty(CourseSectionImpl.EID, eid);
348: }
349:
350: // Add the properties that containing the meeting metadata
351: StringBuffer locationBuffer = new StringBuffer();
352:
353: // Ensure that we've got a meetings object
354: if (meetings == null) {
355: meetings = new ArrayList<Meeting>();
356: }
357:
358: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
359: Meeting meeting = (Meeting) iter.next();
360: // Ensure that the location has no SEP_CHARACTERs in it
361: String meetingLocation = meeting.getLocation();
362: if (meetingLocation == null) {
363: meetingLocation = "";
364: } else {
365: meetingLocation = meetingLocation.replaceAll(
366: CourseSectionImpl.SEP_CHARACTER, "");
367: }
368: locationBuffer.append(meetingLocation);
369: if (iter.hasNext()) {
370: locationBuffer.append(CourseSectionImpl.SEP_CHARACTER);
371: }
372: }
373: if (log.isDebugEnabled())
374: log.debug("Setting group property "
375: + CourseSectionImpl.LOCATION + " to "
376: + locationBuffer.toString());
377: props.addProperty(CourseSectionImpl.LOCATION, locationBuffer
378: .toString());
379:
380: StringBuffer startTimeBuffer = new StringBuffer();
381: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
382: Meeting meeting = (Meeting) iter.next();
383: Time meetingStart = meeting.getStartTime();
384: if (meetingStart != null) {
385: startTimeBuffer.append(CourseSectionImpl
386: .convertTimeToString(meetingStart));
387: }
388: if (iter.hasNext()) {
389: startTimeBuffer.append(CourseSectionImpl.SEP_CHARACTER);
390: }
391: }
392: if (log.isDebugEnabled())
393: log.debug("Setting group property "
394: + CourseSectionImpl.START_TIME + " to "
395: + startTimeBuffer.toString());
396: props.addProperty(CourseSectionImpl.START_TIME, startTimeBuffer
397: .toString());
398:
399: StringBuffer endTimeBuffer = new StringBuffer();
400: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
401: Meeting meeting = (Meeting) iter.next();
402: Time meetingEnd = meeting.getEndTime();
403: if (meetingEnd != null) {
404: endTimeBuffer.append(CourseSectionImpl
405: .convertTimeToString(meetingEnd));
406: }
407: if (iter.hasNext()) {
408: endTimeBuffer.append(CourseSectionImpl.SEP_CHARACTER);
409: }
410: }
411: if (log.isDebugEnabled())
412: log.debug("Setting group property "
413: + CourseSectionImpl.END_TIME + " to "
414: + endTimeBuffer.toString());
415: props.addProperty(CourseSectionImpl.END_TIME, endTimeBuffer
416: .toString());
417:
418: StringBuffer mondayBuffer = new StringBuffer();
419: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
420: Meeting meeting = (Meeting) iter.next();
421: mondayBuffer.append(meeting.isMonday());
422: if (iter.hasNext()) {
423: mondayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
424: }
425: }
426: if (log.isDebugEnabled())
427: log.debug("Setting group property "
428: + CourseSectionImpl.MONDAY + " to "
429: + mondayBuffer.toString());
430: props.addProperty(CourseSectionImpl.MONDAY, mondayBuffer
431: .toString());
432:
433: StringBuffer tuesdayBuffer = new StringBuffer();
434: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
435: Meeting meeting = (Meeting) iter.next();
436: tuesdayBuffer.append(meeting.isTuesday());
437: if (iter.hasNext()) {
438: tuesdayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
439: }
440: }
441: if (log.isDebugEnabled())
442: log.debug("Setting group property "
443: + CourseSectionImpl.TUESDAY + " to "
444: + tuesdayBuffer.toString());
445: props.addProperty(CourseSectionImpl.TUESDAY, tuesdayBuffer
446: .toString());
447:
448: StringBuffer wednesdayBuffer = new StringBuffer();
449: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
450: Meeting meeting = (Meeting) iter.next();
451: wednesdayBuffer.append(Boolean.valueOf(meeting
452: .isWednesday()));
453: if (iter.hasNext()) {
454: wednesdayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
455: }
456: }
457: if (log.isDebugEnabled())
458: log.debug("Setting group property "
459: + CourseSectionImpl.WEDNESDAY + " to "
460: + wednesdayBuffer.toString());
461: props.addProperty(CourseSectionImpl.WEDNESDAY, wednesdayBuffer
462: .toString());
463:
464: StringBuffer thursdayBuffer = new StringBuffer();
465: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
466: Meeting meeting = (Meeting) iter.next();
467: thursdayBuffer
468: .append(Boolean.valueOf(meeting.isThursday()));
469: if (iter.hasNext()) {
470: thursdayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
471: }
472: }
473: if (log.isDebugEnabled())
474: log.debug("Setting group property "
475: + CourseSectionImpl.THURSDAY + " to "
476: + thursdayBuffer.toString());
477: props.addProperty(CourseSectionImpl.THURSDAY, thursdayBuffer
478: .toString());
479:
480: StringBuffer fridayBuffer = new StringBuffer();
481: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
482: Meeting meeting = (Meeting) iter.next();
483: fridayBuffer.append(Boolean.valueOf(meeting.isFriday()));
484: if (iter.hasNext()) {
485: fridayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
486: }
487: }
488: if (log.isDebugEnabled())
489: log.debug("Setting group property "
490: + CourseSectionImpl.FRIDAY + " to "
491: + fridayBuffer.toString());
492: props.addProperty(CourseSectionImpl.FRIDAY, fridayBuffer
493: .toString());
494:
495: StringBuffer saturdayBuffer = new StringBuffer();
496: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
497: Meeting meeting = (Meeting) iter.next();
498: saturdayBuffer
499: .append(Boolean.valueOf(meeting.isSaturday()));
500: if (iter.hasNext()) {
501: saturdayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
502: }
503: }
504: if (log.isDebugEnabled())
505: log.debug("Setting group property "
506: + CourseSectionImpl.SATURDAY + " to "
507: + saturdayBuffer.toString());
508: props.addProperty(CourseSectionImpl.SATURDAY, saturdayBuffer
509: .toString());
510:
511: StringBuffer sundayBuffer = new StringBuffer();
512: for (Iterator iter = meetings.iterator(); iter.hasNext();) {
513: Meeting meeting = (Meeting) iter.next();
514: sundayBuffer.append(Boolean.valueOf(meeting.isSunday()));
515: if (iter.hasNext()) {
516: sundayBuffer.append(CourseSectionImpl.SEP_CHARACTER);
517: }
518: }
519: if (log.isDebugEnabled())
520: log.debug("Setting group property "
521: + CourseSectionImpl.SUNDAY + " to "
522: + sundayBuffer.toString());
523: props.addProperty(CourseSectionImpl.SUNDAY, sundayBuffer
524: .toString());
525: }
526:
527: /* Bean getters & setters */
528:
529: public String getCategory() {
530: return category;
531: }
532:
533: public void setCategory(String category) {
534: this .category = category;
535: }
536:
537: public Course getCourse() {
538: return course;
539: }
540:
541: public void setCourse(Course course) {
542: this .course = course;
543: }
544:
545: public String getEid() {
546: return eid;
547: }
548:
549: public void setEid(String eid) {
550: this .eid = eid;
551: }
552:
553: public String getDescription() {
554: return description;
555: }
556:
557: public void setDescription(String description) {
558: this .description = description;
559: }
560:
561: public List<Meeting> getMeetings() {
562: return meetings;
563: }
564:
565: public void setMeetings(List<Meeting> meetings) {
566: this .meetings = meetings;
567: }
568:
569: public Integer getMaxEnrollments() {
570: return maxEnrollments;
571: }
572:
573: public void setMaxEnrollments(Integer maxEnrollments) {
574: this .maxEnrollments = maxEnrollments;
575: }
576:
577: public String getTitle() {
578: return title;
579: }
580:
581: public void setTitle(String title) {
582: this .title = title;
583: }
584:
585: public String getUuid() {
586: return uuid;
587: }
588:
589: public void setUuid(String uuid) {
590: this .uuid = uuid;
591: }
592:
593: public boolean equals(Object o) {
594: if (o == this ) {
595: return true;
596: }
597: if (o instanceof CourseSectionImpl) {
598: CourseSectionImpl other = (CourseSectionImpl) o;
599: return new EqualsBuilder().append(getUuid(),
600: other.getUuid()).isEquals();
601: }
602: return false;
603: }
604:
605: public int hashCode() {
606: return new HashCodeBuilder().append(getUuid()).toHashCode();
607: }
608:
609: /**
610: * Compares CourseSectionImpls based on their category ID and title. Sections
611: * without a category are sorted last.
612: */
613: public int compareTo(CourseSection other) {
614: if (other == this ) {
615: return 0;
616: }
617: if (this .category != null && other.getCategory() == null) {
618: return -1;
619: } else if (this .category == null && other.getCategory() != null) {
620: return 1;
621: }
622: if (this .category == null && other.getCategory() == null) {
623: return this .title.toLowerCase().compareTo(
624: other.getTitle().toLowerCase());
625: }
626: int categoryComparison = this .category.compareTo(other
627: .getCategory());
628: if (categoryComparison == 0) {
629: return this .title.toLowerCase().compareTo(
630: other.getTitle().toLowerCase());
631: } else {
632: return categoryComparison;
633: }
634:
635: }
636:
637: public String toString() {
638: return new ToStringBuilder(this ).append(title).append(uuid)
639: .append(category).append(maxEnrollments).toString();
640: }
641:
642: /**
643: * Access the group object being decorated. This field is transient, so this
644: * is likely to return null. This method should not be added to the CourseSection
645: * interface, since it is implementation dependent.
646: *
647: * @return The transient Group object being modeled.
648: */
649: public Group getGroup() {
650: return group;
651: }
652: }
|