org.unitime.timetable.onlinesectioning.custom.purdue.PurdueBatchSolverValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.onlinesectioning.custom.purdue.PurdueBatchSolverValidator.java

Source

/*
    
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/
package org.unitime.timetable.onlinesectioning.custom.purdue;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cpsolver.ifs.solver.Solver;
import org.cpsolver.ifs.util.Progress;
import org.cpsolver.ifs.util.CSVFile.CSVField;
import org.cpsolver.studentsct.StudentSectioningSaver;
import org.cpsolver.studentsct.model.CourseRequest;
import org.cpsolver.studentsct.model.Enrollment;
import org.cpsolver.studentsct.model.Request;
import org.cpsolver.studentsct.model.Section;
import org.cpsolver.studentsct.model.Student;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Transaction;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.restlet.Client;
import org.restlet.data.Protocol;
import org.restlet.resource.ClientResource;
import org.unitime.timetable.ApplicationProperties;
import org.unitime.timetable.defaults.ApplicationProperty;
import org.unitime.timetable.gwt.shared.SectioningException;
import org.unitime.timetable.model.Class_;
import org.unitime.timetable.model.CourseOffering;
import org.unitime.timetable.model.OfferingConsentType;
import org.unitime.timetable.model.Session;
import org.unitime.timetable.model.dao.SessionDAO;
import org.unitime.timetable.onlinesectioning.AcademicSessionInfo;
import org.unitime.timetable.onlinesectioning.OnlineSectioningHelper;
import org.unitime.timetable.onlinesectioning.OnlineSectioningLog;
import org.unitime.timetable.onlinesectioning.OnlineSectioningLogger;
import org.unitime.timetable.onlinesectioning.OnlineSectioningLog.Entity;
import org.unitime.timetable.onlinesectioning.custom.ExternalTermProvider;
import org.unitime.timetable.onlinesectioning.custom.purdue.SpecialRegistrationInterface.ApiMode;
import org.unitime.timetable.onlinesectioning.custom.purdue.SpecialRegistrationInterface.CheckRestrictionsRequest;
import org.unitime.timetable.onlinesectioning.custom.purdue.SpecialRegistrationInterface.CheckRestrictionsResponse;
import org.unitime.timetable.onlinesectioning.custom.purdue.SpecialRegistrationInterface.Problem;
import org.unitime.timetable.onlinesectioning.custom.purdue.SpecialRegistrationInterface.ResponseStatus;
import org.unitime.timetable.solver.studentsct.InMemoryReport;
import org.unitime.timetable.solver.studentsct.StudentSolver;
import org.unitime.timetable.util.Formats;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
 * @author Tomas Muller
 */
public class PurdueBatchSolverValidator extends StudentSectioningSaver {
    private static Log sLog = LogFactory.getLog(PurdueBatchSolverValidator.class);
    private String iInitiative = null;
    private String iTerm = null;
    private String iYear = null;
    private String iOwnerId = null;
    private Progress iProgress = null;

    private Client iClient;
    private ExternalTermProvider iExternalTermProvider;
    private AcademicSessionInfo iSession;
    private InMemoryReport iCSV;
    private int iNrThreads = 1;
    private boolean iCanContinue = true;

    private Hashtable<Long, CourseOffering> iCourses = null;
    private Hashtable<Long, Class_> iClasses = null;

    public PurdueBatchSolverValidator(Solver solver) {
        super(solver);
        iInitiative = solver.getProperties().getProperty("Data.Initiative");
        iYear = solver.getProperties().getProperty("Data.Year");
        iTerm = solver.getProperties().getProperty("Data.Term");
        iOwnerId = solver.getProperties().getProperty("General.OwnerPuid");
        iProgress = Progress.getInstance(getModel());
        try {
            String clazz = ApplicationProperty.CustomizationExternalTerm.value();
            if (clazz == null || clazz.isEmpty())
                iExternalTermProvider = new BannerTermProvider();
            else
                iExternalTermProvider = (ExternalTermProvider) Class.forName(clazz).getConstructor().newInstance();
        } catch (Exception e) {
            sLog.error("Failed to create external term provider, using the default one instead.", e);
            iExternalTermProvider = new BannerTermProvider();
        }
        iNrThreads = solver.getProperties().getPropertyInt("Save.XE.NrSaveThreads", 10);
        iCSV = new InMemoryReport("VALIDATION", "Last Validation Results ("
                + Formats.getDateFormat(Formats.Pattern.DATE_TIME_STAMP_SHORT).format(new Date()) + ")");
        ((StudentSolver) solver).setReport(iCSV);
    }

