Statistics
| Revision:

root / trunk / org.gvsig.postgresql / org.gvsig.postgresql.provider / src / main / java / org / gvsig / postgresql / dal / PostgreSQLHelper.java @ 1402

History | View | Annotate | Download (17.7 KB)

1

    
2
package org.gvsig.postgresql.dal;
3

    
4
import java.sql.Blob;
5
import java.sql.Clob;
6
import java.sql.Connection;
7
import java.sql.DriverManager;
8
import java.sql.ResultSet;
9
import java.sql.SQLException;
10
import java.util.HashMap;
11
import java.util.Map;
12
import org.apache.commons.dbcp2.BasicDataSource;
13
import org.apache.commons.io.IOUtils;
14
import org.apache.commons.lang3.ArrayUtils;
15
import org.apache.commons.lang3.BooleanUtils;
16
import org.apache.commons.lang3.StringUtils;
17
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
18
import org.gvsig.fmap.dal.DataTypes;
19
import org.gvsig.fmap.dal.exception.DataException;
20
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
21
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
22
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
23
import org.gvsig.fmap.dal.spi.DataTransactionServices;
24
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
25
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
26
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
27
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
28
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
29
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCDriverClassNotFoundException;
30
import org.gvsig.fmap.dal.store.jdbc2.JDBCConnection;
31
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
32
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
33
import org.gvsig.fmap.dal.store.jdbc2.spi.AbstractConnectionProvider;
34
import org.gvsig.fmap.dal.store.jdbc2.spi.ConnectionProvider;
35
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCConnectionBase;
36
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCHelperBase;
37
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
38
import org.gvsig.fmap.dal.store.jdbc2.spi.SRSSolverBase;
39
import org.gvsig.fmap.dal.store.jdbc2.spi.SRSSolverDumb;
40
import org.gvsig.fmap.geom.Geometry;
41
import org.gvsig.postgresql.dal.operations.PostgreSQLOperationsFactory;
42
import org.gvsig.tools.dispose.Disposable;
43
import org.gvsig.tools.dispose.DisposeUtils;
44
import org.gvsig.tools.exception.BaseException;
45
import org.postgresql.util.DriverInfo;
46

    
47
@SuppressWarnings("UseSpecificCatch")
48
public class PostgreSQLHelper extends JDBCHelperBase {
49

    
50
//    /* friend */ static final Logger LOGGER = LoggerFactory.getLogger(PostgreSQLHelper.class);
51

    
52
    public static final String POSTGRESQL_JDBC_DRIVER = "org.postgresql.Driver";
53
    
54
    public static final boolean USE_CONNECTIONS_POOL = true;
55
    
56
    public static String getConnectionURL(PostgreSQLConnectionParameters params) {
57
        return getConnectionURL(
58
            params.getHost(),
59
            params.getPort(),
60
            params.getDBName()
61
        );
62
    }
63
    
64
    public static String getConnectionURL(String host, Integer port, String db) {
65
        if( StringUtils.isEmpty(host) ) {
66
            throw new IllegalArgumentException("Parameter 'host' can't be null.");
67
        }
68
        String connectionURL = "jdbc:postgresql://" + host;
69
        if (port != null) {
70
            connectionURL = connectionURL + ":" + port;
71
        }
72
        connectionURL = connectionURL + "/" + db;
73
        LOGGER.debug("connectionURL: {}", connectionURL);
74
        return connectionURL;
75
    }
76

    
77
    private static class PostgreSQLConnectionProvider extends AbstractConnectionProvider implements Disposable, ConnectionProvider {
78

    
79
        private static boolean needRegisterDriver = true;
80

    
81
        private BasicDataSource dataSource = null;
82

    
83
        private PostgreSQLConnectionParameters connectionParameters;
84

    
85
        public PostgreSQLConnectionProvider(PostgreSQLConnectionParameters connectionParameters) {
86
            this.connectionParameters = connectionParameters;
87
            DisposeUtils.bind(this);
88
        }
89

    
90
        @Override
91
        public Connection getConnection() throws SQLException {
92
            
93
            Connection conn;
94
            if( USE_CONNECTIONS_POOL ) {
95
                if (this.dataSource == null) {
96
                    this.dataSource = this.createDataSource();               
97
                }
98
                if( LOGGER.isDebugEnabled() ) {
99
                    LOGGER.debug("getConnection:\n" + getStatusInformation());
100
                }
101
                try {
102
                    conn = this.dataSource.getConnection();
103
                } catch(Throwable ex) {
104
                    LOGGER.debug("Error getting connection from pool.",ex);
105
                    throw ex;
106
                }
107
                try {
108
                    conn.setNetworkTimeout(null, this.connectionParameters.getNetworkTimeout());
109
                } catch(Throwable ex) {
110
                    LOGGER.warn("Error setting the network timeout.",ex);
111
                }
112
                if( LOGGER.isDebugEnabled() ) {
113
                    LOGGER.debug("Created connection: {}\n  NumActive: {}\n  NumIdle: {}",
114
                        new Object[] {
115
                            conn.hashCode(), 
116
                            this.dataSource.getNumActive(),
117
                            this.dataSource.getNumIdle()
118
                        }
119
                    );
120
                }
121
            } else {
122
                try {
123
                    conn = DriverManager.getConnection(
124
                        connectionParameters.getUrl(), 
125
                        connectionParameters.getUser(), 
126
                        connectionParameters.getPassword()
127
                    );
128
                } catch(Throwable th) {
129
                    throw th;
130
                }
131
                if( LOGGER.isDebugEnabled() ) {
132
                    LOGGER.debug("Created not polled connection: {}",
133
                        new Object[] {
134
                            conn.hashCode()
135
                        }
136
                    );
137
                }
138
            }
139
            LOGGER.debug("PostgreSQL JDBC Driver: "+DriverInfo.DRIVER_VERSION);
140
            return conn;
141
        }
142
        
143
        public String getStatusInformation() {
144
            StringBuilder builder = new StringBuilder();
145
            if( this.dataSource==null ) {
146
                builder.append("Not poolled connection:\n");
147
                builder.append("  Connection URL: '").append(this.connectionParameters.getUrl()).append("'\n");
148
            } else {
149
                builder.append("BasicDataSource pool status:\n");
150
                builder.append("  Connection URL: '").append(this.dataSource.getUrl()).append("'\n");
151
                if( this.dataSource.getInitialSize()>0 ) {
152
                    builder.append("  InitialSize: ").append(this.dataSource.getInitialSize()).append(" (The initial number of connections that are created when the pool is started)\n");
153
                }
154
                if( this.dataSource.isPoolPreparedStatements() ) {
155
                    builder.append("  PoolPreparedStatements: ").append(this.dataSource.isPoolPreparedStatements()).append("\n");
156
                    builder.append("  MaxOpenPreparedStatements: ").append(this.dataSource.getMaxOpenPreparedStatements()).append(" (The maximum number of open statements that can be allocated from the statement pool at the same time, or non-positive for no limit)\n");
157
                }
158
                builder.append("  MaxTotal: ").append(this.dataSource.getMaxTotal()).append(" (The maximum number of active connections that can be allocated from this pool at the same time)\n");
159
                builder.append("  MaxIdle: ").append(this.dataSource.getMaxIdle()).append(" (The maximum number of connections that can remain idle in the pool)\n");
160
                builder.append("  NumActive:").append(this.dataSource.getNumActive()).append(" (the current number of active connections)\n");
161
                builder.append("  NumIdle:").append(this.dataSource.getNumIdle()).append(" (the current number of idle connections)\n");
162
            }
163
            return builder.toString();
164
        }
165

    
166
        private BasicDataSource createDataSource() throws SQLException {
167
            if (!this.isRegistered()) {
168
                this.registerDriver();
169
            }
170
            PostgreSQLConnectionParameters params = connectionParameters;
171

    
172
            BasicDataSource ds = new BasicDataSource();
173
            ds.setMaxIdle(params.getMaxIdle());
174
            ds.setDriverClassName(params.getJDBCDriverClassName());
175
            if( params.getUseSSL() ) {
176
                String s = BooleanUtils.toStringTrueFalse(params.getUseSSL());
177
                ds.addConnectionProperty("ssl", s );
178
            }
179
            if( !StringUtils.isEmpty(params.getUser()) ) {
180
                ds.setUsername(params.getUser());
181
            }
182
            if( !StringUtils.isEmpty(params.getPassword()) ) {
183
                ds.setPassword(params.getPassword());
184
            }
185
            ds.setUrl(params.getUrl());
186
            
187
            ds.setMinEvictableIdleTimeMillis(20*1000);
188
            ds.setTimeBetweenEvictionRunsMillis(20*1000);
189
            ds.setMinIdle(0);
190
            
191
            ds.setMaxWaitMillis(60L * 1000);
192
            return ds;
193
        }
194

    
195
        private boolean isRegistered() {
196
            return needRegisterDriver;
197
        }
198

    
199
        @Override
200
        public void registerDriver() throws SQLException {
201
            String className = this.connectionParameters.getJDBCDriverClassName();
202
            if (className == null) {
203
                return;
204
            }
205
            try {
206
                Class theClass = Class.forName(className);
207
                if (theClass == null) {
208
                    throw new JDBCDriverClassNotFoundException(PostgreSQLLibrary.NAME, className);
209
                }
210
            } catch (Exception e) {
211
                throw new SQLException("Can't register JDBC driver '" + className + "'.", e);
212
            }
213
            needRegisterDriver = false;
214
        }
215

    
216
        @Override
217
        public void dispose() {
218
            if(!DisposeUtils.release(this)){
219
                return;
220
            }
221
            if( this.dataSource!=null ) {
222
                try {
223
                    this.dataSource.close();
224
                } catch (SQLException ex) {
225
                    LOGGER.warn("Can't close BasicDataSource", ex);
226
                }
227
                this.dataSource = null;
228
            }
229
            this.connectionParameters = null;
230
        }
231
        
232
        @Override
233
        public boolean isDisposed() {
234
            return this.dataSource == null;
235
        }
236

    
237
        @Override
238
        public String getStatus() {
239
            if( dataSource==null ) {
240
                return "Not polled";
241
            }
242
            StringBuilder builder = new StringBuilder();
243
            builder.append("Pool: ");
244
            builder.append(JDBCUtils.getHexId(dataSource));
245
            builder.append(" Actives: ");
246
            builder.append(dataSource.getNumActive());
247
            builder.append("/");
248
            builder.append(dataSource.getMaxTotal()); //.getMaxActive());
249
            builder.append(" idle: ");
250
            builder.append(dataSource.getNumIdle());
251
            builder.append("/");
252
            builder.append(dataSource.getMinIdle());
253
            builder.append(":");
254
            builder.append(dataSource.getMaxIdle());
255
            return builder.toString();
256
        }
257

    
258
    }
259

    
260
    static private Map<String,ConnectionProvider> connectionProviders = new HashMap();
261
    private ConnectionProvider connectionProvider = null;
262
   
263
    /**
264
     * Constructor for use only for testing purposes.
265
     * 
266
     * @param connectionParameters
267
     * @param connectionProvider
268
     */
269
    public PostgreSQLHelper(PostgreSQLConnectionParameters connectionParameters, ConnectionProvider connectionProvider) { 
270
        super(connectionParameters);
271
        this.srssolver = new SRSSolverDumb(this);
272
        this.connectionProvider = connectionProvider;
273
    }
274
  
275

    
276

    
277
    public PostgreSQLHelper(JDBCConnectionParameters connectionParameters) {
278
        super(connectionParameters);
279
        this.srssolver = new SRSSolverBase(this);
280
    }
281

    
282
    @Override
283
    protected void doDispose() throws BaseException {
284
        if( this.connectionProvider!=null ) {
285
            this.connectionProvider.dispose();
286
            this.connectionProvider = null;
287
        }
288
        super.doDispose(); 
289
    }
290
    
291
    private ConnectionProvider getConnectionProvider(JDBCConnectionParameters connectionParameters) {
292
        if (this.connectionProvider == null) {
293
            if (this.getConnectionParameters() == null) {
294
                return null;
295
            }
296
            String key = this.getConnectionProviderKey(connectionParameters);
297
            this.connectionProvider = connectionProviders.get(key);
298
            if (this.connectionProvider == null || ((PostgreSQLConnectionProvider) this.connectionProvider).isDisposed()) {
299
                this.connectionProvider = new PostgreSQLConnectionProvider(this.getConnectionParameters());
300
                connectionProviders.put(key, this.connectionProvider);
301
            } else {
302
                DisposeUtils.bind(this.connectionProvider);
303
            }
304
        }
305
        return this.connectionProvider;
306
    }
307

    
308
    @Override
309
    public synchronized JDBCConnection  getConnection() throws AccessResourceException {
310
        try {
311
            PostgreSQLConnectionParameters connectionParameters = this.getConnectionParameters();
312
            JDBCConnection conn = (JDBCConnection) DataTransactionServices.getConnection(
313
                    this.getTransaction(), 
314
                    this.getConnectionProviderKey(connectionParameters)
315
            );
316
            if( conn != null ) {
317
                return conn;
318
            }
319
            JDBCConnection connection = new JDBCConnectionBase(
320
                    this.getTransaction(), 
321
                    this.getConnectionProvider(connectionParameters).getConnection(), 
322
                    this.getConnectionProviderKey(connectionParameters)
323
            );
324
            return connection;
325
        } catch (SQLException ex) {
326
            throw new AccessResourceException(PostgreSQLLibrary.NAME, ex);
327
        }
328
    }
329
    
330
    @Override
331
    public PostgreSQLConnectionParameters getConnectionParameters() {
332
        return (PostgreSQLConnectionParameters) super.getConnectionParameters();
333
    }
334
    
335
    @Override
336
    public String getConnectionURL() {
337
        return getConnectionURL(this.getConnectionParameters());
338
    }
339

    
340
    @Override
341
    protected String getResourceType() {
342
        return PostgreSQLLibrary.NAME;
343
    }
344

    
345
    @Override
346
    public String getProviderName() {
347
        return PostgreSQLLibrary.NAME;
348
    }
349

    
350
    @Override
351
    public JDBCSQLBuilderBase createSQLBuilder() {
352
        return new PostgreSQLBuilder(this);
353
    }
354
    
355
    @Override
356
    public OperationsFactory getOperations() {
357
        if (this.operationsFactory == null) {
358
            this.operationsFactory = new PostgreSQLOperationsFactory(this);
359
        }
360
        return operationsFactory;
361
    }
362

    
363
    @Override
364
    public GeometrySupportType getGeometrySupportType() {
365
        return GeometrySupportType.EWKB;
366
    }
367

    
368
    @Override
369
    public boolean hasSpatialFunctions() {
370
        return true;
371
    }
372

    
373
    @Override
374
    public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
375
        return true;
376
    }
