Statistics
| Revision:

root / org.gvsig.projection.jcrs / trunk / org.gvsig.projection.jcrs / org.gvsig.projection.jcrs.lib / src / main / java / es / idr / teledeteccion / connection / epsg / HSQLDataSource.java @ 659

History | View | Annotate | Download (14 KB)

1
/*
2
 * Geotools 2 - OpenSource mapping toolkit
3
 * (C) 2005, Geotools Project Managment Committee (PMC)
4
 *
5
 *    This library is free software; you can redistribute it and/or
6
 *    modify it under the terms of the GNU Lesser General Public
7
 *    License as published by the Free Software Foundation; either
8
 *    version 2.1 of the License, or (at your option) any later version.
9
 *
10
 *    This library is distributed in the hope that it will be useful,
11
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 *    Lesser General Public License for more details.
14
 *
15
 *    You should have received a copy of the GNU Lesser General Public
16
 *    License along with this library; if not, write to the Free Software
17
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 */
19
package es.idr.teledeteccion.connection.epsg;
20

    
21
// J2SE dependencies
22
import java.awt.AWTEvent;
23
import java.awt.EventQueue;
24
import java.awt.SecondaryLoop;
25
import java.awt.Toolkit;
26
import java.io.BufferedReader;
27
import java.io.File;
28
import java.io.FileInputStream;
29
import java.io.IOException;
30
import java.io.InputStreamReader;
31
import java.lang.management.ManagementFactory;
32
import java.nio.channels.FileChannel;
33
import java.sql.Connection;
34
import java.sql.ResultSet;
35
import java.sql.SQLException;
36
import java.sql.SQLFeatureNotSupportedException;
37
import java.sql.Statement;
38
import java.util.Date;
39
import java.util.logging.Level;
40
import javax.swing.SwingUtilities;
41

    
42
import org.apache.commons.io.FileUtils;
43
import org.apache.commons.lang3.StringUtils;
44
import org.apache.commons.lang3.mutable.MutableBoolean;
45
import org.geotools.referencing.factory.AbstractAuthorityFactory;
46
import org.geotools.referencing.factory.FactoryGroup;
47
import org.geotools.referencing.factory.epsg.DataSource;
48
import org.geotools.referencing.factory.epsg.FactoryUsingSQL;
49
import org.gvsig.crs.CrsFactory;
50
import org.gvsig.tools.ToolsLocator;
51
import org.gvsig.tools.task.SimpleTaskStatus;
52
// HSQL dependencies
53
import org.hsqldb.jdbc.jdbcDataSource;
54
import org.slf4j.Logger;
55
import org.slf4j.LoggerFactory;
56

    
57
/**
58
 * Connection to the EPSG database in HSQL database engine format using JDBC.
59
 * The EPSG database can be downloaded from <A
60
 * HREF="http://www.epsg.org">http://www.epsg.org</A>. The SQL scripts (modified
61
 * for the HSQL syntax as <A HREF="doc-files/HSQL.html">explained here</A>) are
62
 * bundled into this plugin. The database version is given in the
63
 * {@linkplain org.opengis.metadata.citation.Citation#getEdition edition attribute}
64
 * of the
65
 * {@linkplain org.opengis.referencing.AuthorityFactory#getAuthority authority}.
66
 * The HSQL database is read only.
67
 * <P>
68
 * <H3>Implementation note</H3>
69
 * The SQL scripts are executed the first time a connection is required. The
70
 * database is then created as cached tables ({@code HSQL.properties} and
71
 * {@code HSQL.data} files) in a temporary directory. Future connections to the
72
 * EPSG database while reuse the cached tables, if available. Otherwise, the
73
 * scripts will be executed again in order to recreate them.
74
 *
75
 * @version $Id: HSQLDataSource.java 14624 2005-06-29 02:19:08Z desruisseaux $
76
 * @author Martin Desruisseaux
77
 * @author Didier Richard
78
 *
79
 * @since 2.2
80
 */
