Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.db / org.gvsig.fmap.dal.db.h2 / src / main / java / org / gvsig / fmap / dal / store / h2 / H2SpatialHelper.java @ 45154

History | View | Annotate | Download (19.9 KB)

1
/* gvSIG. Geographic Information System of the Valencian Government
2
 *
3
 * Copyright (C) 2007-2020 Infrastructures and Transports Department
4
 * of the Valencian Government (CIT)
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 */
22
package org.gvsig.fmap.dal.store.h2;
23

    
24
import static java.awt.SystemColor.info;
25
import org.gvsig.fmap.dal.store.jdbc2.spi.ConnectionProvider;
26
import java.io.File;
27
import java.sql.Connection;
28
import java.sql.SQLException;
29
import java.text.MessageFormat;
30
import org.apache.commons.dbcp.BasicDataSource;
31
import org.apache.commons.io.FilenameUtils;
32
import org.apache.commons.lang3.StringUtils;
33
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
34
import org.gvsig.fmap.dal.exception.InitializeException;
35
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
36
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
37
import org.gvsig.fmap.dal.store.h2.operations.H2SpatialOperationsFactory;
38
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
39
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
40
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
41
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
42
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCDriverClassNotFoundException;
43
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
44
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
45
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
46
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCHelperBase;
47
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
48
import org.gvsig.fmap.dal.store.jdbc2.spi.SRSSolverBase;
49
import org.gvsig.fmap.dal.store.jdbc2.spi.SRSSolverDumb;
50
import org.h2.Driver;
51
import org.h2.tools.Server;
52
import org.h2gis.ext.H2GISExtension;
53
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
55

    
56
@SuppressWarnings("UseSpecificCatch")
57
public class H2SpatialHelper extends JDBCHelperBase {
58

    
59
    static final Logger LOGGER = LoggerFactory.getLogger(H2SpatialHelper.class);
60

    
61
    public static final String H2SPATIAL_JDBC_DRIVER = "org.h2.Driver";
62
    
63
    private static File getLocalFile(H2SpatialConnectionParameters params) {
64
        String host = params.getHost();
65
        if( !StringUtils.isEmpty(host) ) {
66
          host = host.toLowerCase().trim();
67
          if( !(host.equals("localhost") || host.equals("127.0.0.1")) ) {
68
            return null;
69
          }
70
        }
71
        File f = params.getFile();
72
        if( f == null ) {
73
          return null;
74
        }
75
        String pathname = f.getAbsolutePath().replace("\\","/");
76
        if( !pathname.endsWith(".mv.db")  ) {
77
          pathname += ".mv.db";
78
        }      
79
        
80
        return new File(pathname);
81
    }
82
    
83
    public static String getConnectionURL(H2SpatialConnectionParameters params) {
84
        String connectionURL;
85
        String dbfilename = params.getFile().getAbsolutePath().replace("\\","/");
86
        if( dbfilename!=null && dbfilename.endsWith(".mv.db") ) {
87
            dbfilename = dbfilename.substring(0, dbfilename.length()-6);
88
        }
89
        StringBuilder commonParameters = new StringBuilder();
90
        commonParameters.append(";MODE=PostgreSQL");
91
        commonParameters.append(";SCHEMA=PUBLIC");
92
        commonParameters.append(";ALLOW_LITERALS=ALL");
93
        if( StringUtils.isEmpty(params.getHost()) ) {
94
            // Asumimos que es una conexion directa sobre el filesystem
95
            if( StringUtils.equalsIgnoreCase(FilenameUtils.getExtension(params.getFile().getName()),"zip") ) {
96
                connectionURL =  MessageFormat.format(
97
                    "jdbc:h2:zip:{0}!/{1}"+commonParameters.toString(),
98
                    dbfilename,
99
                    params.getDBName()
100
                );
101
            } else {
102
                connectionURL =  MessageFormat.format(
103
                    "jdbc:h2:file:{0}"+commonParameters.toString(),
104
                    dbfilename
105
                );
106
            }
107
        } else if( params.getPort() == null ) {
108
            connectionURL =  MessageFormat.format(
109
                "jdbc:h2:tcp://{0}/{1}"+commonParameters.toString(),
110
                params.getHost(),
111
                dbfilename
112
            );            
113
        } else {
114
            connectionURL =  MessageFormat.format("jdbc:h2:tcp://{0}:{1,number,#######}/{2}"+commonParameters.toString(),
115
                params.getHost(),
116
                (int) params.getPort(),
117
                dbfilename
118
            );
119
        }
120
        LOGGER.debug("connectionURL: {}", connectionURL);
121
        return connectionURL;
122
    }
123

    
124
    public static class ConnectionProviderImpl implements ConnectionProvider {
125

    
126
        private static boolean needRegisterDriver = true;
127

    
128
        private BasicDataSource dataSource = null;
129

    
130
        private final H2SpatialConnectionParameters connectionParameters;
131
        
132
        private static Server server = null;
133
        private static boolean startServer = true;
134

    
135
        public ConnectionProviderImpl(H2SpatialConnectionParameters connectionParameters) {
136
            this.connectionParameters = connectionParameters;
137
        }
138

    
139
        @Override
140
        public String getStatus() {
141
            StringBuilder builder = new StringBuilder();
142
            builder.append("Pool: ");
143
            builder.append(JDBCUtils.getHexId(dataSource));
144
            builder.append(" Actives: ");
145
            builder.append(dataSource.getNumActive());
146
            builder.append("/");
147
            builder.append(dataSource.getMaxActive());
148
            builder.append(" idle: ");
149
            builder.append(dataSource.getNumIdle());
150
            builder.append("/");
151
            builder.append(dataSource.getMinIdle());
152
            builder.append(":");
153
            builder.append(dataSource.getMaxIdle());
154
            return builder.toString();
155
        }
156
        
157
        @SuppressWarnings("ConvertToTryWithResources")
158
        public void shutdown() {
159
            LOGGER.info("Shutdown H2 connection.");
160
            try {
161
                Connection conn = this.getConnection();
162
                conn.createStatement().execute("SHUTDOWN");
163
                conn.close();
164
            } catch (Throwable th) {
165
                LOGGER.warn("Problems shutdown the database.", th);
166
            }
167
            try {
168
                if( dataSource!=null ) {
169
                    LOGGER.info("Clossing connection pool.");
170
                    LOGGER.info(this.getStatus());
171
                    dataSource.close();
172
                    LOGGER.info("Connection pool closed.");
173
                    LOGGER.info(this.getStatus());
174
                }
175
            } catch (Throwable th) {
176
                LOGGER.warn("Problems closing connections pool.", th);
177
            }
178
        }
179
        
180
        public static void stopServer() {
181
            if (server == null) {
182
                LOGGER.info("The H2 server is already stopped.");
183
            } else {
184
                LOGGER.info("Stopping the H2 server.");
185
                LOGGER.info("  port  :" + server.getPort());
186
                LOGGER.info("  URL   :" + server.getURL());
187
                LOGGER.info("  shutdown server...");
188
                try {
189
                    server.shutdown();
190
                } catch (Throwable th) {
191
                    LOGGER.warn("Problems shutdown the H2 server.", th);
192
                }
193
                LOGGER.info("  Stoping server...");
194
                try {
195
                    server.stop();
196
                } catch (Throwable th) {
197
                    LOGGER.warn("Problems stopping the H2 server.", th);
198
                }
199
                LOGGER.info("  status:" + server.getStatus());
200
                server = null;
201
                LOGGER.info("H2 Server stopped");
202
            }
203
            startServer = true;
204
        }
205
        
206
        private void startServer() {
207
        
208
            if( startServer && server == null ) {
209
                String port = "9123";
210
                try {
211
                    Server theServer;
212
                    if( this.connectionParameters.getServerPort()>0 ) {
213
                        port = String.valueOf(this.connectionParameters.getServerPort());
214
                    }
215
                    if( this.connectionParameters.getServerAllowOthers() ) {
216
                        theServer = Server.createTcpServer("-tcpPort", port, "-ifExists", "-tcpAllowOthers");
217
                    } else {
218
                        theServer = Server.createTcpServer("-tcpPort", port, "-ifExists");
219
                    }
220
                    theServer.start();
221
                    server = theServer;
222
                    LOGGER.info("H2 Server started" );
223
                    LOGGER.info("  port  :"+ server.getPort());
224
                    LOGGER.info("  URL   :"+ server.getURL());
225
                    LOGGER.info("  status:"+ server.getStatus());
226
                    Runtime.getRuntime().addShutdownHook(new Thread() {
227
                        @Override
228
                        public void run() {
229
                            stopServer();
230
                        }
231
                    });
232
                } catch (SQLException ex) {
233
                    LOGGER.warn("H2 Server not started",ex);
234
                }
235
                // Tanto si consigue lanzar el server como si no, no lo vuelve a intentar
236
                startServer = false;
237
            }
238

    
239
        }
240

    
241
        @Override
242
        public String toString() {
243
            StringBuilder builder = new StringBuilder();
244
            builder.append(" url=").append(connectionParameters.getUrl());
245
            builder.append(" driver name=").append(connectionParameters.getJDBCDriverClassName());
246
            builder.append(" user=").append(connectionParameters.getUser());
247
            return builder.toString();
248
        }
249
        
250
        @Override
251
        public synchronized Connection getConnection() throws SQLException {
252
            if (this.dataSource == null) {
253
                this.dataSource = this.createDataSource();               
254
            }
255
            Connection conn;
256
            try {
257
                conn = this.dataSource.getConnection();
258
            } catch(Throwable th) {
259
                LOGGER.warn("Can't create connection to '"+this.dataSource.getUrl()+"'. "+this.getStatus());
260
                LOGGER.warn("Can't create connection to '"+this.dataSource.getUrl()+"'.",th);
261
                throw th;
262
            }
263
            try {
264
                conn.createStatement().execute("SELECT TOP 1 SRID FROM SPATIAL_REF_SYS");
265
            } catch(SQLException ex) {
266
                H2GISExtension.load(conn);
267
            }
268
            try {
269
                conn.createStatement().execute("CREATE SCHEMA IF NOT EXISTS PUBLIC;SET SCHEMA PUBLIC");
270
            } catch(SQLException ex) {
271
                LOGGER.trace("Can't create schema public.",ex);
272
                // Ignore this error.
273
            }
274
            return conn;
275
        }
276

    
277
        private BasicDataSource createDataSource() throws SQLException {
278
            if (!this.isRegistered()) {
279
                this.registerDriver();
280
            }
281
            startServer();
282
            H2SpatialConnectionParameters params = connectionParameters;
283

    
284
            BasicDataSource ds = new BasicDataSource();
285
            ds.setDriverClassName(params.getJDBCDriverClassName());
286
            if( !StringUtils.isEmpty(params.getUser()) ) {
287
                ds.setUsername(params.getUser());
288
            }
289
            if( !StringUtils.isEmpty(params.getPassword()) ) {
290
                ds.setPassword(params.getPassword());
291
            }
292
            ds.setUrl(params.getUrl());
293

    
294
            ds.setMaxWait(60L * 1000);
295
            
296
            //
297
            // Ajustamos el pool para que las conexiones se cierren a los
298
            // 10 segundos, asi tratamos de que al salir de gvSIG no queden
299
            // conexiones abiertas con la BBDD y pueda quedar corrupta esta.
300
            // Hay que tener en cuenta que es una BBDD embebida, y mientras
301
            // hayan conexiones abiertas pueden quedar cosas por bajar a disco.
302
            //
303
            int sidle = this.connectionParameters.getMaxSecondsIdle();
304
            if( sidle < 0 ) {
305
                ds.setTimeBetweenEvictionRunsMillis(-1);
306
                ds.setMinEvictableIdleTimeMillis(30*1000);
307
            } else {
308
                // Revisamos las conexiones inactivas cada 10 segundos
309
                ds.setTimeBetweenEvictionRunsMillis(sidle*1000);
310
                // Eliminadmos las conexiones que lleven inactivas mas de 10 segundos.
311
                ds.setMinEvictableIdleTimeMillis(sidle*1000);
312
            }
313
            
314
            // Ajustamos el numero minimo de conexiones a 0 para permitir
315
            // que se lleguen a cerrar todas las conexiones del pool.
316
            ds.setMinIdle(0);
317
            // dejaremos el MaxIdle a 20, no parece importante. .
318
            ds.setMaxIdle(20);
319
            
320
            return ds;
321
        }
322

    
323
        private boolean isRegistered() {
324
            return needRegisterDriver;
325
        }
326

    
327
        @Override
328
        public void registerDriver() throws SQLException {
329
            String className = this.connectionParameters.getJDBCDriverClassName();
330
            if (className == null) {
331
                return;
332
            }
333
            try {
334
                Class theClass = Class.forName(className);
335
                if (theClass == null) {
336
                    throw new JDBCDriverClassNotFoundException(H2SpatialLibrary.NAME, className);
337
                }
338
            } catch (Exception e) {
339
                throw new SQLException("Can't register JDBC driver '" + className + "'.", e);
340
            }
341
            needRegisterDriver = false;
342
        }
343

    
344
    }
345

    
346
    private ConnectionProvider connectionProvider = null;
347
 
348
    /**
349
     * Constructor for use only for testing purposes.
350
     * 
351
     * @param connectionParameters
352
     * @param connectionProvider
353
     */
354
    public H2SpatialHelper(JDBCConnectionParameters connectionParameters, ConnectionProvider connectionProvider) { 
355
        super(connectionParameters);
356
        this.srssolver = new SRSSolverDumb(this);
357
        this.connectionProvider = connectionProvider;
358
    }
359
  
360
    public H2SpatialHelper(JDBCConnectionParameters connectionParameters) {
361
        super(connectionParameters);
362
        this.srssolver = new SRSSolverBase(this);
363
    }
364

    
365
    
366
    public void  shutdown() {
367
        try {
368
            if( this.connectionProvider!=null ) {
369
                ((ConnectionProviderImpl) this.connectionProvider).shutdown();
370
                this.connectionProvider = null;
371
            }
372
            ConnectionProviderImpl.stopServer();
373
        } catch (Throwable ex) {
374
            LOGGER.warn("Problems shutdown H2", ex);
375
        }
376
    }
377

    
378
    private void logConnectionStatus(String msg, Connection conn) {
379
        ConnectionProvider cp = this.getConnectionProvider();
380
        StringBuilder builder = new StringBuilder();
381
        builder.append(msg);
382
        if( conn == null ) {
383
            builder.append(": connection null");
384
        } else {
385
            Boolean closed = null;
386
            try {
387
                closed = conn.isClosed();
388
            } catch(Throwable th) {
389
            }
390
            builder.append(": connection ");
391
            builder.append(JDBCUtils.getConnId(conn));
392
            if( closed ) {
393
                builder.append(" (c)");
394
            }
395
            builder.append(" ");
396
        }
397
        builder.append(cp.getStatus());
398
        LOGGER.info(builder.toString());
399
    }
400
        
401
    private ConnectionProvider getConnectionProvider() {
402
        if (this.connectionProvider == null) {
403
          H2SpatialConnectionParameters connectionParameters = this.getConnectionParameters();
404
          if( connectionParameters==null ) {
405
            return null; // Testing mode?
406
          }
407
          this.connectionProvider = new ConnectionProviderImpl(connectionParameters);
408
        }
409
        return this.connectionProvider;
410
    }
411
    
412
    @Override
413
    public synchronized Connection  getConnection() throws AccessResourceException {
414
        try {
415
            if (this.connectionProvider == null) {
416
              H2SpatialConnectionParameters connectionParameters = this.getConnectionParameters();
417
              if( connectionParameters==null ) {
418
                return null; // Testing mode?
419
              }
420
              this.connectionProvider = new ConnectionProviderImpl(connectionParameters);
421
            }
422
            Connection connection = this.connectionProvider.getConnection();
423
            if( LOGGER.isDebugEnabled() ) {
424
                LOGGER.debug("["+JDBCUtils.getConnId(connection)+"] getConnection "+connectionProvider.getStatus()+" "+ connectionProvider.toString());
425
            }
426
            return connection;
427
        } catch (SQLException ex) {
428
            throw new AccessResourceException(H2SpatialLibrary.NAME, ex);
429
        }
430
    }
431

    
432
    @Override
433
    public void closeConnection(Connection connection) {
434
      if( connection!=null ) { // In test ???
435
        LOGGER.debug("["+JDBCUtils.getConnId(connection)+"] closeConnection "+ this.connectionProvider.getStatus());
436
      }
437
      super.closeConnection(connection);
438
    }
439
    
440
    @Override
441
    public H2SpatialConnectionParameters getConnectionParameters() {
442
        return (H2SpatialConnectionParameters) super.getConnectionParameters();
443
    }
444
    
445
    @Override
446
    public String getConnectionURL() {
447
        return getConnectionURL(this.getConnectionParameters());
448
    }
449

    
450
    @Override
451
    protected String getResourceType() {
452
        return H2SpatialLibrary.NAME;
453
    }
454

    
455
    @Override
456
    public String getProviderName() {
457
        return H2SpatialLibrary.NAME;
458
    }
459

    
460
    @Override
461
    public JDBCSQLBuilderBase createSQLBuilder() {
462
        return new H2SpatialSQLBuilder(this);
463
    }
464
    
465
    @Override
466
    public OperationsFactory getOperations() {
467
        if (this.operationsFactory == null) {
468
            this.operationsFactory = new H2SpatialOperationsFactory(this);
469
        }
470
        return operationsFactory;
471
    }
472

    
473
    @Override
474
    public GeometrySupportType getGeometrySupportType() {
475
        return GeometrySupportType.WKB;
476
    }
477

    
478
    @Override
479
    public boolean hasSpatialFunctions() {
480
        return true;
481
    }
482

    
483
    @Override
484
    public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
485
        return true;
486
    }
