001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.referencing.factory.epsg;
017:
018: // J2SE dependencies
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.OutputStream;
023: import java.io.BufferedReader;
024: import java.io.FileInputStream;
025: import java.io.FileOutputStream;
026: import java.io.InputStreamReader;
027: import java.sql.ResultSet;
028: import java.sql.Statement;
029: import java.sql.Connection;
030: import java.sql.SQLException;
031: import java.util.Properties;
032:
033: // Geotools dependencies
034: import org.geotools.factory.Hints;
035: import org.geotools.util.logging.Logging;
036: import org.geotools.referencing.factory.AbstractAuthorityFactory;
037:
038: // HSQL dependencies
039: import org.hsqldb.jdbc.jdbcDataSource;
040:
041: /**
042: * Connection to the EPSG database in HSQL database engine format using JDBC. The EPSG
043: * database can be downloaded from <A HREF="http://www.epsg.org">http://www.epsg.org</A>.
044: * The SQL scripts (modified for the HSQL syntax as <A HREF="doc-files/HSQL.html">explained
045: * here</A>) are bundled into this plugin. The database version is given in the
046: * {@linkplain org.opengis.metadata.citation.Citation#getEdition edition attribute}
047: * of the {@linkplain org.opengis.referencing.AuthorityFactory#getAuthority authority}.
048: * The HSQL database is read only.
049: * <P>
050: * <H3>Implementation note</H3>
051: * The SQL scripts are executed the first time a connection is required. The database
052: * is then created as cached tables ({@code HSQL.properties} and {@code HSQL.data} files)
053: * in a temporary directory. Future connections to the EPSG database while reuse the cached
054: * tables, if available. Otherwise, the scripts will be executed again in order to recreate
055: * them.
056: * <p>
057: * If the EPSG database should be created in a different directory (or already exists in that
058: * directory), this directory can be specified in two ways:
059: * <p>
060: * <ul>
061: * <li>It may be given explicitly as an argument to the {@linkplain #HSQLDataSource(File)
062: * constructor}.</li>
063: * <li>It may be specified as a {@linkplain System#getProperty(String) system property}
064: * nammed {@value #DIRECTORY_KEY}.</li>
065: * </ul>
066: *
067: * @since 2.2
068: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/epsg-hsql/src/main/java/org/geotools/referencing/factory/epsg/HSQLDataSource.java $
069: * @version $Id: HSQLDataSource.java 27862 2007-11-12 19:51:19Z desruisseaux $
070: * @author Martin Desruisseaux
071: * @author Didier Richard
072: *
073: * @deprecated Replaced by {@link FactoryOnHSQL}.
074: */
075: public class HSQLDataSource extends jdbcDataSource implements
076: DataSource {
077: /**
078: * The key for fetching the database directory from {@linkplain System#getProperty(String)
079: * system properties}.
080: *
081: * @since 2.3
082: */
083: public static final String DIRECTORY_KEY = "EPSG-HSQL.directory";
084:
085: /**
086: * The database name.
087: *
088: * @since 2.3
089: */
090: public static final String DATABASE_NAME = "EPSG";
091:
092: /**
093: * The directory where the database is stored.
094: */
095: private File directory;
096:
097: /**
098: * Creates a new instance of this data source. If the {@value #DIRECTORY_KEY}
099: * {@linkplain System#getProperty(String) system property} is defined and contains
100: * the name of a directory with a valid {@linkplain File#getParent parent}, then the
101: * {@value #DATABASE_NAME} database will be saved in that directory. Otherwise, a
102: * temporary directory will be used.
103: */
104: public HSQLDataSource() {
105: this (getDirectory());
106: }
107:
108: /**
109: * Creates a new instance of this data source using the {@value #DATABASE_NAME} database in the
110: * specified directory. If {@code directory} is {@code null}, then callers are responsible
111: * to invoke {@link #setDatabase} explicitly.
112: *
113: * @param directory The directory for the DATABASE_NAME HSQL database,
114: * or {@code null} if none.
115: *
116: * @since 2.3
117: */
118: public HSQLDataSource(final File directory) {
119: this .directory = directory;
120: if (directory != null) {
121: /*
122: * Constructs the full path to the HSQL database. Note: we do not use
123: * File.toURI() because HSQL doesn't seem to expect an encoded URL
124: * (e.g. "%20" instead of spaces).
125: */
126: final StringBuffer url = new StringBuffer(
127: "jdbc:hsqldb:file:");
128: final String path = directory.getAbsolutePath().replace(
129: File.separatorChar, '/');
130: if (path.length() == 0 || path.charAt(0) != '/') {
131: url.append('/');
132: }
133: url.append(path);
134: if (url.charAt(url.length() - 1) != '/') {
135: url.append('/');
136: }
137: url.append(DATABASE_NAME);
138: setDatabase(url.toString());
139: this .directory = directory;
140: }
141: /*
142: * If the temporary directory do not exists or can't be created,
143: * lets the 'database' attribute unset. If the user do not set it
144: * explicitly (for example through JNDI), an exception will be thrown
145: * when 'getConnection()' will be invoked.
146: */
147: setUser("SA"); // System administrator. No password.
148: }
149:
150: /**
151: * Returns the priority for this data source. This priority is set to a lower value than
152: * the {@linkplain AccessDataSource}'s one in order to give the priority to the Access-backed
153: * database, if presents. Priorities are set that way because:
154: * <ul>
155: * <li>The MS-Access format is the primary EPSG database format.</li>
156: * <li>If a user downloads the MS-Access database himself, he probably wants to use it.</li>
157: * </ul>
158: */
159: public int getPriority() {
160: return NORMAL_PRIORITY - 30;
161: }
162:
163: /**
164: * Returns the default directory for the EPSG database. If the {@value #DIRECTORY_KEY}
165: * {@linkplain System#getProperty(String) system property} is defined and contains the
166: * name of a directory with a valid {@linkplain File#getParent parent}, then the
167: * {@value #DATABASE_NAME} database will be saved in that directory. Otherwise,
168: * a temporary directory will be used.
169: */
170: private static File getDirectory() {
171: try {
172: final String property = System.getProperty(DIRECTORY_KEY);
173: if (property != null) {
174: final File directory = new File(property);
175: /*
176: * Creates the directory if needed (mkdir), but NOT the parent directories (mkdirs)
177: * because a missing parent directory may be a symptom of an installation problem.
178: * For example if 'directory' is a subdirectory in the temporary directory (~/tmp/),
179: * this temporary directory should already exists. If it doesn't, an administrator
180: * should probably looks at this problem.
181: */
182: if (directory.isDirectory() || directory.mkdir()) {
183: return directory;
184: }
185: }
186: } catch (SecurityException e) {
187: /*
188: * Can't fetch the base directory from system properties.
189: * Fallback on the default temporary directory.
190: */
191: }
192: File directory = new File(System.getProperty("java.io.tmpdir",
193: "."), "Geotools");
194: if (directory.isDirectory() || directory.mkdir()) {
195: directory = new File(directory, "Databases/HSQL");
196: if (directory.isDirectory() || directory.mkdirs()) {
197: return directory;
198: }
199: }
200: return null;
201: }
202:
203: /**
204: * Returns {@code true} if the database contains data. This method returns {@code false}
205: * if an empty EPSG database has been automatically created by HSQL and not yet populated.
206: */
207: private static boolean dataExists(final Connection connection)
208: throws SQLException {
209: final ResultSet tables = connection.getMetaData().getTables(
210: null, null, "EPSG_%", new String[] { "TABLE" });
211: final boolean exists = tables.next();
212: tables.close();
213: return exists;
214: }
215:
216: /**
217: * Opens a connection to the database. If the cached tables are not available,
218: * they will be created now from the SQL scripts bundled in this plugin.
219: */
220: public Connection getConnection() throws SQLException {
221: final String database = getDatabase();
222: if (database == null || database.trim().length() == 0) {
223: /*
224: * The 'database' attribute is unset if the constructor has been unable
225: * to locate the temporary directory, or to create the subdirectory.
226: */
227: // TODO: localize
228: throw new SQLException(
229: "Can't write to the database directory.");
230: }
231: Connection connection = super .getConnection();
232: if (!dataExists(connection)) {
233: /*
234: * HSQL has created automatically an empty database. We need to populate it.
235: * Executes the SQL scripts bundled in the JAR. In theory, each line contains
236: * a full SQL statement. For this plugin however, we have compressed "INSERT
237: * INTO" statements using Compactor class in this package.
238: */
239: Logging.getLogger("org.geotools.referencing.factory")
240: .config("Creating cached EPSG database."); // TODO: localize
241: final Statement statement = connection.createStatement();
242: try {
243: final BufferedReader in = new BufferedReader(
244: new InputStreamReader(HSQLDataSource.class
245: .getResourceAsStream("EPSG.sql"),
246: "ISO-8859-1"));
247: StringBuffer insertStatement = null;
248: String line;
249: while ((line = in.readLine()) != null) {
250: line = line.trim();
251: final int length = line.length();
252: if (length != 0) {
253: if (line.startsWith("INSERT INTO")) {
254: /*
255: * We are about to insert many rows into a single table.
256: * The row values appear in next lines; the current line
257: * should stop right after the VALUES keyword.
258: */
259: insertStatement = new StringBuffer(line);
260: continue;
261: }
262: if (insertStatement != null) {
263: /*
264: * We are about to insert a row. Prepend the "INSERT INTO"
265: * statement and check if we will have more rows to insert
266: * after this one.
267: */
268: final int values = insertStatement.length();
269: insertStatement.append(line);
270: final boolean hasMore = (line
271: .charAt(length - 1) == ',');
272: if (hasMore) {
273: insertStatement
274: .setLength(insertStatement
275: .length() - 1);
276: }
277: line = insertStatement.toString();
278: insertStatement.setLength(values);
279: if (!hasMore) {
280: insertStatement = null;
281: }
282: }
283: statement.execute(line);
284: }
285: }
286: in.close();
287: /*
288: * The database has been fully created. Now, make it read-only.
289: */
290: if (directory != null) {
291: final File file = new File(directory, DATABASE_NAME
292: + ".properties");
293: final InputStream propertyIn = new FileInputStream(
294: file);
295: final Properties properties = new Properties();
296: properties.load(propertyIn);
297: propertyIn.close();
298: properties.put("readonly", "true");
299: final OutputStream out = new FileOutputStream(file);
300: properties.store(out, "EPSG database on HSQL");
301: out.close();
302: }
303: } catch (IOException exception) {
304: statement.close();
305: SQLException e = new SQLException(
306: "Can't read the SQL script."); // TODO: localize
307: e.initCause(exception);
308: throw e;
309: }
310: statement.close();
311: connection.close();
312: connection = super .getConnection();
313: assert dataExists(connection);
314: }
315: return connection;
316: }
317:
318: /**
319: * Opens a connection and creates an {@linkplain FactoryUsingSQL EPSG factory} for it.
320: *
321: * @param hints A map of hints, including the low-level factories to use for CRS creation.
322: * @return The EPSG factory using HSQLDB SQL syntax.
323: * @throws SQLException if connection to the database failed.
324: */
325: public AbstractAuthorityFactory createFactory(final Hints hints)
326: throws SQLException {
327: return new FactoryUsingHSQL(hints, getConnection());
328: }
329:
330: /**
331: * For compilation with Java 6.
332: */
333: public boolean isWrapperFor(Class type) {
334: return false;
335: }
336:
337: /**
338: * For compilation with Java 6.
339: */
340: public Object unwrap(Class type) throws SQLException {
341: throw new SQLException();
342: }
343: }
|