81
public class HSQLDataSource extends jdbcDataSource implements DataSource {
82

    
83
    // 20090518 cmartinez: Use a different tmp dir for each geotools instance
84
    private static File tmpDir = null;
85

    
86
    private static final Logger logger = LoggerFactory.getLogger(HSQLDataSource.class);
87

    
88
    /**
89
     * Creates a new instance of this data source
90
     */
91
    public HSQLDataSource() {
92
        File directory = getGtTmpDir();
93
        if ( directory.isDirectory() || directory.mkdir() ) {
94
            directory = new File(directory, "Cached databases");
95
            if ( directory.isDirectory() || directory.mkdir() ) {
96
                /*
97
                 * Constructs the full path to the HSQL database. Note: we do not use
98
                 * File.toURI() because HSQL doesn't seem to expect an encoded URL
99
                 * (e.g. "%20" instead of spaces).
100
                 */
101
                final StringBuffer url = new StringBuffer("jdbc:hsqldb:file:");
102
                final String path = directory.getAbsolutePath().replace(File.separatorChar, '/');
103
                if ( path.length() == 0 || path.charAt(0) != '/' ) {
104
                    url.append('/');
105
                }
106
                url.append(path);
107
                if ( url.charAt(url.length() - 1) != '/' ) {
108
                    url.append('/');
109
                }
110
                url.append("EPSG");
111
                setDatabase(url.toString());
112
            }
113
            /*
114
             * If the temporary directory do not exists or can't be created,
115
             * lets the 'database' attribute unset. If the user do not set it
116
             * explicitly (for example through JNDI), an exception will be thrown
117
             * when 'getConnection()' will be invoked.
118
             */
119
        }
120
        setUser("SA"); // System administrator. No password.
121
    }
122

    
123
    private File getGtTmpDir() {
124
        if ( tmpDir == null ) {
125
            tmpDir = new File(CrsFactory.getDataBaseFolder(), "temp-" + getProcessId());
126
            Runtime.getRuntime().addShutdownHook(new cleanOnShutdown(this));
127
        }
128
        return tmpDir;
129
    }
130

    
131
    private static String getProcessId() {
132
        String fallback = "time" + System.currentTimeMillis();
133

    
134
        // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
135
        final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
136
        final int index = jvmName.indexOf('@');
137
        if ( index < 1 ) {
138
            // part before '@' empty (index = 0) / '@' not found (index = -1)
139
            return fallback;
140
        }
141
        try {
142
            return "pid" + Long.toString(Long.parseLong(jvmName.substring(0, index)));
143
        } catch (NumberFormatException e) {
144
            // ignore
145
        }
146
        return fallback;
147
    }
148

    
149
    public static class cleanOnShutdown extends Thread {
150

    
151
        private HSQLDataSource ds;
152

    
153
        public cleanOnShutdown(HSQLDataSource ds) {
154
            this.ds = ds;
155
        }
156

    
157
        public void run() {
158
            this.shutdown();
159
            this.delete();
160
        }
161

    
162
        private void delete() {
163
            File folder = HSQLDataSource.tmpDir;
164
            if ( folder == null ) {
165
                return;
166
            }
167
            try {
168
                logger.info("Deleting CRS temporary database folder (" + folder.getAbsolutePath() + ").");
169
                FileUtils.deleteDirectory(folder);
170
            } catch (IOException e) {
171
                logger.error("Can't delete CRS temporary database folder (" + folder + ".", e);
172
            }
173
        }
174

    
175
        private void shutdown() {
176
            try {
177
                logger.info("Shutdown the data-base.");
178
                Connection connection = this.ds.getConnection(false);
179
                final Statement statement = connection.createStatement();
180
                statement.execute("SHUTDOWN");
181
                statement.close();
182
                connection.close();
183
            } catch (Exception ex) {
184
                logger.error("Can't shutdown the database.", ex);
185
            }
186
        }
187
    }
188

    
189
    /**
190
     * Returns the priority for this data source. This priority is set to a
191
     * lower value than the {@linkplain AccessDataSource}'s one in order to give
192
     * the priority to the Access-backed database, if presents. Priorities are
193
     * set that way because:
194
     * <ul>
195
     * <li>The MS-Access format is the primary EPSG database format.</li>
196
     * <li>If a user downloads the MS-Access database himself, he probably wants
197
     * to use it.</li>
198
     * </ul>
199
     */
200
    public int getPriority() {
201
        return NORMAL_PRIORITY - 30;
202
    }
203

    
204
    /**
205
     * Returns {@code true} if the database contains data. This method returns
206
     * {@code false} if an empty EPSG database has been automatically created by
207
     * HSQL and not yet populated.
208
     */
209
    private static boolean dataExists(final Connection connection) throws SQLException {
210
        final ResultSet tables = connection.getMetaData().getTables(
211
                null, null, "EPSG_%", new String[]{"TABLE"});
212
        final boolean exists = tables.next();
213
        tables.close();
214
        return exists;
215
    }
216

    
217
    /**
218
     * Opens a connection to the database. If the cached tables are not
219
     * available, they will be created now from the SQL scripts bundled in this
220
     * plugin.
221
     */
222
    public Connection getConnection() throws SQLException {
223
        return getConnection(true);
224
    }
225

    
226
    public synchronized Connection getConnection(boolean initialize) throws SQLException {
227
        final String database = getDatabase();
228
        if ( StringUtils.isEmpty(database) ) {
229
            /*
230
             * The 'database' attribute is unset if the constructor has been unable
231
             * to locate the temporary directory, or to create the subdirectory.
232
             */
233
            throw new SQLException("Can't write to the temporary directory.");
234
        }
235
        Connection connection = super.getConnection();
236
        if ( initialize ) {
237
            initializeDatabase();
238
        }
239
        return connection;
240
    }
241

    
242
    public synchronized void initializeDatabase() throws SQLException {
243
        final Connection connection = super.getConnection();
244
        if ( dataExists(connection) ) {
245
            // Database already initialized
246
            return;
247
        }
248
        initializeDatabase2(connection);
249
    }
250
    
251
    private void initializeDatabase2(Connection connection) throws SQLException {
252
        /*
253
         * HSQL has created automatically an empty database. We need to populate it.
254
         * Executes the SQL scripts bundled in the JAR. In theory, each line contains
255
         * a full SQL statement. For this plugin however, we have compressed "INSERT
256
         * INTO" statements using Compactor class in this package.
257
         */
258
        Date t1 = new Date();
259
        logger.info("Creating temporary cached EPSG database in '"
260
                + getGtTmpDir().getAbsolutePath()
261
                + "' from '"
262
                + CrsFactory.getEpsgDatabaseFile()
263
                + "'."
264
        );
265
        final Statement statement = connection.createStatement();
266
        SimpleTaskStatus status = ToolsLocator.getTaskStatusManager().createDefaultSimpleTaskStatus("Creating EPSG DB");
267
        try {
268
            int lineCounter = 0;
269
            File f = CrsFactory.getEpsgDatabaseFile();
270
            status.setAutoremove(true);
271
            status.setRangeOfValues(0, f.length());
272
            FileInputStream sqlInputStream = new FileInputStream(f);
273
            FileChannel chanel = sqlInputStream.getChannel();
274
            final BufferedReader in = new BufferedReader(new InputStreamReader(
275
                    sqlInputStream, "ISO-8859-1"));
276
            StringBuffer insertStatement = null;
277
            String line;
278
            while ( (line = in.readLine()) != null ) {
279
                status.setCurValue(chanel.position());
280
                line = line.trim();
281
                if( line.startsWith("-- " ) ) {
282
                    continue;
283
                }
284
                final int length = line.length();
285
                if ( length != 0 ) {
286
                    if ( line.startsWith("INSERT INTO") ) {
287
                        /*
288
                         * We are about to insert many rows into a single table.
289
                         * The row values appear in next lines; the current line
290
                         * should stop right after the VALUES keyword.
291
                         */
292
                        insertStatement = new StringBuffer(line);
293
                        continue;
294
                    }
295
                    if ( insertStatement != null ) {
296
                        /*
297
                         * We are about to insert a row. Prepend the "INSERT INTO"
298
                         * statement and check if we will have more rows to insert
299
                         * after this one.
300
                         */
301
                        final int values = insertStatement.length();
302
                        insertStatement.append(line);
303
                        final boolean hasMore = (line.charAt(length - 1) == ',');
304
                        if ( hasMore ) {
305
                            insertStatement.setLength(insertStatement.length() - 1);
306
                        }
307
                        line = insertStatement.toString();
308
                        insertStatement.setLength(values);
309
                        if ( !hasMore ) {
310
                            insertStatement = null;
311
                        }
312
                    }
313
                    statement.execute(line);
314
                    lineCounter++;
315
                    if( (lineCounter % 100) == 0) {
316
                        Thread.yield();
317
                    }
318
                }
319
            }
320
            in.close();
321
            Date t2 = new Date();
322
            logger.info("Created temporary EPSG database in "+ (t2.getTime()-t1.getTime())+ "ms.");
323
        } catch (IOException exception) {
324
            throw new SQLException("Can't read the SQL script.", exception);
325
        } finally {
326
            status.terminate();
327
            statement.close();
328
            connection.close();
329
        }
330
    }
331
    
332
    /**
333
     * Open a connection and creates an
334
     * {@linkplain FactoryUsingSQL EPSG factory} for it.
335
     *
336
     * @param factories The low-level factories to use for CRS creation.
337
     * @return The EPSG factory using HSQLDB SQL syntax.
338
     * @throws SQLException if connection to the database failed.
339
     */
340
    public AbstractAuthorityFactory createFactory(final FactoryGroup factories) throws SQLException {
341
        return new FactoryUsingHSQL(factories, getConnection());
342
    }
343

    
344
    public java.util.logging.Logger getParentLogger()
345
            throws SQLFeatureNotSupportedException {
346
        // TODO Auto-generated method stub
347
        return null;
348
    }
349

    
350
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
351
        // TODO Auto-generated method stub
352
        return false;
353
    }
354

    
355
    public <T> T unwrap(Class<T> iface) throws SQLException {
356
        // TODO Auto-generated method stub
357
        return null;
358
    }
359
}