487

    
488
    @Override
489
    public String getQuoteForIdentifiers() {
490
        return "\"";
491
    }
492

    
493
    @Override
494
    public boolean allowAutomaticValues() {
495
        return true;
496
    }
497

    
498
    @Override
499
    public boolean supportOffsetInSelect() {
500
        return true;
501
    }
502

    
503
    @Override
504
    public String getQuoteForStrings() {
505
        return "'";
506
    }
507

    
508
    @Override
509
    public String getSourceId(JDBCStoreParameters parameters) {
510
        return parameters.getDBName() + "." + 
511
               parameters.getSchema()+ "." + 
512
               parameters.getTable();
513
    }
514

    
515
    @Override
516
    public JDBCNewStoreParameters createNewStoreParameters() {
517
        return new H2SpatialNewStoreParameters();
518
    }
519

    
520
    @Override
521
    public JDBCStoreParameters createOpenStoreParameters() {
522
        return new H2SpatialStoreParameters();
523
    }
524

    
525
    @Override
526
    public JDBCServerExplorerParameters createServerExplorerParameters() {
527
        return new H2SpatialExplorerParameters();
528
    }
529

    
530
    @Override
531
    public JDBCServerExplorer createServerExplorer(
532
            JDBCServerExplorerParameters parameters, 
533
            DataServerExplorerProviderServices providerServices
534
        ) throws InitializeException {
535
        
536
        JDBCServerExplorer explorer = new H2SpatialExplorer(
537
                parameters, 
538
                providerServices, 
539
                this
540
        );
541
        this.initialize(explorer, parameters, null);
542
        return explorer;
543
    }
544
    
545
}