public void reloadOffering(
      final OnlineSectioningServer server, OnlineSectioningHelper helper, Long offeringId) {
    // Load new students
    Map<Long, org.unitime.timetable.model.Student> newStudents =
        new HashMap<Long, org.unitime.timetable.model.Student>();
    /*
    for (org.unitime.timetable.model.Student student : (List<org.unitime.timetable.model.Student>)helper.getHibSession().createQuery(
                  "select s from Student s " +
                  "left join fetch s.courseDemands as cd " +
                  "left join fetch cd.courseRequests as cr " +
                  "left join fetch cr.courseOffering as co " +
                  "left join fetch cr.classWaitLists as cwl " +
                  "left join fetch s.classEnrollments as e " +
                  "left join fetch s.academicAreaClassifications as a " +
                  "left join fetch s.posMajors as mj " +
                  "left join fetch s.waitlists as w " +
                  "left join fetch s.groups as g " +
                  "where s.uniqueId in (select xe.student.uniqueId from StudentClassEnrollment xe where xe.courseOffering.instructionalOffering.uniqueId = :offeringId) " +
                  "or s.uniqueId in (select xr.courseDemand.student.uniqueId from CourseRequest xr where xr.courseOffering.instructionalOffering.uniqueId = :offeringId)"
                  ).setLong("offeringId", offeringId).list()) {
    	newStudents.put(student.getUniqueId(), student);
    }
    */
    for (org.unitime.timetable.model.Student student :
        (List<org.unitime.timetable.model.Student>)
            helper
                .getHibSession()
                .createQuery(
                    "select distinct s from Student s "
                        + "left join s.courseDemands as cd "
                        + "left join cd.courseRequests as cr "
                        + "left join fetch s.classEnrollments as e "
                        + "left join fetch s.academicAreaClassifications as a "
                        + "left join fetch s.posMajors as mj "
                        + "left join fetch s.waitlists as w "
                        + "left join fetch s.groups as g "
                        + "where cr.courseOffering.instructionalOffering.uniqueId = :offeringId")
                .setLong("offeringId", offeringId)
                .list()) {
      newStudents.put(student.getUniqueId(), student);
    }
    for (org.unitime.timetable.model.Student student :
        (List<org.unitime.timetable.model.Student>)
            helper
                .getHibSession()
                .createQuery(
                    "select distinct s from Student s "
                        + "left join fetch s.courseDemands as cd "
                        + "left join fetch cd.courseRequests as cr "
                        + "left join fetch cr.courseOffering as co "
                        + "left join fetch cr.classWaitLists as cwl "
                        + "left join fetch s.classEnrollments as e "
                        + "left join fetch s.academicAreaClassifications as a "
                        + "left join fetch s.posMajors as mj "
                        + "left join fetch s.waitlists as w "
                        + "left join fetch s.groups as g "
                        + "where e.courseOffering.instructionalOffering.uniqueId = :offeringId and e.courseRequest is null")
                .setLong("offeringId", offeringId)
                .list()) {
      newStudents.put(student.getUniqueId(), student);
    }

    // Persist expected spaces if needed
    if (server.needPersistExpectedSpaces(offeringId))
      PersistExpectedSpacesAction.persistExpectedSpaces(offeringId, false, server, helper);

    // Existing offering
    XOffering oldOffering = server.getOffering(offeringId);
    XEnrollments oldEnrollments = server.getEnrollments(offeringId);

    // New offering
    XOffering newOffering = null;
    InstructionalOffering io =
        InstructionalOfferingDAO.getInstance().get(offeringId, helper.getHibSession());
    List<XDistribution> distributions = new ArrayList<XDistribution>();
    if (io != null) {
      // Load linked sections and ignore student conflict constraints
      List<DistributionPref> distPrefs =
          helper
              .getHibSession()
              .createQuery(
                  "select distinct p from DistributionPref p inner join p.distributionObjects o, Department d, "
                      + "Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering io "
                      + "where p.distributionType.reference in (:ref1, :ref2) and d.session.uniqueId = :sessionId "
                      + "and io.uniqueId = :offeringId and (o.prefGroup = c or o.prefGroup = c.schedulingSubpart) "
                      + "and p.owner = d and p.prefLevel.prefProlog = :pref")
              .setString("ref1", GroupConstraint.ConstraintType.LINKED_SECTIONS.reference())
              .setString("ref2", IgnoreStudentConflictsConstraint.REFERENCE)
              .setString("pref", PreferenceLevel.sRequired)
              .setLong("sessionId", server.getAcademicSession().getUniqueId())
              .setLong("offeringId", offeringId)
              .list();
      if (!distPrefs.isEmpty()) {
        for (DistributionPref pref : distPrefs) {
          int variant = 0;
          for (Collection<Class_> sections : ReloadAllData.getSections(pref)) {
            XDistributionType type = XDistributionType.IngoreConflicts;
            if (GroupConstraint.ConstraintType.LINKED_SECTIONS
                .reference()
                .equals(pref.getDistributionType().getReference()))
              type = XDistributionType.LinkedSections;
            XDistribution distribution =
                new XDistribution(type, pref.getUniqueId(), variant++, sections);
            distributions.add(distribution);
          }
        }
      }

      newOffering = ReloadAllData.loadOffering(io, distributions, server, helper);
      if (newOffering != null) server.update(newOffering);
      else if (oldOffering != null) server.remove(oldOffering);

      // Load sectioning info
      List<Object[]> infos =
          helper
              .getHibSession()
              .createQuery(
                  "select i.clazz.uniqueId, i.nbrExpectedStudents from SectioningInfo i where i.clazz.schedulingSubpart.instrOfferingConfig.instructionalOffering.uniqueId = :offeringId")
              .setLong("offeringId", offeringId)
              .list();
      XExpectations expectations = new XExpectations(offeringId);
      for (Object[] info : infos) {
        Long sectionId = (Long) info[0];
        Double expected = (Double) info[1];
        expectations.setExpectedSpace(sectionId, expected);
      }
      server.update(expectations);
    } else if (oldOffering != null) {
      server.remove(oldOffering);
    }

    List<XStudent[]> students = new ArrayList<XStudent[]>();

    if (oldEnrollments != null) {
      Set<Long> checked = new HashSet<Long>();
      for (XRequest old : oldEnrollments.getRequests()) {
        if (!checked.add(old.getStudentId())) continue;
        XStudent oldStudent = server.getStudent(old.getStudentId());
        org.unitime.timetable.model.Student student = newStudents.get(oldStudent.getStudentId());
        if (student == null)
          student = StudentDAO.getInstance().get(oldStudent.getStudentId(), helper.getHibSession());
        XStudent newStudent =
            (student == null ? null : ReloadAllData.loadStudent(student, null, server, helper));
        if (newStudent != null) server.update(newStudent, true);
        else server.remove(oldStudent);
        students.add(new XStudent[] {oldStudent, newStudent});
        newStudents.remove(oldStudent.getStudentId());
      }
    }
    for (org.unitime.timetable.model.Student student : newStudents.values()) {
      XStudent oldStudent = server.getStudent(student.getUniqueId());
      XStudent newStudent = ReloadAllData.loadStudent(student, null, server, helper);
      if (newStudent != null) server.update(newStudent, true);
      else if (oldStudent != null) server.remove(oldStudent);
      students.add(new XStudent[] {oldStudent, newStudent});
    }

    if (!server.getAcademicSession().isSectioningEnabled()) return;

    if (!CustomStudentEnrollmentHolder.isAllowWaitListing()) return;

    if (newOffering == null && oldOffering == null) return;

    Set<SectioningRequest> queue = new TreeSet<SectioningRequest>();

    Set<XCourseId> courseIds = new HashSet<XCourseId>();
    if (newOffering != null) courseIds.addAll(newOffering.getCourses());
    if (oldOffering != null) courseIds.addAll(oldOffering.getCourses());

    for (XCourseId course : courseIds) {
      for (XStudent[] student : students) {
        if (student[0] == null && student[1] == null) continue;
        XEnrollment oldEnrollment = null;
        XCourseRequest oldRequest = null;
        if (student[0] != null) {
          for (XRequest r : student[0].getRequests())
            if (r instanceof XCourseRequest) {
              XCourseRequest cr = (XCourseRequest) r;
              for (XCourseId c : cr.getCourseIds()) {
                if (c.equals(course)) {
                  oldRequest = cr;
                  if (cr.getEnrollment() != null
                      && offeringId.equals(cr.getEnrollment().getOfferingId()))
                    oldEnrollment = cr.getEnrollment();
                  break;
                }
              }
            }
        }
        XCourseRequest newRequest = null;
        XEnrollment newEnrollment = null;
        if (student[1] != null) {
          for (XRequest r : student[1].getRequests())
            if (r instanceof XCourseRequest) {
              XCourseRequest cr = (XCourseRequest) r;
              for (XCourseId c : cr.getCourseIds())
                if (c.equals(course)) {
                  newRequest = cr;
                  if (cr.getEnrollment() != null
                      && offeringId.equals(cr.getEnrollment().getOfferingId()))
                    newEnrollment = cr.getEnrollment();
                  break;
                }
            }
        }
        if (oldRequest == null && newRequest == null) continue;

        OnlineSectioningLog.Action.Builder action =
            helper.addAction(this, server.getAcademicSession());
        action.setStudent(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(
                    student[0] == null ? student[1].getStudentId() : student[0].getStudentId())
                .setExternalId(
                    student[0] == null ? student[1].getExternalId() : student[0].getExternalId()));
        action.addOther(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(offeringId)
                .setName(newOffering == null ? oldOffering.getName() : newOffering.getName())
                .setType(OnlineSectioningLog.Entity.EntityType.OFFERING));
        action.addOther(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(course.getCourseId())
                .setName(course.getCourseName())
                .setType(OnlineSectioningLog.Entity.EntityType.COURSE));

        if (oldEnrollment != null) {
          OnlineSectioningLog.Enrollment.Builder enrollment =
              OnlineSectioningLog.Enrollment.newBuilder();
          enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.PREVIOUS);
          for (Long sectionId : oldEnrollment.getSectionIds())
            enrollment.addSection(
                OnlineSectioningHelper.toProto(oldOffering.getSection(sectionId), oldEnrollment));
          action.addEnrollment(enrollment);
          if (newRequest == null) action.addRequest(OnlineSectioningHelper.toProto(oldRequest));
        }

        if (newRequest == null) {
          // nothing to re-assign
          if (oldEnrollment != null && CustomStudentEnrollmentHolder.hasProvider()) {
            // there was a drop
            try {
              CustomStudentEnrollmentHolder.getProvider()
                  .resection(
                      server,
                      helper,
                      new SectioningRequest(newOffering, newRequest, student[1], action)
                          .setOldOffering(oldOffering)
                          .setOldStudent(student[0])
                          .setOldRequest(oldRequest)
                          .setLastEnrollment(oldEnrollment),
                      null);
            } catch (Exception ex) {
              action.setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
              action.addMessage(
                  OnlineSectioningLog.Message.newBuilder()
                      .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                      .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
              helper.error("Unable to resection student: " + ex.getMessage(), ex);
            }
          }
          action.setEndTime(System.currentTimeMillis());
          server.execute(
              server
                  .createAction(NotifyStudentAction.class)
                  .forStudent(
                      student[0] == null ? student[1].getStudentId() : student[0].getStudentId())
                  .oldEnrollment(oldOffering, course, oldEnrollment),
              helper.getUser());
          continue;
        } else {
          action.addRequest(OnlineSectioningHelper.toProto(newRequest));
        }

        if (oldEnrollment == null && newEnrollment == null) {
          if (student[1].canAssign(newRequest)
              && isWaitListed(student[1], newRequest, server, helper))
            queue.add(
                new SectioningRequest(newOffering, newRequest, student[1], action)
                    .setOldOffering(oldOffering)
                    .setOldRequest(oldRequest)
                    .setOldStudent(student[0]));
          continue;
        }

        if (newEnrollment != null) {
          // new enrollment is valid and / or has all the same times
          if (check(
              newOffering,
              course,
              distributions,
              student[1],
              newEnrollment,
              server)) { // || isSame(oldEnrollment, newEnrollment)) {
            OnlineSectioningLog.Enrollment.Builder enrollment =
                OnlineSectioningLog.Enrollment.newBuilder();
            enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.STORED);
            for (XSection assignment : newOffering.getSections(newEnrollment))
              enrollment.addSection(OnlineSectioningHelper.toProto(assignment, newEnrollment));
            action.addEnrollment(enrollment);

            // there may have been a change
            if (CustomStudentEnrollmentHolder.hasProvider()) {
              try {
                CustomStudentEnrollmentHolder.getProvider()
                    .resection(
                        server,
                        helper,
                        new SectioningRequest(newOffering, newRequest, student[1], action)
                            .setOldOffering(oldOffering)
                            .setOldStudent(student[0])
                            .setOldRequest(oldRequest)
                            .setLastEnrollment(oldEnrollment),
                        newEnrollment);
              } catch (Exception ex) {
                action.setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
                action.addMessage(
                    OnlineSectioningLog.Message.newBuilder()
                        .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                        .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
                helper.error("Unable to resection student: " + ex.getMessage(), ex);
              }
            }
            action.setEndTime(System.currentTimeMillis());

            if (!isVerySame(
                newEnrollment.getCourseId(),
                newOffering.getSections(newEnrollment),
                oldOffering.getSections(oldEnrollment)))
              server.execute(
                  server
                      .createAction(NotifyStudentAction.class)
                      .forStudent(
                          student[0] == null
                              ? student[1].getStudentId()
                              : student[0].getStudentId())
                      .oldEnrollment(oldOffering, course, oldEnrollment),
                  helper.getUser());
            continue;
          }
        }
        newRequest = server.assign(newRequest, null);
        queue.add(
            new SectioningRequest(newOffering, newRequest, student[1], action)
                .setOldOffering(oldOffering)
                .setOldRequest(oldRequest)
                .setOldStudent(student[0])
                .setLastEnrollment(oldEnrollment)
                .setNewEnrollment(newEnrollment));
      }
    }

    if (!queue.isEmpty()) {
      DataProperties properties = new DataProperties();
      ResectioningWeights w = new ResectioningWeights(properties);
      DistanceConflict dc = new DistanceConflict(server.getDistanceMetric(), properties);
      TimeOverlapsCounter toc = new TimeOverlapsCounter(null, properties);
      Date ts = new Date();
      for (SectioningRequest r : queue) {
        helper.debug(
            "Resectioning "
                + r.getRequest()
                + " (was "
                + (r.getLastEnrollment() == null
                    ? "not assigned"
                    : r.getLastEnrollment().getSectionIds())
                + ")");
        long c0 = OnlineSectioningHelper.getCpuTime();
        XEnrollment e = r.resection(server, w, dc, toc);

        if (e != null) {
          e.setTimeStamp(ts);
          OnlineSectioningLog.Enrollment.Builder enrollment =
              OnlineSectioningLog.Enrollment.newBuilder();
          enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.STORED);
          for (Long sectionId : e.getSectionIds())
            enrollment.addSection(
                OnlineSectioningHelper.toProto(newOffering.getSection(sectionId), e));
          r.getAction().addEnrollment(enrollment);
        }

        if (CustomStudentEnrollmentHolder.hasProvider()) {
          try {
            e = CustomStudentEnrollmentHolder.getProvider().resection(server, helper, r, e);
          } catch (Exception ex) {
            r.getAction().setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
            r.getAction()
                .addMessage(
                    OnlineSectioningLog.Message.newBuilder()
                        .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                        .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
            helper.error("Unable to resection student: " + ex.getMessage(), ex);
            if (r.getNewEnrollment() != null) server.assign(r.getRequest(), r.getNewEnrollment());
            continue;
          }
        }

        if (e == null && r.getLastEnrollment() == null) {
          // remained unassigned
          continue;
        }

        if (e != null) {
          r.setRequest(server.assign(r.getRequest(), e));
        }
        helper.debug("New: " + (e == null ? "not assigned" : e.getSectionIds()));

        org.unitime.timetable.model.Student student =
            StudentDAO.getInstance().get(r.getRequest().getStudentId(), helper.getHibSession());
        Map<Long, StudentClassEnrollment> enrollmentMap =
            new HashMap<Long, StudentClassEnrollment>();
        String approvedBy = null;
        Date approvedDate = null;
        for (Iterator<StudentClassEnrollment> i = student.getClassEnrollments().iterator();
            i.hasNext(); ) {
          StudentClassEnrollment enrl = i.next();
          if ((enrl.getCourseRequest() != null
                  && enrl.getCourseRequest()
                      .getCourseDemand()
                      .getUniqueId()
                      .equals(r.getRequest().getRequestId()))
              || (enrl.getCourseOffering() != null
                  && enrl.getCourseOffering()
                      .getUniqueId()
                      .equals(r.getCourseId().getCourseId()))) {
            helper.debug("Deleting " + enrl.getClazz().getClassLabel());
            enrollmentMap.put(enrl.getClazz().getUniqueId(), enrl);
            if (approvedBy == null && enrl.getApprovedBy() != null) {
              approvedBy = enrl.getApprovedBy();
              approvedDate = enrl.getApprovedDate();
            }
            enrl.getClazz().getStudentEnrollments().remove(enrl);
            helper.getHibSession().delete(enrl);
            i.remove();
          }
        }
        CourseDemand cd = null;
        demands:
        for (CourseDemand x : student.getCourseDemands())
          for (org.unitime.timetable.model.CourseRequest q : x.getCourseRequests())
            if (q.getCourseOffering().getInstructionalOffering().getUniqueId().equals(offeringId)) {
              cd = x;
              break demands;
            }

        if (r.getRequest().getEnrollment() != null) { // save enrollment
          org.unitime.timetable.model.CourseRequest cr = null;
          CourseOffering co = null;
          if (co == null)
            for (CourseOffering x : io.getCourseOfferings())
              if (x.getUniqueId().equals(r.getRequest().getEnrollment().getCourseId())) co = x;
          for (Long sectionId : r.getRequest().getEnrollment().getSectionIds()) {
            Class_ clazz = Class_DAO.getInstance().get(sectionId, helper.getHibSession());
            if (cd != null && cr == null) {
              for (org.unitime.timetable.model.CourseRequest x : cd.getCourseRequests())
                if (x.getCourseOffering()
                    .getInstructionalOffering()
                    .getUniqueId()
                    .equals(offeringId)) {
                  cr = x;
                  break;
                }
            }
            if (co == null)
              co =
                  clazz
                      .getSchedulingSubpart()
                      .getInstrOfferingConfig()
                      .getInstructionalOffering()
                      .getControllingCourseOffering();
            StudentClassEnrollment enrl = new StudentClassEnrollment();
            enrl.setClazz(clazz);
            clazz.getStudentEnrollments().add(enrl);
            enrl.setCourseOffering(co);
            enrl.setCourseRequest(cr);
            StudentClassEnrollment old = enrollmentMap.get(sectionId);
            enrl.setTimestamp(old != null ? old.getTimestamp() : ts);
            enrl.setChangedBy(
                old != null
                    ? old.getChangedBy()
                    : helper.getUser() == null
                        ? StudentClassEnrollment.SystemChange.SYSTEM.toString()
                        : helper.getUser().getExternalId());
            enrl.setStudent(student);
            enrl.setApprovedBy(approvedBy);
            enrl.setApprovedDate(approvedDate);
            student.getClassEnrollments().add(enrl);
            /*
            if (cr != null) {
            	if (cr.getClassEnrollments() == null) cr.setClassEnrollments(new HashSet<StudentClassEnrollment>());
            	cr.getClassEnrollments().add(enrl);
            }
            */
            helper.debug("Adding " + enrl.getClazz().getClassLabel());
          }
        } else if (!r.getRequest().isAlternative()) { // wait-list
          if (cd != null && !cd.isWaitlist()) {
            cd.setWaitlist(true);
            helper.getHibSession().saveOrUpdate(cd);
          }
          if (!r.getRequest().isWaitlist()) r.setRequest(server.waitlist(r.getRequest(), true));
        }

        helper.getHibSession().save(student);

        EnrollStudent.updateSpace(
            server,
            r.getRequest().getEnrollment() == null
                ? null
                : SectioningRequest.convert(
                    r.getStudent(),
                    r.getRequest(),
                    server,
                    newOffering,
                    r.getRequest().getEnrollment()),
            r.getLastEnrollment() == null
                ? null
                : SectioningRequest.convert(
                    r.getOldStudent(),
                    r.getOldRequest(),
                    server,
                    oldOffering,
                    r.getLastEnrollment()),
            newOffering,
            oldOffering);
        server.persistExpectedSpaces(offeringId);

        server.execute(
            server
                .createAction(NotifyStudentAction.class)
                .forStudent(r.getRequest().getStudentId())
                .oldEnrollment(oldOffering, r.getCourseId(), r.getLastEnrollment()),
            helper.getUser());

        r.getAction()
            .setResult(
                e == null
                    ? OnlineSectioningLog.Action.ResultType.NULL
                    : OnlineSectioningLog.Action.ResultType.SUCCESS);
        r.getAction().setCpuTime(OnlineSectioningHelper.getCpuTime() - c0);
        r.getAction().setEndTime(System.currentTimeMillis());
      }
    }
  }
  public List<EnrollmentRequest> check(
      OnlineSectioningServer server, OnlineSectioningHelper helper) {
    XStudent student = server.getStudent(getStudentId());
    if (student == null) throw new SectioningException(MSG.exceptionBadStudentId());
    List<EnrollmentRequest> requests = new ArrayList<EnrollmentRequest>();
    Hashtable<Long, EnrollmentRequest> courseId2request = new Hashtable<Long, EnrollmentRequest>();
    Hashtable<Long, XOffering> courseId2offering = new Hashtable<Long, XOffering>();
    for (ClassAssignmentInterface.ClassAssignment ca : getAssignment()) {
      // Skip free times and dummy sections
      if (ca == null || ca.isFreeTime() || ca.getClassId() == null || ca.isDummy()) continue;

      XCourse course = server.getCourse(ca.getCourseId());
      if (course == null)
        throw new SectioningException(
            MSG.exceptionCourseDoesNotExist(MSG.courseName(ca.getSubject(), ca.getClassNumber())));
      XOffering offering = server.getOffering(course.getOfferingId());
      if (offering == null)
        throw new SectioningException(
            MSG.exceptionCourseDoesNotExist(MSG.courseName(ca.getSubject(), ca.getClassNumber())));

      // Check section limits
      XSection section = offering.getSection(ca.getClassId());
      if (section == null)
        throw new SectioningException(
            MSG.exceptionEnrollNotAvailable(
                MSG.clazz(ca.getSubject(), ca.getCourseNbr(), ca.getSubpart(), ca.getSection())));

      // Check cancelled flag
      if (section.isCancelled()) {
        if (server.getConfig().getPropertyBoolean("Enrollment.CanKeepCancelledClass", false)) {
          boolean contains = false;
          for (XRequest r : student.getRequests())
            if (r instanceof XCourseRequest) {
              XCourseRequest cr = (XCourseRequest) r;
              if (cr.getEnrollment() != null
                  && cr.getEnrollment().getSectionIds().contains(section.getSectionId())) {
                contains = true;
                break;
              }
            }
          if (!contains)
            throw new SectioningException(
                MSG.exceptionEnrollCancelled(
                    MSG.clazz(
                        ca.getSubject(), ca.getCourseNbr(), ca.getSubpart(), ca.getSection())));
        } else {
          throw new SectioningException(
              MSG.exceptionEnrollCancelled(
                  MSG.clazz(ca.getSubject(), ca.getCourseNbr(), ca.getSubpart(), ca.getSection())));
        }
      }

      EnrollmentRequest request = courseId2request.get(ca.getCourseId());
      if (request == null) {
        request = new EnrollmentRequest(course, new ArrayList<XSection>());
        courseId2request.put(ca.getCourseId(), request);
        requests.add(request);
      }
      request.getSections().add(section);
      courseId2offering.put(course.getCourseId(), offering);
    }

    // Check for NEW and CHANGE deadlines
    check:
    for (EnrollmentRequest request : requests) {
      XCourse course = request.getCourse();
      List<XSection> sections = request.getSections();

      for (XRequest r : student.getRequests()) {
        if (r instanceof XCourseRequest) {
          XEnrollment enrollment = ((XCourseRequest) r).getEnrollment();
          if (enrollment != null
              && enrollment.getCourseId().equals(course.getCourseId())) { // course change
            for (XSection s : sections)
              if (!enrollment.getSectionIds().contains(s.getSectionId())
                  && !server.checkDeadline(
                      course.getCourseId(), s.getTime(), OnlineSectioningServer.Deadline.CHANGE))
                throw new SectioningException(
                    MSG.exceptionEnrollDeadlineChange(
                        MSG.clazz(
                            course.getSubjectArea(),
                            course.getCourseNumber(),
                            s.getSubpartName(),
                            s.getName(course.getCourseId()))));
            continue check;
          }
        }
      }

      // new course
      for (XSection section : sections) {
        if (!server.checkDeadline(
            course.getOfferingId(), section.getTime(), OnlineSectioningServer.Deadline.NEW))
          throw new SectioningException(
              MSG.exceptionEnrollDeadlineNew(
                  MSG.clazz(
                      course.getSubjectArea(),
                      course.getCourseNumber(),
                      section.getSubpartName(),
                      section.getName(course.getCourseId()))));
      }
    }

    // Check for DROP deadlines
    for (XRequest r : student.getRequests()) {
      if (r instanceof XCourseRequest) {
        XEnrollment enrollment = ((XCourseRequest) r).getEnrollment();
        if (enrollment != null && !courseId2offering.containsKey(enrollment.getCourseId())) {
          XOffering offering = server.getOffering(enrollment.getOfferingId());
          if (offering != null)
            for (XSection section : offering.getSections(enrollment)) {
              if (!server.checkDeadline(
                  offering.getOfferingId(),
                  section.getTime(),
                  OnlineSectioningServer.Deadline.DROP))
                throw new SectioningException(
                    MSG.exceptionEnrollDeadlineDrop(enrollment.getCourseName()));
            }
        }
      }
    }

    Hashtable<Long, XConfig> courseId2config = new Hashtable<Long, XConfig>();
    for (EnrollmentRequest request : requests) {
      XCourse course = request.getCourse();
      XOffering offering = courseId2offering.get(course.getCourseId());
      XEnrollments enrollments = server.getEnrollments(course.getOfferingId());
      List<XSection> sections = request.getSections();
      XSubpart subpart = offering.getSubpart(sections.get(0).getSubpartId());
      XConfig config = offering.getConfig(subpart.getConfigId());
      courseId2config.put(course.getCourseId(), config);

      XReservation reservation = null;
      reservations:
      for (XReservation r : offering.getReservations()) {
        if (!r.isApplicable(student)) continue;
        if (r.getLimit() >= 0
            && r.getLimit() <= enrollments.countEnrollmentsForReservation(r.getReservationId())) {
          boolean contain = false;
          for (XEnrollment e : enrollments.getEnrollmentsForReservation(r.getReservationId()))
            if (e.getStudentId().equals(student.getStudentId())) {
              contain = true;
              break;
            }
          if (!contain) continue;
        }
        if (!r.getConfigsIds().isEmpty() && !r.getConfigsIds().contains(config.getConfigId()))
          continue;
        for (XSection section : sections)
          if (r.getSectionIds(section.getSubpartId()) != null
              && !r.getSectionIds(section.getSubpartId()).contains(section.getSectionId()))
            continue reservations;
        if (reservation == null || r.compareTo(reservation) < 0) reservation = r;
      }

      if (reservation == null || !reservation.canAssignOverLimit()) {
        for (XSection section : sections) {
          if (section.getLimit() >= 0
              && section.getLimit()
                  <= enrollments.countEnrollmentsForSection(section.getSectionId())) {
            boolean contain = false;
            for (XEnrollment e : enrollments.getEnrollmentsForSection(section.getSectionId()))
              if (e.getStudentId().equals(student.getStudentId())) {
                contain = true;
                break;
              }
            if (!contain)
              throw new SectioningException(
                  MSG.exceptionEnrollNotAvailable(
                      MSG.clazz(
                          course.getSubjectArea(),
                          course.getCourseNumber(),
                          section.getSubpartName(),
                          section.getName())));
          }
          if ((reservation == null
                  || !offering.getSectionReservations(section.getSectionId()).contains(reservation))
              && offering.getUnreservedSectionSpace(section.getSectionId(), enrollments) <= 0) {
            boolean contain = false;
            for (XEnrollment e : enrollments.getEnrollmentsForSection(section.getSectionId()))
              if (e.getStudentId().equals(student.getStudentId())) {
                contain = true;
                break;
              }
            if (!contain)
              throw new SectioningException(
                  MSG.exceptionEnrollNotAvailable(
                      MSG.clazz(
                          course.getSubjectArea(),
                          course.getCourseNumber(),
                          section.getSubpartName(),
                          section.getName())));
          }
        }

        if (config.getLimit() >= 0
            && config.getLimit() <= enrollments.countEnrollmentsForConfig(config.getConfigId())) {
          boolean contain = false;
          for (XEnrollment e : enrollments.getEnrollmentsForConfig(config.getConfigId()))
            if (e.getStudentId().equals(student.getStudentId())) {
              contain = true;
              break;
            }
          if (!contain)
            throw new SectioningException(
                MSG.exceptionEnrollNotAvailable(
                        MSG.courseName(course.getSubjectArea(), course.getCourseNumber()))
                    + " "
                    + config.getName());
        }
        if ((reservation == null
                || !offering.getConfigReservations(config.getConfigId()).contains(reservation))
            && offering.getUnreservedConfigSpace(config.getConfigId(), enrollments) <= 0) {
          boolean contain = false;
          for (XEnrollment e : enrollments.getEnrollmentsForConfig(config.getConfigId()))
            if (e.getStudentId().equals(student.getStudentId())) {
              contain = true;
              break;
            }
          if (!contain)
            throw new SectioningException(
                MSG.exceptionEnrollNotAvailable(
                        MSG.courseName(course.getSubjectArea(), course.getCourseNumber()))
                    + " "
                    + config.getName());
        }

        if (course.getLimit() >= 0
            && course.getLimit() <= enrollments.countEnrollmentsForCourse(course.getCourseId())) {
          boolean contain = false;
          for (XEnrollment e : enrollments.getEnrollmentsForCourse(course.getCourseId()))
            if (e.getStudentId().equals(student.getStudentId())) {
              contain = true;
              break;
            }
          if (!contain)
            throw new SectioningException(
                MSG.exceptionEnrollNotAvailable(
                    MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
        }
      }
    }

    for (EnrollmentRequest request : requests) {
      XCourse course = request.getCourse();
      XOffering offering = courseId2offering.get(course.getCourseId());
      List<XSection> sections = request.getSections();
      XSubpart subpart = offering.getSubpart(sections.get(0).getSubpartId());
      XConfig config = offering.getConfig(subpart.getConfigId());
      if (sections.size() < config.getSubparts().size()) {
        throw new SectioningException(
            MSG.exceptionEnrollmentIncomplete(
                MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
      } else if (sections.size() > config.getSubparts().size()) {
        throw new SectioningException(
            MSG.exceptionEnrollmentInvalid(
                MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
      }
      for (XSection s1 : sections) {
        for (XSection s2 : sections) {
          if (s1.getSectionId() < s2.getSectionId()
              && s1.isOverlapping(offering.getDistributions(), s2)) {
            throw new SectioningException(
                MSG.exceptionEnrollmentOverlapping(
                    MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
          }
          if (!s1.getSectionId().equals(s2.getSectionId())
              && s1.getSubpartId().equals(s2.getSubpartId())) {
            throw new SectioningException(
                MSG.exceptionEnrollmentInvalid(
                    MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
          }
        }
        if (!offering.getSubpart(s1.getSubpartId()).getConfigId().equals(config.getConfigId())) {
          throw new SectioningException(
              MSG.exceptionEnrollmentInvalid(
                  MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
        }
      }
      if (!offering.isAllowOverlap(student, config.getConfigId(), sections))
        for (EnrollmentRequest otherRequest : requests) {
          XOffering other = courseId2offering.get(otherRequest.getCourse().getCourseId());
          XConfig otherConfig = courseId2config.get(otherRequest.getCourse().getCourseId());
          if (!other.equals(offering)
              && !other.isAllowOverlap(
                  student, otherConfig.getConfigId(), otherRequest.getSections())) {
            List<XSection> assignment = otherRequest.getSections();
            for (XSection section : sections)
              if (section.isOverlapping(offering.getDistributions(), assignment))
                throw new SectioningException(
                    MSG.exceptionEnrollmentConflicting(
                        MSG.courseName(course.getSubjectArea(), course.getCourseNumber())));
          }
        }
    }

    return requests;
  }