377

    
378
    @Override
379
    public String getQuoteForIdentifiers() {
380
        return "\"";
381
    }
382

    
383
    @Override
384
    public boolean allowAutomaticValues() {
385
        return true;
386
    }
387

    
388
    @Override
389
    public boolean supportOffsetInSelect() {
390
        return true;
391
    }
392

    
393
    @Override
394
    public String getQuoteForStrings() {
395
        return "'";
396
    }
397
    
398
    @Override
399
    public JDBCNewStoreParameters createNewStoreParameters() {
400
        return new PostgreSQLNewStoreParameters();
401
    }
402

    
403
    @Override
404
    public JDBCStoreParameters createOpenStoreParameters() {
405
        return new PostgreSQLStoreParameters();
406
    }
407

    
408
    @Override
409
    public JDBCServerExplorerParameters createServerExplorerParameters() {
410
        return new PostgreSQLServerExplorerParameters();
411
    }
412

    
413
    @Override
414
    public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
415
        Object value;
416
        try {
417
            int rsIndex = 1;
418
            for (FeatureAttributeDescriptor column : columns) {
419
                switch (column.getType()) {
420
                    case DataTypes.GEOMETRY:
421
                        value = this.getGeometryFromColumn(rs, rsIndex++);
422
                        if (value != null){
423
                            ((Geometry)value).setProjection(column.getSRS());
424
                        }
425
                        break;
426
                    case DataTypes.BYTEARRAY:
427
                        value = rs.getBytes(rsIndex++);
428
                        break;
429
                    default:
430
                        value = rs.getObject(rsIndex++);
431
                        if (value instanceof Blob) {
432
                            Blob blob = (Blob) value;
433
                            value = blob.getBytes(1, (int) blob.length());
434
                            blob.free();
435
                        } else if (value instanceof Clob) {
436
                            Clob clob = (Clob) value;
437
                            value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
438
                            clob.free();
439
                        }
440
                }
441
                feature.set(column.getIndex(), value);
442
            }
443
            if (ArrayUtils.isNotEmpty(extraValueNames)) {
444
                feature.setExtraValueNames(extraValueNames);
445
                for (int index = 0; index < extraValueNames.length; index++) {
446
                    value = rs.getObject(rsIndex++);
447
                    if (value instanceof Blob) {
448
                        Blob blob = (Blob) value;
449
                        value = blob.getBytes(0, (int) blob.length());
450
                        blob.free();
451
                    }
452
                    feature.setExtraValue(index, value);
453
                }
454
            }
455
        } catch (Exception ex) {
456
            throw new JDBCCantFetchValueException(ex);
457
        }
458
    }
459

    
460
}