/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.spatialite;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Map;
import org.geotools.data.jdbc.FilterToSQL;
import org.geotools.geometry.jts.Geometries;
import org.geotools.jdbc.BasicSQLDialect;
import org.geotools.jdbc.JDBCDataStore;
import org.geotools.referencing.CRS;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;
/**
* Dialect for SpatiaLite embedded database.
*
* @author Justin Deoliveira, OpenGeo
*
*
* @source $URL$
*/
public class SpatiaLiteDialect extends BasicSQLDialect {
public static String SPATIALITE_SPATIAL_INDEX = "org.geotools.data.spatialite.spatialIndex";
static String spatialiteLibFile() {
String libspatialite = System.mapLibraryName("spatialite");
if (libspatialite.endsWith("jnilib")) {
libspatialite = libspatialite.replaceAll("jnilib", "dylib");
}
return libspatialite;
}
public SpatiaLiteDialect(JDBCDataStore dataStore) {
super(dataStore);
}
@Override
public void initializeConnection(Connection cx) throws SQLException {
Statement st = cx.createStatement();
try {
//load the spatial extensions
String libspatialite = spatialiteLibFile();
try {
st.execute( "SELECT load_extension('"+libspatialite+"')" );
}
catch(SQLException e) {
LOGGER.warning("libspatialite not found, attempting to load internal library" );
loadSpatiaLiteLib(libspatialite);
st.execute( "SELECT load_extension('"+libspatialite+"')" );
}
st.close();
st = cx.createStatement();
//determine if the spatial metadata tables need to be created
boolean initSpatialMetaData = false;
try {
st.execute( "SELECT count(*) from geometry_columns");
}catch( SQLException e ) {
initSpatialMetaData = true;
}
if ( initSpatialMetaData ) {
st.execute( "SELECT InitSpatialMetaData()");
st.close();
st = cx.createStatement();
}
//determine if the spatial ref sys table needs to be loaded
boolean loadSpatialRefSys = false;
ResultSet rs = st.executeQuery( "SELECT * FROM spatial_ref_sys");
try {
loadSpatialRefSys = !rs.next();
}
finally {
dataStore.closeSafe( rs );
}
if ( loadSpatialRefSys ) {
try {
BufferedReader in = new BufferedReader( new InputStreamReader(
getClass().getResourceAsStream( "init_spatialite-2.3.sql") ) );
String line = null;
while( (line = in.readLine() ) != null ) {
st.execute( line );
}
in.close();
}
catch( IOException e ) {
throw new RuntimeException( "Error reading spatial ref sys file", e );
}
}
}
finally {
dataStore.closeSafe( st );
}
}
void loadSpatiaLiteLib(String lib) {
//copy the local lib into a temp file and load directly
InputStream libstream = SpatiaLiteDataStoreFactory.class.getResourceAsStream(lib);
if (libstream == null) {
throw new RuntimeException("No library " + lib);
}
File libfile = new File(System.getProperty("user.dir"), lib);
try {
BufferedInputStream in = new BufferedInputStream(libstream);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(libfile));
int c = -1;
while( (c = in.read()) != -1 ) {
out.write(c);
}
out.flush();
out.close();
in.close();
}
catch(IOException ioe) {
throw new RuntimeException(ioe);
}
}
@Override
public Class<?> getMapping(ResultSet columnMetaData, Connection cx) throws SQLException {
//the sqlite jdbc driver maps geometry type to varchar, so do a lookup
// in the geometry_columns table
String tbl = columnMetaData.getString( "TABLE_NAME");
String col = columnMetaData.getString( "COLUMN_NAME");
String sql = "SELECT type FROM geometry_columns " +
"WHERE f_table_name = '" + tbl + "' " +
"AND f_geometry_column = '" + col + "'";
LOGGER.fine( sql );
Statement st = cx.createStatement();
try {
ResultSet rs = st.executeQuery( sql );
try {
if ( rs.next() ) {
String type = rs.getString( "type" );
return Geometries.getForName( type ).getBinding();
}
}
finally {
dataStore.closeSafe( rs );
}
}
finally {
dataStore.closeSafe( st );
}
return null;
}
@Override
public void registerClassToSqlMappings(Map<Class<?>, Integer> mappings) {
super.registerClassToSqlMappings(mappings);
mappings.put( Geometries.POINT.getBinding(), Geometries.POINT.getSQLType() );
mappings.put( Geometries.LINESTRING.getBinding(), Geometries.LINESTRING.getSQLType() );
mappings.put( Geometries.POLYGON.getBinding(), Geometries.POLYGON.getSQLType() );
mappings.put( Geometries.MULTIPOINT.getBinding(), Geometries.MULTIPOINT.getSQLType() );
mappings.put( Geometries.MULTILINESTRING.getBinding(), Geometries.MULTILINESTRING.getSQLType() );
mappings.put( Geometries.MULTIPOLYGON.getBinding(), Geometries.MULTIPOLYGON.getSQLType() );
mappings.put( Geometries.GEOMETRY.getBinding(), Geometries.GEOMETRY.getSQLType() );
mappings.put( Geometries.GEOMETRYCOLLECTION.getBinding(), Geometries.GEOMETRYCOLLECTION.getSQLType() );
//override some internal defaults
mappings.put(Long.class, Types.INTEGER);
mappings.put(Double.class, Types.REAL);
}
@Override
public String getGeometryTypeName(Integer type) {
return Geometries.getForSQLType( type ).getName();
}
@Override
public Integer getGeometrySRID(String schemaName, String tableName, String columnName,
Connection cx) throws SQLException {
String sql = "SELECT srid FROM geometry_columns " +
"WHERE f_table_name = '" + tableName + "' " +
"AND f_geometry_column = '" + columnName + "'";
Statement st = cx.createStatement();
try {
LOGGER.fine( sql );
ResultSet rs = st.executeQuery( sql );
try {
if ( rs.next() ) {
return Integer.valueOf( rs.getInt( 1 ) );
}
}
finally {
dataStore.closeSafe( rs );
}
}
finally {
dataStore.closeSafe( st );
}
return super.getGeometrySRID(schemaName, tableName, columnName, cx);
}
@Override
public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) {
sql.append( "AsText(");
encodeColumnName( gatt.getLocalName(), sql);
sql.append( ")||';").append(srid).append("'");
}
@Override
public Geometry decodeGeometryValue(GeometryDescriptor descriptor, ResultSet rs, int column,
GeometryFactory factory, Connection cx) throws IOException, SQLException {
String string = rs.getString( column );
if ( string == null || "".equals( string.trim() ) ) {
return null;
}
String[] split = string.split( ";" );
String wkt = split[0];
try {
return new WKTReader(factory).read( wkt );
}
catch( ParseException e ) {
throw (IOException) new IOException().initCause( e );
}
}
@Override
public void encodeGeometryValue(Geometry value, int srid, StringBuffer sql) throws IOException {
sql.append("GeomFromText('") .append( new WKTWriter().write( value ) ).append( "',")
.append(srid).append(")");
}
@Override
public Geometry decodeGeometryValue(GeometryDescriptor descriptor, ResultSet rs, String column,
GeometryFactory factory, Connection cx) throws IOException, SQLException {
return null;
}
@Override
public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) {
sql.append("asText(envelope(");
encodeColumnName(geometryColumn, sql);
sql.append( "))");
}
@Override
public Envelope decodeGeometryEnvelope(ResultSet rs, int column, Connection cx)
throws SQLException, IOException {
String wkt = rs.getString( column );
if ( wkt != null ) {
try {
return new WKTReader().read( wkt ).getEnvelopeInternal();
}
catch (ParseException e) {
throw (IOException) new IOException("Error decoding envelope bounds").initCause( e );
}
}
return null;
}
@Override
public void postCreateTable(String schemaName, SimpleFeatureType featureType, Connection cx)
throws SQLException, IOException {
//create any geometry columns entries after the fact
for ( AttributeDescriptor ad : featureType.getAttributeDescriptors() ) {
if ( ad instanceof GeometryDescriptor ) {
GeometryDescriptor gd = (GeometryDescriptor) ad;
StringBuffer sql = new StringBuffer( "INSERT INTO geometry_columns VALUES (");
//table name
sql.append( "'").append( featureType.getTypeName() ).append( "'," );
//geometry name
sql.append( "'").append( gd.getLocalName() ).append( "',");
//type
String gType = Geometries.getForBinding((Class<? extends Geometry>) gd.getType().getBinding() ).getName();
if ( gType == null ) {
throw new IOException( "Unknown geometry type: " + gd.getType().getBinding() );
}
sql.append( "'").append( gType ).append( "',");
//coord dimension
sql.append( 2 ).append( ",");
//srid
Integer epsgCode = null;
if ( gd.getCoordinateReferenceSystem() != null ) {
CoordinateReferenceSystem crs = gd.getCoordinateReferenceSystem();
try {
epsgCode = CRS.lookupEpsgCode( crs , true );
}
catch (Exception e) {}
}
if ( epsgCode == null ) {
throw new IOException( "Unable to find epsg code code.");
}
sql.append( epsgCode ).append( ",");
//spatial index enabled
sql.append( 0 ).append( ")");
LOGGER.fine( sql.toString() );
Statement st = cx.createStatement();
try {
st.executeUpdate( sql.toString() );
}
finally {
dataStore.closeSafe( st );
}
}
}
}
@Override
public void postCreateFeatureType(SimpleFeatureType featureType, DatabaseMetaData metadata,
String schemaName, Connection cx) throws SQLException {
//figure out if the table has a spatial index and mark the feature type as so
for (AttributeDescriptor ad : featureType.getAttributeDescriptors()) {
if (!(ad instanceof GeometryDescriptor)) {
continue;
}
GeometryDescriptor gd = (GeometryDescriptor) ad;
String idxTableName = "idx_" + featureType.getTypeName() + "_" + gd.getLocalName();
ResultSet rs = metadata.getTables(null, schemaName, idxTableName, new String[]{"TABLE"});
try {
if (rs.next()) {
gd.getUserData().put(SPATIALITE_SPATIAL_INDEX, idxTableName);
}
}
finally {
dataStore.closeSafe(rs);
}
}
}
@Override
public boolean lookupGeneratedValuesPostInsert() {
return true;
}
@Override
public Object getLastAutoGeneratedValue(String schemaName, String tableName, String columnName,
Connection cx) throws SQLException {
Statement st = cx.createStatement();
try {
ResultSet rs = st.executeQuery( "SELECT last_insert_rowid();");
try {
if (rs.next()) {
return rs.getInt( 1 );
}
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(st);
}
return null;
}
@Override
public boolean isLimitOffsetSupported() {
//TODO: figure out why aggregate functions don't work with limit offset applied
return false;
}
@Override
public void applyLimitOffset(StringBuffer sql, int limit, int offset) {
if(limit > 0 && limit < Integer.MAX_VALUE) {
sql.append(" LIMIT " + limit);
if(offset > 0) {
sql.append(" OFFSET " + offset);
}
} else if(offset > 0) {
sql.append(" OFFSET " + offset);
}
}
@Override
public FilterToSQL createFilterToSQL() {
return new SpatiaLiteFilterToSQL();
}
}