org.jasig.schedassist.impl.relationship.advising.AdvisorListRelationshipDataSourceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.impl.relationship.advising.AdvisorListRelationshipDataSourceImpl.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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.jasig.schedassist.impl.relationship.advising;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.impl.relationship.RelationshipDataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Spring {@link SimpleJdbcDaoSupport} backed mechanism for
 * loading and refreshing the AdvisorList data.
 * 
 * The AdvisorList is a semi-colon delimited text file that is automatically sent
 * every 6 hours to a location on the file system.
 * 
 * It's format is the following:
 <pre>
 student1@wisc.edu;One,Test Student;0000000001;9010000001;Graduate;L&S;College of Letters and Science;4.000;1092;Fall 2008-2009;87.000;PHD 922L&S;;NWD;G922L;Sociology - LS;PHD 922L&S;Sociology PHD-L&S;Sociology;advisor1@wisc.edu;One,Advisor;1000000001;9020000001;ADVR;Academic
 </pre>
 *
 * For the purposes of this application we are only interested in the following fields:
 * <ul>
 <li>Student ISIS Emplid (field 3)</li>
 <li>Term ID number (field 9)</li>
 <li>Term description (field 10)</li>
 <li>Academic Program description (field 16)</li>
 <li>Advisor ISIS Emplid (field 22)</li>
 <li>Advisor type (field 25)</li>
 <li>Committee Role (field 26)</li>
 </ul>
 *
 * The {@link #parseLine(String)} method of this class creates a {@link StudentAdvisorAssignment}
 * from such a line. The Academic Program description is stored as the advisorRelationshipDescription
 * on the returned object, as a student may have multiple advisors, and this field provides
 * the distinction.
 * 
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: AdvisorListDataSourceImpl.java 2905 2010-11-18 21:05:33Z npblair $
 */
public class AdvisorListRelationshipDataSourceImpl implements RelationshipDataSource, InitializingBean {

    protected final Log LOG = LogFactory.getLog(this.getClass());
    public static final String CONFIG = System.getProperty(
            AdvisorListRelationshipDataSourceImpl.class.getPackage().getName() + ".CONFIG",
            "advisorlist-dataSource.xml");

    private Resource advisorListResource;
    private Long resourceLastModified = -1L;
    private Date lastReloadTimestamp;
    private SimpleJdbcTemplate simpleJdbcTemplate;
    private JdbcTemplate jdbcTemplate;
    private int advisorEmplidFieldNumber = 21;
    private int relationshipDescriptionFieldNumber = 15;
    private int studentEmplidFieldNumber = 2;
    private int termNumberFieldNumber = 8;
    private int termDescriptionFieldNumber = 9;
    private int advisorTypeFieldNumber = 24;
    private int committeeRoleFieldNumber = 25;

    /**
     * @param advisorListResource the advisorListResource to set
     */
    public void setAdvisorListResource(Resource advisorListResource) {
        this.advisorListResource = advisorListResource;
    }

    /**
     * @return the simpleJdbcTemplate
     */
    protected SimpleJdbcTemplate getSimpleJdbcTemplate() {
        return simpleJdbcTemplate;
    }

    /**
     * @return the jdbcTemplate
     */
    protected JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    /**
     * @return the advisorEmplidFieldNumber
     */
    public int getAdvisorEmplidFieldNumber() {
        return advisorEmplidFieldNumber;
    }

    /**
     * @param advisorEmplidFieldNumber the advisorEmplidFieldNumber to set
     */
    public void setAdvisorEmplidFieldNumber(int advisorEmplidFieldNumber) {
        this.advisorEmplidFieldNumber = advisorEmplidFieldNumber;
    }

    /**
     * @return the relationshipDescriptionFieldNumber
     */
    public int getRelationshipDescriptionFieldNumber() {
        return relationshipDescriptionFieldNumber;
    }

    /**
     * @param relationshipDescriptionFieldNumber the relationshipDescriptionFieldNumber to set
     */
    public void setRelationshipDescriptionFieldNumber(int relationshipDescriptionFieldNumber) {
        this.relationshipDescriptionFieldNumber = relationshipDescriptionFieldNumber;
    }

    /**
     * @return the studentEmplidFieldNumber
     */
    public int getStudentEmplidFieldNumber() {
        return studentEmplidFieldNumber;
    }

    /**
     * @param studentEmplidFieldNumber the studentEmplidFieldNumber to set
     */
    public void setStudentEmplidFieldNumber(int studentEmplidFieldNumber) {
        this.studentEmplidFieldNumber = studentEmplidFieldNumber;
    }

    /**
     * @return the termNumberFieldNumber
     */
    public int getTermNumberFieldNumber() {
        return termNumberFieldNumber;
    }

    /**
     * @param termNumberFieldNumber the termNumberFieldNumber to set
     */
    public void setTermNumberFieldNumber(int termNumberFieldNumber) {
        this.termNumberFieldNumber = termNumberFieldNumber;
    }

    /**
     * @return the termDescriptionFieldNumber
     */
    public int getTermDescriptionFieldNumber() {
        return termDescriptionFieldNumber;
    }

    /**
     * @param termDescriptionFieldNumber the termDescriptionFieldNumber to set
     */
    public void setTermDescriptionFieldNumber(int termDescriptionFieldNumber) {
        this.termDescriptionFieldNumber = termDescriptionFieldNumber;
    }

    /**
     * @return the advisorTypeFieldNumber
     */
    public int getAdvisorTypeFieldNumber() {
        return advisorTypeFieldNumber;
    }

    /**
     * @param advisorTypeFieldNumber the advisorTypeFieldNumber to set
     */
    public void setAdvisorTypeFieldNumber(int advisorTypeFieldNumber) {
        this.advisorTypeFieldNumber = advisorTypeFieldNumber;
    }

    /**
     * @return the committeeRoleFieldNumber
     */
    public int getCommitteeRoleFieldNumber() {
        return committeeRoleFieldNumber;
    }

    /**
     * @param committeeRoleFieldNumber the committeeRoleFieldNumber to set
     */
    public void setCommitteeRoleFieldNumber(int committeeRoleFieldNumber) {
        this.committeeRoleFieldNumber = committeeRoleFieldNumber;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        if (advisorListResource == null) {
            throw new IllegalStateException("advisorListResource is required");
        }

        if (simpleJdbcTemplate == null) {
            throw new IllegalStateException("dataSource is required");
        }
    }

    /**
     * Main method to allow command line invocation of the {@link #reloadData(Resource)} method.
     * This method attempts to load a {@link ClassPathXmlApplicationContext} from the 
     * location specified in the System property:
     <pre>
     -Dorg.jasig.schedassist.impl.relationship.advising.AdvisorListRelationshipDataSourceImpl.CONFIG
     </pre>
     * The default value for this property is "advisorlist-dataSource.xml" (in the default package).
     * This Spring applicationContext must contain a fully configured {@link RelationshipDataSource}
     * bean.
     * 
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG);

        RelationshipDataSource advisorListDataSource = (RelationshipDataSource) context
                .getBean("advisorListDataSource");

        advisorListDataSource.reloadData();
    }

    /**
     * This method deletes all existing rows from the isis_records table, then invokes
     * {@link #batchLoadData(Resource)} to refresh it.
     * 
     * This method is marked with Spring's {@link Transactional} annotation, and if
     * the available application is running should only be executed when transactional 
     * support is available.
     * 
     * @see Transactional
     * @param resource
     * @throws IOException
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public synchronized void reloadData() {
        final String propertyValue = System.getProperty("org.jasig.schedassist.runScheduledTasks", "true");
        if (Boolean.parseBoolean(propertyValue)) {
            String currentTerm = TermCalculator.getCurrentTerm();
            if (isResourceUpdated(advisorListResource)) {
                LOG.info("resource updated, reloading advisorList data");
                List<StudentAdvisorAssignment> records = readResource(advisorListResource, currentTerm);

                LOG.info("deleting all existing records from advisorlist table");
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                this.getJdbcTemplate().execute("delete from advisorlist");
                long deleteTime = stopWatch.getTime();
                LOG.info("finished deleting existing (" + deleteTime + " msec), starting batch insert");
                stopWatch.reset();
                stopWatch.start();
                SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(records.toArray());
                this.getSimpleJdbcTemplate().batchUpdate(
                        "insert into advisorlist (advisor_emplid, advisor_relationship, student_emplid, term_description, term_number, advisor_type, committee_role) values (:advisorEmplid, :advisorRelationshipDescription, :studentEmplid, :termDescription, :termNumber, :advisorType, :committeeRole)",
                        batch);
                long insertTime = stopWatch.getTime();
                stopWatch.stop();
                LOG.info("batch insert complete (" + insertTime + " msec)");
                LOG.info("reloadData complete (total time: " + (insertTime + deleteTime) + " msec)");
                this.lastReloadTimestamp = new Date();
                try {
                    this.resourceLastModified = advisorListResource.lastModified();
                } catch (IOException e) {
                    LOG.debug("ignoring IOException from accessing Resource.lastModified()");
                }
            } else {
                LOG.info("resource not modified since last reload, skipping");
            }
        } else {
            LOG.debug("ignoring reloadData as 'org.jasig.schedassist.runScheduledTasks' set to false");
        }
    }

    /**
     * 
     * @param resource
     * @return
     */
    protected boolean isResourceUpdated(final Resource resource) {
        boolean result = true;
        try {
            result = (this.resourceLastModified == -1L) || (resource.lastModified() > this.resourceLastModified);
        } catch (IOException e) {
            // this exception will occur if the Resource is not representable as a File
            // in this case - always return true?
            throw new IllegalStateException(
                    "caught IOException from Resource#lastModified(), is " + resource + " a folder?", e);
        }
        return result;
    }

    /**
     * Converts a semi-colon delimited line of text into a {@link StudentAdvisorAssignment}.
     * 
     * Uses {@link String#split(String)}.
     * Default mapping:
     <ol>
     <li>{@link StudentAdvisorAssignment#setAdvisorEmplid(String)} - field #22 (index 21)</li>
     <li>{@link StudentAdvisorAssignment#setAdvisorRelationshipDescription(String)} - field #16 (index 15)</li>
     <li>{@link StudentAdvisorAssignment#setStudentEmplid(String)} - field #3 (index 2)</li>
     <li>{@link StudentAdvisorAssignment#setTermDescription(String)} - field #11 (index 10)</li>
     <li>{@link StudentAdvisorAssignment#setTermNumber(String)} - field #10 (index 9)</li>
     <li>{@link StudentAdvisorAssignment#setAdvisorType(String)} - field #25 (index 24)</li>
     <li>{@link StudentAdvisorAssignment#setCommitteeRole(String)} - field #26 (index 25)</li>
     </ol>
     *
     * @param line
     * @return
     */
    protected StudentAdvisorAssignment parseLine(final String line) {
        LOG.debug("parseLine: " + line);
        String[] tokens = line.split(";");

        if (tokens.length != 25 && tokens.length != 26) {
            LOG.debug("returning null for malformed line (tokens length " + tokens.length + "): " + line);
            return null;
        }
        StudentAdvisorAssignment record = new StudentAdvisorAssignment();
        // IDs must be lower-cased safely
        record.setAdvisorEmplid(nullSafeGetLowerCase(tokens, getAdvisorEmplidFieldNumber()));
        record.setStudentEmplid(nullSafeGetLowerCase(tokens, getStudentEmplidFieldNumber()));

        record.setAdvisorRelationshipDescription(tokens[getRelationshipDescriptionFieldNumber()]);
        record.setTermNumber(tokens[getTermNumberFieldNumber()]);
        record.setTermDescription(tokens[getTermDescriptionFieldNumber()]);
        record.setAdvisorType(tokens[getAdvisorTypeFieldNumber()]);
        if (tokens.length == 26) {
            record.setCommitteeRole(tokens[getCommitteeRoleFieldNumber()]);
        }
        LOG.debug("parseLine result: " + record);
        return record;
    }

    /**
     * 
     * @param tokens
     * @param field
     * @return the value in the array, lower cased if not null
     */
    private String nullSafeGetLowerCase(final String[] tokens, final int field) {
        String value = tokens[field];
        if (value == null) {
            return null;
        }

        return value.toLowerCase();
    }

    /**
     * Read each line from the supplied {@link Resource}, invokes
     * {@link #parseLine(String)}, and return a {@link List} of the 
     * corresponding {@link StudentAdvisorAssignment}s.
     * 
     * If the {@link StudentAdvisorAssignment#getTermNumber()} returned from {@link #parseLine(String)} is not 
     * equal to or greater than the term argument, it is not added to the returned {@link List}.
     * 
     * @see TermCalculator#termGreaterThanOrEquals(String, String)
     * @param resource
     * @param term
     * @return
     * @throws IOException
     */
    protected List<StudentAdvisorAssignment> readResource(final Resource resource, final String term) {
        List<StudentAdvisorAssignment> results = new ArrayList<StudentAdvisorAssignment>();
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));

            String currentLine = reader.readLine();
            while (null != currentLine) {
                StudentAdvisorAssignment record = parseLine(currentLine);
                if (null != record) {
                    if (TermCalculator.termGreaterThanOrEquals(record.getTermNumber(), term)) {
                        results.add(record);
                    }
                }
                currentLine = reader.readLine();
            }
            reader.close();
        } catch (IOException e) {
            throw new AdvisorListDataException("IOException in readResource", e);
        }
        return results;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.relationship.RelationshipDataSource#getLastReloadTimestamp()
     */
    @Override
    public Date getLastReloadTimestamp() {
        return null == this.lastReloadTimestamp ? null : new Date(this.lastReloadTimestamp.getTime());
    }

}