org.jasig.schedassist.impl.relationship.CSVRelationshipDataSourceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.impl.relationship.CSVRelationshipDataSourceImpl.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;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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.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.SimpleJdbcTemplate;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import au.com.bytecode.opencsv.CSVReader;

/**
 * Simple {@link RelationshipDataSource} backed by a CSV file.
 * The CSV file should contain 3 columns, in this order:
 <pre>
 ownerIdentifier, visitorIdentifier, relationshipDescription
 </pre>
 * 
 * 
 * @author Nicholas Blair
 * @version $Id: CSVRelationshipDataSourceImpl.java 147 2011-06-10 15:03:02Z npblair $
 */
public class CSVRelationshipDataSourceImpl implements RelationshipDataSource, InitializingBean {

    private Log LOG = LogFactory.getLog(this.getClass());
    /**
     * System property to specify the path (on the classpath) to the Spring ApplicationContext for the {@link #main(String[])} method.
     */
    public static final String CONFIG = System.getProperty(
            CSVRelationshipDataSourceImpl.class.getPackage().getName() + ".CONFIG",
            "csv-relationship-dataSource.xml");

    private Resource csvResource;
    private Long resourceLastModified = -1L;
    private Date lastReloadTimestamp;
    private SimpleJdbcTemplate simpleJdbcTemplate;
    private JdbcTemplate jdbcTemplate;

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

    /**
     * @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);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        if (csvResource == 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.CSVRelationshipDataSourceImpl.CONFIG
     </pre>
     * The default value for this property is "csv-relationship-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 csvDataSource = (RelationshipDataSource) context.getBean("csvDataSource");

        csvDataSource.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 Scheduling Assistant 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)) {
            if (isResourceUpdated(csvResource)) {
                LOG.info("resource updated, reloading advisorList data");
                //List<StudentAdvisorAssignment> records = readResource(advisorListResource, currentTerm);
                List<CSVRelationship> records = new ArrayList<CSVRelationship>();
                try {
                    records = readCSVResource(csvResource);
                } catch (IOException e) {
                    LOG.error("caught IOException reading csv data source", e);
                    return;
                }

                if (records.isEmpty()) {
                    LOG.warn("resource returned empty set, skipping reloadData");
                    return;
                }

                LOG.info("deleting all existing records from csv_relationships table");
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                this.getJdbcTemplate().execute("delete from csv_relationships");
                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 csv_relationships (owner_id, visitor_id, rel_description) values (:ownerIdentifier, :visitorIdentifier, :relationshipDescription)",
                        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 = csvResource.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?
        }
        return result;
    }

    /**
     * Read the {@link Resource} argument as a CSV file and extract a {@link List}
     * of {@link CSVRelationship}s.
     * 
     * @see CSVReader
     * @param resource
     * @return a never null, but potentially empty list of {@link CSVRelationship}s.
     */
    protected List<CSVRelationship> readCSVResource(final Resource resource) throws IOException {
        Set<CSVRelationship> results = new HashSet<CSVRelationship>();
        CSVReader lineReader = new CSVReader(new InputStreamReader(resource.getInputStream()));
        String[] tokens = lineReader.readNext();
        while (null != tokens) {
            if (tokens.length == 3) {
                CSVRelationship relationship = new CSVRelationship();
                relationship.setOwnerIdentifier(tokens[0]);
                relationship.setVisitorIdentifier(tokens[1]);
                relationship.setRelationshipDescription(tokens[2]);
                results.add(relationship);
            } else {
                LOG.debug("skipping CSV line with tokens.length != 3, " + Arrays.toString(tokens));
            }

            tokens = lineReader.readNext();
        }

        return new ArrayList<CSVRelationship>(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());
    }

}