/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.spatial.testing;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.codec.Wkt;
import org.geolatte.geom.codec.WktDecodeException;
import org.geolatte.geom.codec.WktDecoder;
import org.jboss.logging.Logger;
import org.hibernate.spatial.HSMessageLogger;
/**
* <p>Unit testsuite-suite support class.</p>
*
* @author Karel Maesen, Geovise BVBA.
*/
public class DataSourceUtils {
private static HSMessageLogger LOG = Logger.getMessageLogger(
HSMessageLogger.class,
DataSourceUtils.class.getName()
);
private final SQLExpressionTemplate sqlExpressionTemplate;
private final String jdbcDriver;
private final String jdbcUrl;
private final String jdbcUser;
private final String jdbcPass;
private DataSource dataSource;
/**
* Constructor for the DataSourceUtils object.
* <p/>
* <p>The following entities are required in the property file:
* <il>
* <li> jdbcUrl: jdbc connection URL</li>
* <li> dbUsername: username for the database</li>
* <li> dbPassword: password for the database</li>
* <li> driver: fully-qualified class name for the JDBC Driver</li>
* </il>
*
* @param jdbcDriver
* @param jdbcUrl
* @param jdbcUser
* @param jdbcPass
* @param sqlExpressionTemplate SQLExpressionTemplate object that generates SQL statements for this database
*/
public DataSourceUtils(
String jdbcDriver,
String jdbcUrl,
String jdbcUser,
String jdbcPass,
SQLExpressionTemplate sqlExpressionTemplate) {
this.jdbcDriver = jdbcDriver;
this.jdbcUrl = jdbcUrl;
this.jdbcUser = jdbcUser;
this.jdbcPass = jdbcPass;
this.sqlExpressionTemplate = sqlExpressionTemplate;
createBasicDataSource();
}
/**
* Constructor using a properties file
*
* @param propertyFile
* @param template
*/
public DataSourceUtils(String propertyFile, SQLExpressionTemplate template) {
Properties properties = readProperties( propertyFile );
this.jdbcUrl = properties.getProperty( "jdbcUrl" );
this.jdbcDriver = properties.getProperty( "jdbcDriver" );
this.jdbcUser = properties.getProperty( "jdbcUser" );
this.jdbcPass = properties.getProperty( "jdbcPass" );
this.sqlExpressionTemplate = template;
createBasicDataSource();
}
private Properties readProperties(String propertyFile) {
InputStream is = null;
try {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream( propertyFile );
if ( is == null ) {
throw new RuntimeException( String.format( "File %s not found on classpath.", propertyFile ) );
}
Properties properties = new Properties();
properties.load( is );
return properties;
}
catch (IOException e) {
throw (new RuntimeException( e ));
}
finally {
if ( is != null ) {
try {
is.close();
}
catch (IOException e) {
//nothing to do
}
}
}
}
private void createBasicDataSource() {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName( jdbcDriver );
bds.setUrl( jdbcUrl );
bds.setUsername( jdbcUser );
bds.setPassword( jdbcPass );
dataSource = bds;
}
/**
* Closes the connections to the database.
*
* @throws SQLException
*/
public void close() throws SQLException {
((BasicDataSource) dataSource).close();
}
/**
* Returns a DataSource for the configured database.
*
* @return a DataSource
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Returns a JDBC connection to the database
*
* @return a JDBC Connection object
*
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
Connection cn = getDataSource().getConnection();
cn.setAutoCommit( false );
return cn;
}
/**
* Delete all testsuite-suite data from the database
*
* @throws SQLException
*/
public void deleteTestData() throws SQLException {
Connection cn = null;
try {
cn = getDataSource().getConnection();
cn.setAutoCommit( false );
PreparedStatement pmt = cn.prepareStatement( "delete from GEOMTEST" );
if ( !pmt.execute() ) {
int updateCount = pmt.getUpdateCount();
LOG.info( "Removing " + updateCount + " rows." );
}
cn.commit();
pmt.close();
}
finally {
try {
if ( cn != null ) {
cn.close();
}
}
catch (SQLException e) {
// nothing to do
}
}
}
public void insertTestData(TestData testData) throws SQLException {
Connection cn = null;
try {
cn = getDataSource().getConnection();
cn.setAutoCommit( false );
Statement stmt = cn.createStatement();
for ( TestDataElement testDataElement : testData ) {
String sql = sqlExpressionTemplate.toInsertSql( testDataElement );
LOG.debug( "adding stmt: " + sql );
stmt.addBatch( sql );
}
int[] insCounts = stmt.executeBatch();
cn.commit();
stmt.close();
LOG.info( "Loaded " + sum( insCounts ) + " rows." );
}
catch (SQLException e) {
e.printStackTrace();
throw e;
}
finally {
try {
if ( cn != null ) {
cn.close();
}
}
catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* Parses the content of a file into an executable SQL statement.
*
* @param fileName name of a file containing SQL-statements
*
* @return
*
* @throws IOException
*/
public String parseSqlIn(String fileName) throws IOException {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( fileName );
if ( is == null ) {
throw new RuntimeException( "File " + fileName + " not found on Classpath." );
}
BufferedReader reader = null;
try {
reader = new BufferedReader(
new InputStreamReader( is, Charset.forName( "UTF-8" ) )
);
StringWriter sw = new StringWriter();
BufferedWriter writer = new BufferedWriter( sw );
for ( int c = reader.read(); c != -1; c = reader.read() ) {
writer.write( c );
}
writer.flush();
return sw.toString();
}
finally {
if ( reader != null ) {
reader.close();
}
is.close();
}
}
/**
* Executes a SQL statement.
* <p/>
* This is used e.g. to drop/create a spatial index, or update the
* geometry metadata statements.
*
* @param sql the (native) SQL Statement to execute
*
* @throws SQLException
*/
public void executeStatement(String sql) throws SQLException {
Connection cn = null;
try {
cn = getDataSource().getConnection();
cn.setAutoCommit( false );
PreparedStatement statement = cn.prepareStatement( sql );
LOG.info( "Executing statement: " + sql );
statement.execute();
cn.commit();
statement.close();
}
finally {
try {
if ( cn != null ) {
cn.close();
}
}
catch (SQLException e) {
} //do nothing.
}
}
/**
* Operations to fully initialize the
*/
public void afterCreateSchema() {
}
/**
* Return the geometries of the testsuite-suite objects as raw (i.e. undecoded) objects from the database.
*
* @param type type of geometry
*
* @return map of identifier, undecoded geometry object
*/
public Map<Integer, Object> rawDbObjects(String type) {
Map<Integer, Object> map = new HashMap<Integer, Object>();
Connection cn = null;
PreparedStatement pstmt = null;
ResultSet results = null;
try {
cn = getDataSource().getConnection();
pstmt = cn.prepareStatement( "select id, geom from geomtest where type = ? order by id" );
pstmt.setString( 1, type );
results = pstmt.executeQuery();
while ( results.next() ) {
Integer id = results.getInt( 1 );
Object obj = results.getObject( 2 );
map.put( id, obj );
}
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
try {
if ( results != null ) {
results.close();
}
}
catch (SQLException e) {
//nothing to do
}
try {
if ( pstmt != null ) {
pstmt.close();
}
}
catch (SQLException e) {
//nothing to do
}
try {
if ( cn != null ) {
cn.close();
}
}
catch (SQLException e) {
// nothing we can do.
}
}
return map;
}
/**
* Returns the JTS geometries that are expected of a decoding of the testsuite-suite object's geometry.
* <p/>
* <p>This method reads the WKT of the testsuite-suite objects and returns the result.</p>
*
* @param type type of geometry
*
* @return map of identifier and JTS geometry
*/
public Map<Integer, Geometry> expectedGeoms(String type, TestData testData) {
Map<Integer, Geometry> result = new HashMap<Integer, Geometry>();
WktDecoder decoder = Wkt.newDecoder();
for ( TestDataElement testDataElement : testData ) {
if ( testDataElement.type.equalsIgnoreCase( type ) ) {
try {
result.put( testDataElement.id, decoder.decode( testDataElement.wkt ) );
}
catch (WktDecodeException e) {
System.out
.println(
String.format(
"Parsing WKT fails for case %d : %s",
testDataElement.id,
testDataElement.wkt
)
);
throw new RuntimeException( e );
}
}
}
return result;
}
private static int sum(int[] insCounts) {
int result = 0;
for ( int idx = 0; idx < insCounts.length; idx++ ) {
result += insCounts[idx];
}
return result;
}
}