    @Override
    public void save() throws Exception {
        iProgress.setStatus("Validating solution ...");
        List<Protocol> protocols = new ArrayList<Protocol>();
        protocols.add(Protocol.HTTP);
        protocols.add(Protocol.HTTPS);
        iClient = new Client(protocols);
        iCSV.setHeader(new CSVField[] { new CSVField("PUID"), new CSVField("Name"), new CSVField("Course"),
                new CSVField("CRN"), new CSVField("Code"), new CSVField("Message") });
        org.hibernate.Session hibSession = null;
        Transaction tx = null;
        try {
            hibSession = SessionDAO.getInstance().getSession();
            hibSession.setCacheMode(CacheMode.IGNORE);
            hibSession.setFlushMode(FlushMode.MANUAL);

            tx = hibSession.beginTransaction();

            Session session = Session.getSessionUsingInitiativeYearTerm(iInitiative, iYear, iTerm);
            if (session == null)
                throw new Exception("Session " + iInitiative + " " + iTerm + iYear + " not found!");
            ApplicationProperties.setSessionId(session.getUniqueId());
            iSession = new AcademicSessionInfo(session);

            validate(session, hibSession);

            hibSession.flush();

            tx.commit();
            tx = null;
        } catch (Exception e) {
            iProgress.fatal("Unable to validate, reason: " + e.getMessage(), e);
            sLog.error(e.getMessage(), e);
            if (tx != null)
                tx.rollback();
        } finally {
            if (hibSession != null && hibSession.isOpen())
                hibSession.close();
            try {
                iClient.stop();
            } catch (Exception e) {
                sLog.error(e.getMessage(), e);
            }
        }
    }

    public void validate(Session session, org.hibernate.Session hibSession) {
        setPhase("Loading classes...", 1);
        iClasses = new Hashtable<Long, Class_>();
        for (Class_ clazz : (List<Class_>) hibSession.createQuery("select distinct c from Class_ c where "
                + "c.schedulingSubpart.instrOfferingConfig.instructionalOffering.session.uniqueId = :sessionId")
                .setLong("sessionId", session.getUniqueId()).list()) {
            iClasses.put(clazz.getUniqueId(), clazz);
        }
        incProgress();

        iCourses = new Hashtable<Long, CourseOffering>();
        setPhase("Loading courses...", 1);
        for (CourseOffering course : (List<CourseOffering>) hibSession
                .createQuery(
                        "select distinct c from CourseOffering c where c.subjectArea.session.uniqueId = :sessionId")
                .setLong("sessionId", session.getUniqueId()).list()) {
            iCourses.put(course.getUniqueId(), course);
        }
        incProgress();

        setPhase("Validating students...", getModel().getStudents().size());
        if (iNrThreads <= 1) {
            for (Student student : getModel().getStudents()) {
                incProgress();
                if (student.isDummy())
                    continue;
                validateStudent(student);
            }
        } else {
            List<Worker> workers = new ArrayList<Worker>();
            Iterator<Student> students = getModel().getStudents().iterator();
            for (int i = 0; i < iNrThreads; i++)
                workers.add(new Worker(i, students));
            for (Worker worker : workers)
                worker.start();
            for (Worker worker : workers) {
                try {
                    worker.join();
                } catch (InterruptedException e) {
                    iCanContinue = false;
                    try {
                        worker.join();
                    } catch (InterruptedException x) {
                    }
                }
            }
            if (!iCanContinue)
                throw new RuntimeException("The validate was interrupted.");
        }
    }

    protected void validateStudent(Student student) {
        long c0 = OnlineSectioningHelper.getCpuTime();
        OnlineSectioningLog.Action.Builder action = OnlineSectioningLog.Action.newBuilder();
        action.setOperation("batch-validate");
        action.setSession(OnlineSectioningLog.Entity.newBuilder().setUniqueId(iSession.getUniqueId())
                .setName(iSession.toCompactString()));
        action.setStartTime(System.currentTimeMillis());
        action.setUser(getUser());
        action.setStudent(OnlineSectioningLog.Entity.newBuilder().setUniqueId(student.getId())
                .setExternalId(student.getExternalId()).setName(student.getName())
                .setType(OnlineSectioningLog.Entity.EntityType.STUDENT));
        OnlineSectioningLog.Enrollment.Builder requested = OnlineSectioningLog.Enrollment.newBuilder();
        requested.setType(OnlineSectioningLog.Enrollment.EnrollmentType.REQUESTED);
        for (Request request : student.getRequests()) {
            action.addRequest(OnlineSectioningHelper.toProto(request));
            if (request instanceof CourseRequest) {
                Enrollment e = getAssignment().getValue(request);
                if (e != null)
                    for (Section section : e.getSections())
                        requested.addSection(OnlineSectioningHelper.toProto(section, e));
            }
        }
        action.addEnrollment(requested);
        List<CSVField[]> csv = new ArrayList<CSVField[]>();
        try {
            validate(student, action, csv);
        } finally {
            action.setEndTime(System.currentTimeMillis()).setCpuTime(OnlineSectioningHelper.getCpuTime() - c0);
        }
        StringBuffer table = new StringBuffer();
        synchronized (iCSV) {
            for (CSVField[] line : csv) {
                if (table.length() > 0)
                    table.append("\n");
                table.append(iCSV.addLine(line));
            }
            action.addOptionBuilder().setKey("table").setValue(table.toString());
        }
        OnlineSectioningLogger.getInstance().record(OnlineSectioningLog.Log.newBuilder().addAction(action).build());
    }

