/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.referencing.factory.epsg;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Logger;
import org.geotools.factory.Hints;
import org.geotools.util.logging.Logging;
import org.hsqldb.jdbc.jdbcDataSource;
import org.opengis.referencing.FactoryException;
/**
* This utility class knows everything there is to know about the care and
* feeding of our pet EPSG database. This utility class is used to hold logic
* previously associated with our own custom DataSource.
* <p>
* The EPSG database can be downloaded from <A
* HREF="http://www.epsg.org">http://www.epsg.org</A>. The SQL scripts
* (modified for the HSQL syntax as <A HREF="doc-files/HSQL.html">explained here</A>)
* are bundled into this plugin. The database version is given in the
* {@linkplain org.opengis.metadata.citation.Citation#getEdition edition attribute}
* of the
* {@linkplain org.opengis.referencing.AuthorityFactory#getAuthority authority}.
* The HSQL database is read only.
* <p>
*
* @since 2.4
*
* @source $URL$
* @version $Id$
* @author Jody Garnett
*
* @todo This class is used only by {@link HSQLDataSource}, which is deprecated.
*/
public class HsqlEpsgDatabase {
/**
* The key for fetching the database directory from
* {@linkplain System#getProperty(String) system properties}.
*/
public static final String DIRECTORY_KEY = "EPSG-HSQL.directory";
/**
* The database name.
*/
public static final String DATABASE_NAME = "EPSG";
/**
* Creates a DataSource that is set up and ready to go.
* <p>
* This method pays attention to the system property "EPSG-HSQL.directory"
* and makes use of the default database name "EPSG".
* </p>
*
* @return
* @throws SQLException
*/
public static javax.sql.DataSource createDataSource() throws SQLException {
return createDataSource(getDirectory());
}
public static javax.sql.DataSource createDataSource(Hints hints ) throws FactoryException {
try {
return createDataSource(getDirectory());
} catch (SQLException e) {
throw new FactoryException( e );
}
}
public static javax.sql.DataSource createDataSource(File directory) throws SQLException {
jdbcDataSource dataSource = new jdbcDataSource();
/*
* Constructs the full path to the HSQL database. Note: we do not use
* File.toURI() because HSQL doesn't seem to expect an encoded URL (e.g.
* "%20" instead of spaces).
*/
final StringBuilder url = new StringBuilder("jdbc:hsqldb:file:");
final String path = directory.getAbsolutePath().replace(File.separatorChar, '/');
if (path.length()==0 || path.charAt(0)!='/') {
url.append('/');
}
url.append(path);
if (url.charAt(url.length()-1) != '/') {
url.append('/');
}
url.append(HsqlEpsgDatabase.DATABASE_NAME);
dataSource.setDatabase(url.toString());
/*
* If the temporary directory do not exists or can't be created, lets
* the 'database' attribute unset. If the user do not set it explicitly
* (for example through JNDI), an exception will be thrown when
* 'getConnection()' will be invoked.
*/
dataSource.setUser("SA"); // System administrator. No password.
if (!dataExists(dataSource)) {
generateData(dataSource);
try {
forceReadOnly(directory);
} catch (IOException file) {
throw (SQLException) new SQLException("Can't read the SQL script.").initCause(file);
// TODO: inline cause when we will be allowed to target Java 6.
}
}
return dataSource;
}
/**
* HSQL has created automatically an empty database. We need to populate it.
* Executes the SQL scripts bundled in the JAR. In theory, each line
* contains a full SQL statement. For this plugin however, we have
* compressed "INSERT INTO" statements using Compactor class in this
* package.
*/
private static void generateData(javax.sql.DataSource dataSource) throws SQLException {
Connection connection = dataSource.getConnection();
Logging.getLogger("org.geotools.referencing.factory").config(
"Creating cached EPSG database."); // TODO: localize
final Statement statement = connection.createStatement();
try {
final BufferedReader in = new BufferedReader(new InputStreamReader(
HsqlEpsgDatabase.class.getResourceAsStream("EPSG.sql"),
"ISO-8859-1"));
StringBuilder insertStatement = null;
String line;
while ((line = in.readLine()) != null) {
line = line.trim();
final int length = line.length();
if (length != 0) {
if (line.startsWith("INSERT INTO")) {
/*
* We are about to insert many rows into a single table.
* The row values appear in next lines; the current line
* should stop right after the VALUES keyword.
*/
insertStatement = new StringBuilder(line);
continue;
}
if (insertStatement != null) {
/*
* We are about to insert a row. Prepend the "INSERT
* INTO" statement and check if we will have more rows
* to insert after this one.
*/
final int values = insertStatement.length();
insertStatement.append(line);
final boolean hasMore = (line.charAt(length - 1) == ',');
if (hasMore) {
insertStatement.setLength(insertStatement.length() - 1);
}
line = insertStatement.toString();
insertStatement.setLength(values);
if (!hasMore) {
insertStatement = null;
}
}
statement.execute(line);
}
}
in.close();
} catch (IOException exception) {
SQLException e = new SQLException("Can't read the SQL script."); // TODO: localize
e.initCause(exception); // TODO: inline cause when we will be allowed to target Java 6.
throw e;
} finally {
statement.close();
connection.close();
}
}
private static void forceReadOnly(File directory) throws IOException {
final File file = new File(directory, HsqlEpsgDatabase.DATABASE_NAME + ".properties");
final InputStream propertyIn = new FileInputStream(file);
final Properties properties = new Properties();
properties.load(propertyIn);
propertyIn.close();
properties.put("readonly", "true");
final OutputStream out = new FileOutputStream(file);
properties.store(out, "EPSG database on HSQL");
out.close();
}
/**
* Returns the default directory for the EPSG database. If the
* {@value #DIRECTORY_KEY}
* {@linkplain System#getProperty(String) system property} is defined and
* contains the name of a directory with a valid
* {@linkplain File#getParent parent}, then the {@value #DATABASE_NAME}
* database will be saved in that directory. Otherwise, a temporary
* directory will be used.
*
* @throws SQLException
*/
static File getDirectory() throws SQLException {
try {
final String property = System.getProperty(HsqlEpsgDatabase.DIRECTORY_KEY);
if (property != null) {
final File directory = new File(property);
/*
* Creates the directory if needed (mkdir), but NOT the parent
* directories (mkdirs) because a missing parent directory may
* be a symptom of an installation problem. For example if
* 'directory' is a subdirectory in the temporary directory
* (~/tmp/), this temporary directory should already exists. If
* it doesn't, an administrator should probably looks at this
* problem.
*/
if (directory.isDirectory() || directory.mkdir()) {
return directory;
}
}
} catch (SecurityException e) {
/*
* Can't fetch the base directory from system properties. Fallback
* on the default temporary directory.
*/
}
File directory = new File(System.getProperty("java.io.tmpdir", "."), "Geotools");
if (directory.isDirectory() || directory.mkdir()) {
directory = new File(directory, "Databases/HSQL");
if (directory.isDirectory() || directory.mkdirs()) {
return directory;
}
}
throw new SQLException("Can't write to the database directory.");
}
static boolean dataExists(javax.sql.DataSource dataSource) throws SQLException {
Connection connection = dataSource.getConnection();
try {
return dataExists(connection);
} finally {
connection.close();
}
}
/**
* Returns {@code true} if the database contains data. This method returns
* {@code false} if an empty EPSG database has been automatically created by
* HSQL and not yet populated.
*/
static boolean dataExists(final Connection connection) throws SQLException {
final ResultSet tables = connection.getMetaData().getTables(null, null,
"EPSG_%", new String[] { "TABLE" });
final boolean exists = tables.next();
tables.close();
return exists;
}
}