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 |
} |