package edu.lmu.cs.headmaster.ws.dao;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import edu.lmu.cs.headmaster.ws.dao.util.QueryBuilder;
import edu.lmu.cs.headmaster.ws.domain.Event;
import edu.lmu.cs.headmaster.ws.domain.Student;
import edu.lmu.cs.headmaster.ws.types.Term;
/**
* Hibernate implementation of the student dao.
*/
public class StudentDaoHibernateImpl extends HibernateDaoSupport implements StudentDao {
// Search patterns
private static final Pattern ALL_DIGITS = Pattern.compile("\\d+");
private static final Pattern WORD_COMMA_WORD = Pattern.compile("(\\w+)\\s*,\\s*(\\w*)");
@Override
public Student getStudentById(Long id) {
return getHibernateTemplate().get(Student.class, id);
}
@Override
@SuppressWarnings("unchecked")
public List<Student> getStudents(String query, Boolean active, Boolean transferStudent,
Integer expectedGraduationYearFrom, Integer expectedGraduationYearTo,
Double minCumulativeGpa, Double maxCumulativeGpa,
Double minTermGpa, Double maxTermGpa,
Term term, Integer year, int skip, int max) {
return createStudentQuery(query, active, transferStudent,
expectedGraduationYearFrom, expectedGraduationYearTo,
minCumulativeGpa, maxCumulativeGpa, minTermGpa, maxTermGpa, term, year)
.build(getSession())
.setFirstResult(skip)
.setMaxResults(max)
.list();
}
@Override
public List<Event> getStudentAttendanceById(Long id) {
Student student = (Student)getSession()
.createQuery("from Student s left join fetch s.attendance where s.id = :id")
.setParameter("id", id)
.uniqueResult();
return (student == null) ? null : student.getAttendance();
}
@Override
@SuppressWarnings("unchecked")
public List<String> getMatchingCollegesOrSchools(String query, int skip, int max) {
return (List<String>)getSession()
.createQuery(
"select distinct m.collegeOrSchool from Major m where lower(m.collegeOrSchool) like lower(:query) order by m.collegeOrSchool"
).setString("query", "%" + query + "%")
.setFirstResult(skip)
.setMaxResults(max)
.list();
}
@Override
@SuppressWarnings("unchecked")
public List<String> getMatchingDegrees(String query, int skip, int max) {
return (List<String>)getSession()
.createQuery(
"select distinct m.degree from Major m where lower(m.degree) like lower(:query) order by m.degree"
).setString("query", "%" + query + "%")
.setFirstResult(skip)
.setMaxResults(max)
.list();
}
@Override
@SuppressWarnings("unchecked")
public List<String> getMatchingDisciplines(String query, int skip, int max) {
// HQL does not do unions, so we drop to SQL here.
final String wildcard = "%" + query + "%";
return (List<String>)getSession()
.createSQLQuery("select distinct discipline from major where lower(discipline) like lower(:query1) union select distinct minors as discipline from student_minors where lower(minors) like lower(:query2) order by discipline")
.setString("query1", wildcard)
.setString("query2", wildcard)
.setFirstResult(skip)
.setMaxResults(max)
.list();
}
@Override
public Student createStudent(Student student) {
getHibernateTemplate().save(student);
return student;
}
@Override
public void createOrUpdateStudent(Student student) {
// FIXME This implementation is not totally clean. In particular,
// saveOrUpdate does not delete orphaned majors (i.e., a record remains
// in the major table even if that major is not referenced by any
// student).
//
// Calling merge instead of saveOrUpdate *will* delete orphans, but it
// cannot handle pure reordering of majors. The OneToMany relationship
// forces major IDs to be unique within the join table, but that breaks
// updates where only the majors_order column changes.
//
// So, for now, we stay with saveOrUpdate. This will leave dangling
// majors over time, but for now that is better than database exceptions
// due to violation of unique constraints.
getHibernateTemplate().saveOrUpdate(student);
}
/**
* Returns a base HQL query object (no pagination) for the given parameters
* for students.
*/
private QueryBuilder createStudentQuery(String query, Boolean active, Boolean transferStudent,
Integer expectedGraduationYearFrom, Integer expectedGraduationYearTo,
Double minCumulativeGpa, Double maxCumulativeGpa,
Double minTermGpa, Double maxTermGpa, Term term, Integer year) {
// The desired return order is lastName, firstName.
QueryBuilder builder = new QueryBuilder(
"select s from Student s",
"order by lower(s.lastName), lower(s.firstName)"
);
if (minTermGpa != null || maxTermGpa != null) {
builder.append(" join s.record.grades as sgrade");
builder.clause("sgrade.term = :term", term);
builder.clause("sgrade.year = :year", year);
}
if (minTermGpa != null) {
builder.clause("sgrade.gpa >= :minTermGpa", minTermGpa);
}
if (maxTermGpa != null) {
builder.clause("sgrade.gpa <= :maxTermGpa", maxTermGpa);
}
if (minCumulativeGpa != null) {
builder.clause("s.record.cumulativeGpa >= :minCumGpa", minCumulativeGpa);
}
if (maxCumulativeGpa != null) {
builder.clause("s.record.cumulativeGpa <= :maxCumGpa", maxCumulativeGpa);
}
if (query != null) {
Matcher m = WORD_COMMA_WORD.matcher(query);
if (m.matches()) {
builder.clause("lower(s.lastName) like lower(:lastName)", m.group(1) + "%");
builder.clause("lower(s.firstName) like lower(:firstName)", m.group(2) + "%");
} else {
m = ALL_DIGITS.matcher(query);
if (m.matches()) {
builder.clause("s.schoolId = :schoolId", query);
} else {
builder.clause("lower(s.lastName) like lower(:lastName)", query + "%");
}
}
}
if (active != null) {
builder.clause("s.active = :active", active);
}
if (expectedGraduationYearFrom != null) {
builder.clause("s.expectedGraduationYear >= :gradYearFrom", expectedGraduationYearFrom);
}
if (expectedGraduationYearTo != null) {
builder.clause("s.expectedGraduationYear <= :gradYearTo", expectedGraduationYearTo);
}
if (transferStudent != null) {
builder.clause("s.transferStudent = :transferStudent", transferStudent);
}
// All done.
return builder;
}
}