    protected Gson getGson() {
        GsonBuilder builder = new GsonBuilder().registerTypeAdapter(DateTime.class, new JsonSerializer<DateTime>() {
            @Override
            public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
                return new JsonPrimitive(src.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"));
            }
        }).registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
            @Override
            public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                    throws JsonParseException {
                return new DateTime(json.getAsJsonPrimitive().getAsString(), DateTimeZone.UTC);
            }
        });
        return builder.create();
    }

    protected String getSpecialRegistrationApiSite() {
        return ApplicationProperties.getProperty("purdue.specreg.site");
    }

    protected String getSpecialRegistrationApiValidationSite() {
        return ApplicationProperties.getProperty("purdue.specreg.site.validation",
                getSpecialRegistrationApiSite() + "/checkRestrictions");
    }

    protected String getSpecialRegistrationApiKey() {
        return ApplicationProperties.getProperty("purdue.specreg.apiKey");
    }

    protected ApiMode getSpecialRegistrationApiMode() {
        return ApiMode.valueOf(ApplicationProperties.getProperty("purdue.specreg.mode.batch", "PREREG"));
    }

    protected void validate(Student student, OnlineSectioningLog.Action.Builder action, List<CSVField[]> csv) {
        iProgress.info("[" + student.getExternalId() + "] " + student.getName());

        String term = iExternalTermProvider.getExternalTerm(iSession);
        String campus = iExternalTermProvider.getExternalCampus(iSession);
        String puid = getBannerId(student);

        CheckRestrictionsRequest req = new CheckRestrictionsRequest();
        req.studentId = puid;
        req.term = term;
        req.campus = campus;
        req.mode = getSpecialRegistrationApiMode();

        for (Request request : student.getRequests()) {
            Enrollment enrollment = getAssignment().getValue(request);
            if (enrollment != null && enrollment.isCourseRequest()) {
                CourseOffering course = iCourses.get(enrollment.getCourse().getId());
                if (course != null) {
                    for (Section section : enrollment.getSections()) {
                        Class_ clazz = iClasses.get(section.getId());
                        if (clazz != null)
                            SpecialRegistrationHelper.addCrn(req, clazz.getExternalId(course));
                    }
                }
            }
        }
        if (req.changes == null) {
            action.addOptionBuilder().setKey("validation_request").setValue(getGson().toJson(req));
            action.setResult(OnlineSectioningLog.Action.ResultType.NULL);
            return;
        }

        CheckRestrictionsResponse resp = null;
        ClientResource resource = null;
        try {
            resource = new ClientResource(getSpecialRegistrationApiValidationSite());
            resource.setNext(iClient);
            resource.addQueryParameter("apiKey", getSpecialRegistrationApiKey());

            Gson gson = getGson();
            action.addOptionBuilder().setKey("validation_request").setValue(gson.toJson(req));
            long t1 = System.currentTimeMillis();

            resource.post(new GsonRepresentation<CheckRestrictionsRequest>(req));

            action.setApiPostTime(System.currentTimeMillis() - t1);

            resp = (CheckRestrictionsResponse) new GsonRepresentation<CheckRestrictionsResponse>(
                    resource.getResponseEntity(), CheckRestrictionsResponse.class).getObject();
            action.addOptionBuilder().setKey("validation_response").setValue(gson.toJson(resp));

            if (ResponseStatus.success != resp.status)
                throw new SectioningException(resp.message == null || resp.message.isEmpty()
                        ? "Failed to check student eligibility (" + resp.status + ")."
                        : resp.message);
        } catch (Exception e) {
            action.setApiException(e.getMessage());
            action.setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
            action.addMessage(
                    OnlineSectioningLog.Message.newBuilder().setLevel(OnlineSectioningLog.Message.Level.FATAL)
                            .setText(e.getClass().getSimpleName() + ": " + e.getMessage()));
            iProgress.error("[" + student.getExternalId() + "] Failed to validate: " + e.getMessage(), e);
            csv.add(new CSVField[] { new CSVField(puid), new CSVField(student.getName()), new CSVField(""),
                    new CSVField(""), new CSVField("FAIL"), new CSVField(e.getMessage()) });
            return;
        } finally {
            if (resource != null) {
                if (resource.getResponse() != null)
                    resource.getResponse().release();
                resource.release();
            }
        }

        action.setResult(OnlineSectioningLog.Action.ResultType.TRUE);
        if (resp != null && resp.outJson != null && resp.outJson.problems != null) {
            for (Problem p : resp.outJson.problems) {
                if (p.crn == null) {
                    iProgress.warn("[" + student.getExternalId() + "] " + p.message);
                    csv.add(new CSVField[] { new CSVField(puid), new CSVField(student.getName()), new CSVField(""),
                            new CSVField(""), new CSVField(p.code), new CSVField(p.message) });
                    action.addMessage(OnlineSectioningLog.Message.newBuilder()
                            .setLevel(OnlineSectioningLog.Message.Level.WARN).setText(p.message));
                } else {
                    csv.add(new CSVField[] { new CSVField(puid), new CSVField(student.getName()),
                            new CSVField(getCourseNameForCrn(student, p.crn)), new CSVField(p.crn),
                            new CSVField(p.code), new CSVField(p.message) });
                    iProgress.warn("[" + student.getExternalId() + "] " + p.crn + ": " + p.message);
                    action.addMessage(OnlineSectioningLog.Message.newBuilder()
                            .setLevel(OnlineSectioningLog.Message.Level.WARN).setText(p.crn + ": " + p.message));
                }
                action.setResult(OnlineSectioningLog.Action.ResultType.FALSE);
            }
        }
    }

    protected String getBannerId(Student student) {
        String id = student.getExternalId();
        while (id.length() < 9)
            id = "0" + id;
        return id;
    }

    public String getCourseNameForCrn(Student student, String crn) {
        for (Request request : student.getRequests()) {
            Enrollment enrollment = getAssignment().getValue(request);
            if (enrollment != null && enrollment.isCourseRequest()) {
                CourseOffering course = iCourses.get(enrollment.getCourse().getId());
                for (Section section : enrollment.getSections()) {
                    Class_ clazz = iClasses.get(section.getId());
                    if (clazz != null && course != null && crn.equals(clazz.getExternalId(course)))
                        return course.getCourseName();
                }
            }
        }
        return null;
    }

    public OfferingConsentType getConsent(Student student, String crn) {
        for (Request request : student.getRequests()) {
            Enrollment enrollment = getAssignment().getValue(request);
            if (enrollment != null && enrollment.isCourseRequest()) {
                CourseOffering course = iCourses.get(enrollment.getCourse().getId());
                for (Section section : enrollment.getSections()) {
                    Class_ clazz = iClasses.get(section.getId());
                    if (clazz != null && course != null && crn.equals(clazz.getExternalId(course)))
                        course.getConsentType();
                }
            }
        }
        return null;
    }

    protected Entity getUser() {
        return Entity.newBuilder().setExternalId(iOwnerId).setType(Entity.EntityType.MANAGER).build();
    }

    protected void checkTermination() {
        if (getTerminationCondition() != null && !getTerminationCondition().canContinue(getSolution()))
            throw new RuntimeException("The validate was interrupted.");
    }

    protected void setPhase(String phase, long progressMax) {
        checkTermination();
        iProgress.setPhase(phase, progressMax);
    }

    protected void incProgress() {
        checkTermination();
        iProgress.incProgress();
    }

    protected class Worker extends Thread {
        private Iterator<Student> iStudents;

        public Worker(int index, Iterator<Student> students) {
            setName("XEValidator-" + (1 + index));
            iStudents = students;
        }

        @Override
        public void run() {
            iProgress.debug(getName() + " has started.");
            while (true) {
                Student student = null;
                synchronized (iStudents) {
                    if (!iCanContinue) {
                        iProgress.debug(getName() + " has stopped.");
                        return;
                    }
                    if (!iStudents.hasNext())
                        break;
                    student = iStudents.next();
                    iProgress.incProgress();
                }
                if (!student.isDummy())
                    validateStudent(student);
            }
            iProgress.debug(getName() + " has finished.");
        }
    }
}