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