OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.model;
15
 
16
import org.openconcerto.sql.Log;
17
import org.openconcerto.sql.State;
18
import org.openconcerto.sql.request.SQLCache;
174 ilm 19
import org.openconcerto.sql.utils.SQLUtils;
65 ilm 20
import org.openconcerto.utils.CompareUtils;
17 ilm 21
import org.openconcerto.utils.ExceptionHandler;
22
import org.openconcerto.utils.ExceptionUtils;
23
import org.openconcerto.utils.RTInterruptedException;
24
import org.openconcerto.utils.ThreadFactory;
174 ilm 25
import org.openconcerto.utils.Tuple2;
17 ilm 26
import org.openconcerto.utils.cache.CacheResult;
132 ilm 27
import org.openconcerto.utils.cache.ICacheSupport;
182 ilm 28
import org.openconcerto.utils.cc.ITransformerExn;
17 ilm 29
 
80 ilm 30
import java.beans.PropertyChangeEvent;
31
import java.beans.PropertyChangeListener;
17 ilm 32
import java.sql.Connection;
33
import java.sql.ResultSet;
34
import java.sql.ResultSetMetaData;
35
import java.sql.SQLException;
142 ilm 36
import java.sql.SQLNonTransientException;
37
import java.sql.SQLTransientException;
73 ilm 38
import java.sql.Savepoint;
17 ilm 39
import java.sql.Statement;
40
import java.util.Arrays;
41
import java.util.Collections;
42
import java.util.HashMap;
43
import java.util.HashSet;
44
import java.util.Hashtable;
45
import java.util.List;
46
import java.util.Map;
47
import java.util.NoSuchElementException;
48
import java.util.Set;
49
import java.util.WeakHashMap;
50
import java.util.concurrent.ExecutorService;
51
import java.util.concurrent.LinkedBlockingQueue;
52
import java.util.concurrent.ThreadPoolExecutor;
53
import java.util.concurrent.TimeUnit;
174 ilm 54
import java.util.concurrent.atomic.AtomicLong;
55
import java.util.concurrent.atomic.AtomicReference;
17 ilm 56
import java.util.logging.Level;
57
 
73 ilm 58
import org.apache.commons.dbcp.AbandonedConfig;
17 ilm 59
import org.apache.commons.dbcp.BasicDataSource;
73 ilm 60
import org.apache.commons.dbcp.ConnectionFactory;
61
import org.apache.commons.dbcp.PoolableConnection;
62
import org.apache.commons.dbcp.PoolableConnectionFactory;
63
import org.apache.commons.dbcp.PoolingConnection;
17 ilm 64
import org.apache.commons.dbcp.PoolingDataSource;
65
import org.apache.commons.dbcp.SQLNestedException;
66
import org.apache.commons.dbutils.BasicRowProcessor;
67
import org.apache.commons.dbutils.ResultSetHandler;
68
import org.apache.commons.dbutils.RowProcessor;
69
import org.apache.commons.dbutils.handlers.ArrayHandler;
70
import org.apache.commons.dbutils.handlers.ArrayListHandler;
71
import org.apache.commons.dbutils.handlers.MapHandler;
72
import org.apache.commons.dbutils.handlers.MapListHandler;
73
import org.apache.commons.dbutils.handlers.ScalarHandler;
73 ilm 74
import org.apache.commons.pool.KeyedObjectPool;
75
import org.apache.commons.pool.KeyedObjectPoolFactory;
76
import org.apache.commons.pool.ObjectPool;
17 ilm 77
import org.apache.commons.pool.impl.GenericObjectPool;
142 ilm 78
import org.h2.constant.ErrorCode;
17 ilm 79
import org.postgresql.util.GT;
80
import org.postgresql.util.PSQLException;
81
 
132 ilm 82
import net.jcip.annotations.GuardedBy;
83
import net.jcip.annotations.ThreadSafe;
84
 
17 ilm 85
/**
86
 * Une source de donnée SQL.
87
 *
88
 * @author ILM Informatique 10 juin 2004
89
 */
61 ilm 90
@ThreadSafe
17 ilm 91
public final class SQLDataSource extends BasicDataSource implements Cloneable {
92
 
93
    // MAYBE add a cache, but ATTN synchronized : one connection per thread, but only one shared DS
94
 
95
    /** A map of supported database systems and associated drivers. */
96
    static public final Map<SQLSystem, String> DRIVERS;
97
    static {
98
        DRIVERS = new HashMap<SQLSystem, String>();
99
        DRIVERS.put(SQLSystem.MYSQL, "com.mysql.jdbc.Driver");
100
        DRIVERS.put(SQLSystem.POSTGRESQL, "org.postgresql.Driver");
101
        DRIVERS.put(SQLSystem.DERBY, "org.apache.derby.jdbc.ClientDriver");
102
        DRIVERS.put(SQLSystem.H2, "org.h2.Driver");
103
        DRIVERS.put(SQLSystem.MSSQL, "com.microsoft.sqlserver.jdbc.SQLServerDriver");
104
    }
105
 
106
    // timeouts in seconds
107
    static public final int loginTimeOut = 15;
108
    static public final int socketTimeOut = 8 * 60;
109
 
80 ilm 110
    // in milliseconds
111
    static public int QUERY_TUNING = 0;
112
 
17 ilm 113
    static public interface IgnoringRowProcessor extends RowProcessor {
114
 
115
        @Override
116
        public Map<String, Object> toMap(ResultSet rs) throws SQLException;
117
 
118
        /**
119
         * Convert the passed result set to a map, ignoring some columns.
120
         *
121
         * @param rs the result set.
122
         * @param toIgnore which columns' label to ignore.
123
         * @return a map with all columns of <code>rs</code> except <code>toIgnore</code>.
124
         * @throws SQLException if an error occurs while reading <code>rs</code>.
125
         */
126
        public Map<String, Object> toMap(ResultSet rs, Set<String> toIgnore) throws SQLException;
127
    }
128
 
129
    // ignoring case-sensitive processor
130
    static private class IgnoringCSRowProcessor extends BasicRowProcessor implements IgnoringRowProcessor {
131
        @Override
132
        public Map<String, Object> toMap(ResultSet rs) throws SQLException {
133
            return toMap(rs, Collections.<String> emptySet());
134
        }
135
 
136
        // on ne veut pas de CaseInsensitiveMap
137
        @Override
138
        public Map<String, Object> toMap(ResultSet rs, Set<String> toIgnore) throws SQLException {
139
            final Map<String, Object> result = new HashMap<String, Object>();
140
            final ResultSetMetaData rsmd = rs.getMetaData();
141
            final int cols = rsmd.getColumnCount();
142
            for (int i = 1; i <= cols; i++) {
143
                // ATTN use label not base name (eg "des" in "SELECT DESIGNATION as des FROM ...")
144
                final String label = rsmd.getColumnLabel(i);
145
                if (!toIgnore.contains(label))
146
                    result.put(label, rs.getObject(i));
147
            }
148
            return result;
149
        }
150
    }
151
 
152
    static public final IgnoringRowProcessor ROW_PROC = new IgnoringCSRowProcessor();
153
    // all thread safe
154
    public static final ColumnListHandler COLUMN_LIST_HANDLER = new ColumnListHandler();
155
    public static final ArrayListHandler ARRAY_LIST_HANDLER = new ArrayListHandler();
142 ilm 156
    public static final ListListHandlerGeneric<Object> LIST_LIST_HANDLER = ListListHandlerGeneric.create(Object.class, null);
17 ilm 157
    public static final ArrayHandler ARRAY_HANDLER = new ArrayHandler();
158
    public static final ScalarHandler SCALAR_HANDLER = new ScalarHandler();
159
    public static final MapListHandler MAP_LIST_HANDLER = new MapListHandler(ROW_PROC);
160
    public static final MapHandler MAP_HANDLER = new MapHandler(ROW_PROC);
161
 
61 ilm 162
    // Cache, linked to cacheEnable and tables
163
    @GuardedBy("this")
164
    private SQLCache<List<?>, Object> cache;
165
    @GuardedBy("this")
17 ilm 166
    private boolean cacheEnabled;
80 ilm 167
    private final PropertyChangeListener descL;
17 ilm 168
    // tables that can be used in queries (and thus can impact the cache)
61 ilm 169
    @GuardedBy("this")
170
    private Set<SQLTable> tables;
17 ilm 171
 
172
    private static int count = 0; // compteur de requetes
173
 
80 ilm 174
    private final DBSystemRoot sysRoot;
93 ilm 175
    private ConnectionFactory connectionFactory;
61 ilm 176
    // no need to synchronize multiple call to this attribute since we only access the
177
    // Thread.currentThread() key
178
    @GuardedBy("handlers")
17 ilm 179
    private final Map<Thread, HandlersStack> handlers;
180
 
61 ilm 181
    @GuardedBy("this")
17 ilm 182
    private ExecutorService exec = null;
183
 
65 ilm 184
    private final Object setInitialShemaLock = new String("initialShemaWriteLock");
61 ilm 185
    // linked to initialSchema and uptodate
186
    @GuardedBy("this")
17 ilm 187
    private boolean initialShemaSet;
61 ilm 188
    @GuardedBy("this")
17 ilm 189
    private String initialShema;
61 ilm 190
    // which Connection have the right default schema
191
    @GuardedBy("this")
73 ilm 192
    private final Map<Connection, Object> schemaUptodate;
193
    // which Connection aren't invalidated
194
    @GuardedBy("this")
17 ilm 195
    private final Map<Connection, Object> uptodate;
196
 
61 ilm 197
    private volatile int retryWait;
63 ilm 198
    @GuardedBy("this")
199
    private boolean blockWhenExhausted;
67 ilm 200
    @GuardedBy("this")
201
    private long softMinEvictableIdleTimeMillis;
61 ilm 202
 
73 ilm 203
    @GuardedBy("this")
204
    private int txIsolation;
205
    @GuardedBy("this")
206
    private Integer dbTxIsolation;
207
    @GuardedBy("this")
208
    private boolean checkOnceDBTxIsolation;
209
 
142 ilm 210
    private final Object testLock = new String("testLock");
17 ilm 211
 
80 ilm 212
    public SQLDataSource(DBSystemRoot sysRoot, String base, String login, String pass) {
213
        this(sysRoot, sysRoot.getServer().getURL(base), login, pass, Collections.<SQLTable> emptySet());
17 ilm 214
    }
215
 
80 ilm 216
    private SQLDataSource(DBSystemRoot sysRoot, String url, String login, String pass, Set<SQLTable> tables) {
217
        this(sysRoot);
17 ilm 218
 
80 ilm 219
        final SQLSystem system = getSystem();
17 ilm 220
        if (!DRIVERS.containsKey(system))
221
            throw new IllegalArgumentException("unknown database system: " + system);
222
 
223
        this.setDriverClassName(DRIVERS.get(system));
224
        this.setUrl("jdbc:" + system.getJDBCName() + ":" + url);
225
 
226
        this.setUsername(login);
227
        this.setPassword(pass);
228
        this.setTables(tables);
229
 
80 ilm 230
        if (system == SQLSystem.MYSQL) {
17 ilm 231
            this.addConnectionProperty("transformedBitIsBoolean", "true");
73 ilm 232
            // by default allowMultiQueries, since it's more convenient (i.e. pass String around
233
            // instead of List<String>) and faster (less trips to the server, allow
234
            // SQLUtils.executeMultiple())
235
            this.addConnectionProperty("allowMultiQueries", "true");
83 ilm 236
        } else if (system == SQLSystem.MSSQL) {
237
            // Otherwise we get SQLState S0002 instead of 42S02 (needed at least in
238
            // SQLBase.getFwkMetadata())
239
            // http://msdn.microsoft.com/fr-fr/library/ms712451.aspx
240
            // http://technet.microsoft.com/en-us/library/ms378988(v=sql.105).aspx
241
            this.addConnectionProperty("xopenStates", "true");
242
            // see
243
            // https://connect.microsoft.com/SQLServer/feedback/details/295907/resultsetmetadata-gettablename-returns-null-or-inconsistent-results
244
            // http://social.msdn.microsoft.com/Forums/sqlserver/en-US/55e8cbb2-b11c-446e-93ab-dc30658caf99/resultsetmetadatagettablename-returns-instead-of-table-name?forum=sqldataaccess
245
            // 1. The statement that the resultset belongs to was created with
246
            // TYPE_SCROLL_INSENSITIVE or TYPE_SCROLL_SENSITIVE : The full table or view name will
247
            // be returned
248
            // 2. The statement that the resultset belongs to was created without specifying the
249
            // cursor type, or the cursor type is TYPE_FORWARD_ONLY : The full table or view name
250
            // will be returned if the column is a text, ntext, or image, else empty string.
251
            this.addConnectionProperty("selectMethod", "cursor");
17 ilm 252
        }
253
        this.setLoginTimeout(loginTimeOut);
254
        this.setSocketTimeout(socketTimeOut);
93 ilm 255
        this.setTCPKeepAlive(true);
142 ilm 256
        this.setRetryWait(7000);
17 ilm 257
        // ATTN DO NOT call execute() or any method that might create a connection
258
        // since at this point dsInit() has not been called and thus connection properties might be
259
        // missing (eg allowMultiQueries). And the faulty connection will stay in the pool.
260
    }
261
 
262
    @Override
263
    public final void setLoginTimeout(int timeout) {
80 ilm 264
        if (this.getSystem() == SQLSystem.MYSQL) {
17 ilm 265
            this.addConnectionProperty("connectTimeout", timeout + "000");
83 ilm 266
        } else if (this.getSystem() == SQLSystem.POSTGRESQL || this.getSystem() == SQLSystem.MSSQL) {
17 ilm 267
            this.addConnectionProperty("loginTimeout", timeout + "");
93 ilm 268
        } else {
269
            Log.get().warning("Ignoring login timeout for " + this);
17 ilm 270
        }
271
    }
272
 
273
    public final void setSocketTimeout(int timeout) {
80 ilm 274
        if (this.getSystem() == SQLSystem.MYSQL) {
17 ilm 275
            this.addConnectionProperty("socketTimeout", timeout + "000");
80 ilm 276
        } else if (this.getSystem() == SQLSystem.H2) {
142 ilm 277
            // org.h2.util.NetUtils.createSocket() doesn't use setSoTimeout(), so this is the next
278
            // best thing. But it isn't checked everywhere, see DbSettings.maxQueryTimeout
279
            this.addConnectionProperty("MAX_QUERY_TIMEOUT", timeout + "000");
80 ilm 280
        } else if (this.getSystem() == SQLSystem.POSTGRESQL) {
17 ilm 281
            this.addConnectionProperty("socketTimeout", timeout + "");
93 ilm 282
        } else {
283
            Log.get().log(getLogLevelForIgnoredTCPParam(), "Ignoring socket timeout for " + this);
17 ilm 284
        }
285
    }
286
 
93 ilm 287
    // if TCP isn't used or is used to connect to localhost, TCP should be quite robust
288
    private final Level getLogLevelForIgnoredTCPParam() {
182 ilm 289
        return this.sysRoot.getServer().isLocalhost() ? Level.CONFIG : Level.WARNING;
93 ilm 290
    }
291
 
292
    public final void setTCPKeepAlive(final boolean b) {
293
        if (this.getSystem() == SQLSystem.POSTGRESQL || this.getSystem() == SQLSystem.MYSQL) {
294
            this.addConnectionProperty("tcpKeepAlive", String.valueOf(b));
295
        } else {
296
            Log.get().log(getLogLevelForIgnoredTCPParam(), "Ignoring TCP keep alive for " + this);
297
        }
298
    }
299
 
300
    /**
301
     * Set the number of milliseconds to wait before retrying if a connection fails to establish or
302
     * if a query fails to execute.
303
     *
304
     * @param retryWait the number of milliseconds to wait, negative to disable retrying.
305
     */
17 ilm 306
    public final void setRetryWait(int retryWait) {
307
        this.retryWait = retryWait;
308
    }
309
 
61 ilm 310
    synchronized void setTables(Set<SQLTable> tables) {
17 ilm 311
        // don't change the cache if we're only adding tables
312
        final boolean update = this.cache == null || !tables.containsAll(this.tables);
61 ilm 313
        this.tables = Collections.unmodifiableSet(new HashSet<SQLTable>(tables));
17 ilm 314
        if (update)
315
            updateCache();
316
    }
317
 
142 ilm 318
    private synchronized Set<SQLTable> getTables() {
319
        return this.tables;
320
    }
321
 
61 ilm 322
    private synchronized void updateCache() {
17 ilm 323
        if (this.cache != null)
132 ilm 324
            this.cache.getSupp().die();
325
        this.cache = createCache(null);
73 ilm 326
        for (final HandlersStack s : this.handlers.values()) {
327
            s.updateCache();
328
        }
329
    }
330
 
132 ilm 331
    // the cache for committed data
332
    synchronized final SQLCache<List<?>, Object> getCommittedCache() {
333
        return this.cache;
334
    }
335
 
336
    final SQLCache<List<?>, Object> getCache() {
337
        // transactions are isolated from one another, so their caches should be too
338
        final HandlersStack stack = getHandlersStack();
339
        if (stack != null && stack.hasTransaction())
340
            return stack.getCache();
341
        else
342
            return this.getCommittedCache();
343
    }
344
 
345
    synchronized SQLCache<List<?>, Object> createCache(final TransactionPoint o) {
73 ilm 346
        final SQLCache<List<?>, Object> res;
132 ilm 347
        if (this.isCacheEnabled() && this.tables.size() > 0) {
73 ilm 348
            // the general cache should wait for transactions to end, but the cache of transactions
349
            // must not.
132 ilm 350
            final boolean committedCache = o == null;
351
            final Object desc = committedCache ? this : o;
352
            final ICacheSupport<SQLData> cacheSupp = committedCache ? null : this.cache.getSupp();
353
            res = new SQLCache<List<?>, Object>(cacheSupp, 30, 30, "results of " + desc.toString(), o) {
354
                @Override
355
                protected String getCacheSuppName(String cacheName) {
356
                    assert committedCache : "Creating extra ICacheSupport";
357
                    return SQLDataSource.this.toString();
358
                }
359
            };
360
        } else {
73 ilm 361
            res = null;
132 ilm 362
        }
73 ilm 363
        return res;
17 ilm 364
    }
365
 
366
    /**
367
     * Enable or disable the cache. ATTN if you enable the cache you must
368
     * {@link SQLTable#fire(SQLTableEvent) fire} table events, or use a class that does like
369
     * {@link SQLRowValues}.
370
     *
371
     * @param b <code>true</code> to enable the cache.
372
     */
61 ilm 373
    public final synchronized void setCacheEnabled(boolean b) {
17 ilm 374
        if (this.cacheEnabled != b) {
375
            this.cacheEnabled = b;
376
            updateCache();
377
        }
378
    }
379
 
73 ilm 380
    public final synchronized boolean isCacheEnabled() {
381
        return this.cacheEnabled;
382
    }
383
 
17 ilm 384
    /* pour le clonage */
80 ilm 385
    private SQLDataSource(DBSystemRoot sysRoot) {
386
        this.sysRoot = sysRoot;
17 ilm 387
        // on a besoin d'une implementation synchronisée
61 ilm 388
        this.handlers = new Hashtable<Thread, HandlersStack>();
17 ilm 389
        // weak, since this is only a hint to avoid initializing the connection
390
        // on each borrowal
73 ilm 391
        this.schemaUptodate = new WeakHashMap<Connection, Object>();
17 ilm 392
        this.uptodate = new WeakHashMap<Connection, Object>();
393
        this.initialShemaSet = false;
394
        this.initialShema = null;
395
 
93 ilm 396
        // used by #getNewConnection() when there's an exception and by #validateDBConnectivity()
17 ilm 397
        this.setValidationQuery("SELECT 1");
93 ilm 398
        // We must set a socket timeout high enough for large queries but the validation query
399
        // should return almost instantly. Not too low since overloaded links may have very high
400
        // latencies.
401
        this.setValidationQueryTimeout(6);
402
        // don't test on borrow, this would double the queries.
17 ilm 403
        this.setTestOnBorrow(false);
93 ilm 404
        // don't test on return, the connection was just used and this would double the queries.
405
        this.setTestOnReturn(false);
406
        // for now don't test on idle as our evictor is run quite often to promptly close unneeded
407
        // connections. If it is slowed down, we risk overloading the server or just hit its maximum
408
        // connection count. MAYBE enable when we upgrade to DBCP 2, since it depends on POOL 2
409
        // which has an EvictionPolicy, so for example we could test connections after a smaller
410
        // amount of time than minEvictableIdleTimeMillis.
411
        this.setTestWhileIdle(false);
17 ilm 412
 
413
        this.setInitialSize(3);
93 ilm 414
        // neither DOS the server...
415
        this.setBlockWhenExhausted(true);
416
        this.setMaxActive(12);
417
        // ... nor us
418
        this.setMaxWait(5000);
17 ilm 419
        // creating connections is quite costly so make sure we always have a couple free
420
        this.setMinIdle(2);
421
        // but not too much as it can lock out other users (the server has a max connection count)
93 ilm 422
        this.setMaxIdle(10);
67 ilm 423
        // check 5 connections every 4 seconds
424
        this.setTimeBetweenEvictionRunsMillis(4000);
425
        this.setNumTestsPerEvictionRun(5);
426
        // kill extra (above minIdle) connections after 40s
427
        this.setSoftMinEvictableIdleTimeMillis(TimeUnit.SECONDS.toMillis(40));
428
        // kill idle connections after 30 minutes (even if it means re-creating some new ones
93 ilm 429
        // immediately afterwards to ensure minIdle connections). For now it's a poor man's solution
430
        // for lacking testWhileIdle. After that time NAT tables expires, the path to the server
431
        // might be broken...
67 ilm 432
        this.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(30));
433
 
73 ilm 434
        // the default of many systems
435
        this.txIsolation = Connection.TRANSACTION_READ_COMMITTED;
436
        // by definition unknown without a connection
437
        this.dbTxIsolation = null;
438
        // it's rare that DB configuration changes, and it's expensive to add a trip to the server
439
        // for each new connection
440
        this.checkOnceDBTxIsolation = true;
441
 
17 ilm 442
        // see #createDataSource() for properties not supported by this class
61 ilm 443
        this.tables = Collections.emptySet();
80 ilm 444
        this.descL = new PropertyChangeListener() {
445
            @Override
446
            public void propertyChange(PropertyChangeEvent evt) {
447
                if (evt.getPropertyName().equals("descendants")) {
448
                    // the dataSource must always have all tables, to listen to them for its cache
449
                    setTables(((DBSystemRoot) evt.getSource()).getDescs(SQLTable.class));
450
                }
451
            }
452
        };
453
        this.sysRoot.addListener(this.descL);
17 ilm 454
        this.cache = null;
455
        this.cacheEnabled = false;
456
    }
457
 
458
    /**
459
     * Exécute la requête et retourne le résultat sous forme de liste de map. Si la requete va
460
     * retourner beaucoup de lignes, il est peut-être préférable d'utiliser un ResultSetHandler.
461
     *
462
     * @param query le requête à exécuter.
463
     * @return le résultat de la requête.
464
     * @see MapListHandler
465
     * @see #execute(String, ResultSetHandler)
466
     */
467
    public List execute(String query) {
468
        return (List) this.execute(query, MAP_LIST_HANDLER);
469
    }
470
 
471
    /**
472
     * Exécute la requête et retourne la première colonne uniquement.
473
     *
474
     * @param query le requête à exécuter.
475
     * @return le résultat de la requête.
476
     * @see ColumnListHandler
477
     * @see #execute(String, ResultSetHandler)
478
     */
479
    public List executeCol(String query) {
480
        return (List) this.execute(query, COLUMN_LIST_HANDLER);
481
    }
482
 
483
    /**
484
     * Exécute la requête et retourne le résultat sous forme de liste de tableau. Si la requete va
485
     * retourner beaucoup de lignes, il est peut-être préférable d'utiliser un ResultSetHandler.
486
     *
487
     * @param query le requête à exécuter.
488
     * @return le résultat de la requête.
489
     * @see ArrayListHandler
490
     * @see #execute(String, ResultSetHandler)
491
     */
492
    public List executeA(String query) {
493
        return (List) this.execute(query, ARRAY_LIST_HANDLER);
494
    }
495
 
496
    /**
497
     * Exécute la requête et retourne la première ligne du résultat sous forme de map.
498
     *
499
     * @param query le requête à exécuter.
500
     * @return le résultat de la requête.
501
     * @see MapHandler
502
     * @see #execute(String)
503
     */
142 ilm 504
    public Map<String, Object> execute1(String query) {
505
        return (Map<String, Object>) this.execute(query, MAP_HANDLER);
17 ilm 506
    }
507
 
508
    /**
509
     * Exécute la requête et retourne la première ligne du résultat sous forme de tableau.
510
     *
511
     * @param query le requête à exécuter.
512
     * @return le résultat de la requête.
513
     * @see ArrayHandler
514
     * @see #executeA(String)
515
     */
516
    public Object[] executeA1(String query) {
517
        return (Object[]) this.execute(query, ARRAY_HANDLER);
518
    }
519
 
520
    /**
521
     * Exécute la requête et retourne la valeur de la premiere colonne de la premiere ligne.
522
     *
523
     * @param query le requête à exécuter.
524
     * @return le résultat de la requête.
525
     */
526
    public Object executeScalar(String query) {
527
        return this.execute(query, SCALAR_HANDLER);
528
    }
529
 
530
    /**
531
     * Exécute la requête et passe le résultat au ResultSetHandler.
532
     *
533
     * @param query le requête à exécuter.
534
     * @param rsh le handler à utiliser, ou <code>null</code>.
535
     * @return le résultat du handler, <code>null</code> si rsh est <code>null</code>.
536
     * @see #execute(String)
537
     */
538
    public Object execute(String query, ResultSetHandler rsh) {
539
        return this.execute(query, rsh, null);
540
    }
541
 
542
    /**
543
     * Execute <code>query</code> within <code>c</code>, passing the result set to <code>rsh</code>.
544
     *
545
     * @param query the query to perform.
546
     * @param rsh what to do with the result, can be <code>null</code>.
547
     * @param changeState whether <code>query</code> changes the state of a connection.
548
     * @return the result of <code>rsh</code>, <code>null</code> if rsh or the resultSet is
549
     *         <code>null</code>.
550
     * @throws RTInterruptedException if the current thread is interrupted while waiting for the
551
     *         cache or for the database.
552
     */
553
    public final Object execute(final String query, final ResultSetHandler rsh, final boolean changeState) throws RTInterruptedException {
554
        return this.execute(query, rsh, changeState, null);
555
    }
556
 
557
    private Object execute(final String query, final ResultSetHandler rsh, final Connection c) throws RTInterruptedException {
558
        // false since the vast majority of request do NOT change the state
559
        return this.execute(query, rsh, false, c);
560
    }
561
 
132 ilm 562
    final List<Object> getCacheKey(final String query, final ResultSetHandler rsh) {
563
        return query.startsWith("SELECT") ? Arrays.asList(new Object[] { query, rsh }) : null;
564
    }
565
 
17 ilm 566
    /**
567
     * Execute <code>query</code> within <code>c</code>, passing the result set to <code>rsh</code>.
568
     *
569
     * @param query the query to perform.
570
     * @param rsh what to do with the result, can be <code>null</code>.
571
     * @param changeState whether <code>query</code> changes the state of a connection.
572
     * @param passedConn the sql connection to use.
573
     * @return the result of <code>rsh</code>, <code>null</code> if rsh or the resultSet is
574
     *         <code>null</code>.
575
     * @throws RTInterruptedException if the current thread is interrupted while waiting for the
576
     *         cache or for the database.
577
     */
578
    private Object execute(final String query, final ResultSetHandler rsh, final boolean changeState, final Connection passedConn) throws RTInterruptedException {
579
        final long timeMs = System.currentTimeMillis();
580
        final long time = System.nanoTime();
581
        // some systems refuse to execute nothing
582
        if (query.length() == 0) {
65 ilm 583
            SQLRequestLog.log(query, "Pas de requête.", timeMs, time);
17 ilm 584
            return null;
585
        }
586
 
587
        final IResultSetHandler irsh = rsh instanceof IResultSetHandler ? (IResultSetHandler) rsh : null;
132 ilm 588
        final boolean readCache = irsh == null || irsh.readCache();
589
        final boolean canWriteCache = irsh == null || irsh.canWriteCache();
590
        final SQLCache<List<?>, Object> cache = !readCache && !canWriteCache ? null : this.getCache();
591
        final List<Object> key = cache == null ? null : getCacheKey(query, rsh);
592
        final CacheResult<Object> cacheRes;
593
        if (key != null) {
142 ilm 594
            final Set<? extends SQLData> data = irsh == null || irsh.getCacheModifiers() == null ? this.getTables() : irsh.getCacheModifiers();
132 ilm 595
            cacheRes = cache.check(key, readCache, canWriteCache, data);
596
            if (cacheRes.getState() == CacheResult.State.INTERRUPTED)
17 ilm 597
                throw new RTInterruptedException("interrupted while waiting for the cache");
132 ilm 598
            else if (cacheRes.getState() == CacheResult.State.VALID) {
17 ilm 599
                // cache actif
600
                if (State.DEBUG)
601
                    State.INSTANCE.addCacheHit();
65 ilm 602
                SQLRequestLog.log(query, "En cache.", timeMs, time);
132 ilm 603
                return cacheRes.getRes();
17 ilm 604
            }
132 ilm 605
        } else {
606
            cacheRes = null;
17 ilm 607
        }
608
 
609
        Object result = null;
610
        QueryInfo info = null;
65 ilm 611
        final long afterCache = System.nanoTime();
612
        final long afterQueryInfo, afterExecute, afterHandle;
142 ilm 613
        int count = 0;
17 ilm 614
        try {
615
            info = new QueryInfo(query, changeState, passedConn);
616
            try {
65 ilm 617
                afterQueryInfo = System.nanoTime();
17 ilm 618
                final Object[] res = this.executeTwice(info);
619
                final Statement stmt = (Statement) res[0];
620
                ResultSet rs = (ResultSet) res[1];
621
                // TODO 1. rename #execute(String) to #executeN(String)
622
                // and make #execute(String) do #execute(String, null)
623
                // 2. let null rs pass to rsh
624
                // otherwise you write ds.execute("req", new ResultSetHandler() {
625
                // public Object handle(ResultSet rs) throws SQLException {
626
                // return "OK";
627
                // }
628
                // });
629
                // and OK won't be returned if "req" returns a null rs.
65 ilm 630
                afterExecute = System.nanoTime();
17 ilm 631
                if (rsh != null && rs != null) {
632
                    if (this.getSystem() == SQLSystem.DERBY || this.getSystem() == SQLSystem.POSTGRESQL) {
633
                        rs = new SQLResultSet(rs);
634
                    }
635
                    result = rsh.handle(rs);
142 ilm 636
                    count = SQLResultSet.getRowProcessedCount(rs);
17 ilm 637
                }
142 ilm 638
 
65 ilm 639
                afterHandle = System.nanoTime();
17 ilm 640
 
641
                stmt.close();
642
                // if key was added to the cache
643
                if (key != null) {
61 ilm 644
                    synchronized (this) {
132 ilm 645
                        putInCache(cache, irsh, cacheRes, result);
61 ilm 646
                    }
17 ilm 647
                }
648
                info.releaseConnection();
649
            } catch (SQLException exn) {
650
                // don't usually do a getSchema() as it access the db
651
                throw new IllegalStateException("Impossible d'accéder au résultat de " + query + "\n in " + this, exn);
652
            }
653
        } catch (RuntimeException e) {
654
            // for each #check() there must be a #removeRunning()
655
            // let the cache know we ain't gonna tell it the result
132 ilm 656
            if (cacheRes != null)
657
                cache.removeRunning(cacheRes);
17 ilm 658
            if (info != null)
659
                info.releaseConnection(e);
660
            throw e;
661
        }
662
 
142 ilm 663
        SQLRequestLog.log(query, "", info.getConnection(), timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime(), count);
17 ilm 664
 
665
        return result;
666
    }
667
 
132 ilm 668
    private synchronized void putInCache(final SQLCache<List<?>, Object> cache, final IResultSetHandler irsh, final CacheResult<Object> cacheRes, Object result) {
669
        if (irsh != null && irsh.writeCache() || irsh == null && IResultSetHandler.shouldCache(result)) {
670
            cache.put(cacheRes, result);
671
        } else {
672
            cache.removeRunning(cacheRes);
61 ilm 673
        }
674
    }
675
 
17 ilm 676
    private synchronized final ExecutorService getExec() {
677
        if (this.exec == null) {
678
            // not daemon since we want the connections to be returned
679
            final ThreadFactory factory = new ThreadFactory(SQLDataSource.class.getSimpleName() + " " + this.toString() + " exec n° ", false);
680
            // a rather larger number of threads since all they do is wait severals seconds
681
            this.exec = new ThreadPoolExecutor(0, 32, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), factory);
682
        }
683
        return this.exec;
684
    }
685
 
67 ilm 686
    private final class QueryInfo {
17 ilm 687
        private final String query;
688
        // whether query change the state of our connection
689
        private final boolean changeState;
690
        // can change if private
691
        private Connection c;
692
        // whether we acquired a new connection (and thus can do whatever we want with it)
693
        private final boolean privateConnection;
694
 
695
        QueryInfo(String query, boolean changeState, final Connection passedConn) {
696
            super();
697
            this.query = query;
698
            this.changeState = changeState;
699
 
700
            // if passedConn is provided use it, else we need to find one
701
            boolean acquiredConnection = false;
702
            final Connection foundConn;
703
            if (passedConn != null)
704
                foundConn = passedConn;
705
            else if (!handlingConnection()) {
706
                foundConn = getNewConnection();
707
                acquiredConnection = true;
708
            } else {
709
                final HandlersStack threadHandlers = getHandlersStack();
710
                if (!changeState || threadHandlers.isChangeAllowed()) {
711
                    foundConn = threadHandlers.getConnection();
712
                } else {
713
                    throw new IllegalStateException("the passed query change the connection's state and the current thread has a connection which will thus be changed."
714
                            + " A possible solution is to execute it in the setup() of a ConnectionHandler\n" + query);
715
                }
716
            }
717
 
718
            this.privateConnection = acquiredConnection;
719
            this.c = foundConn;
720
        }
721
 
722
        public final Connection getConnection() {
723
            return this.c;
724
        }
725
 
726
        public final String getQuery() {
727
            return this.query;
728
        }
729
 
730
        void releaseConnection(RuntimeException e) {
731
            // MySQL reste des fois bloqué dans SocketInputStream.socketRead0()
732
            // (le serveur ayant tué la query)
733
            if (e instanceof InterruptedQuery && getSystem() == SQLSystem.MYSQL) {
734
                final ExecutorThread thread = ((InterruptedQuery) e).getThread();
735
 
736
                if (this.privateConnection) {
737
                    if (this.changeState)
738
                        // no need to try to save the connection, it is no longer valid
739
                        this.releaseConnection();
740
                    else {
741
                        // test if the connection is still valid before returning it to the pool
742
                        getExec().execute(new Runnable() {
743
                            public void run() {
744
                                // on attend un peu
745
                                try {
746
                                    thread.join(1500);
747
                                    // pour voir si on meurt
748
                                    if (thread.isAlive()) {
749
                                        Log.get().warning(getFailedCancelMsg());
750
                                        closeConnection(getConnection());
751
                                    } else {
752
                                        // la connexion est ok, on la remet dans le pool
753
                                        returnConnection(getConnection());
754
                                    }
755
                                } catch (InterruptedException e) {
756
                                    // the datasource is closing
757
                                    Log.get().fine("Interrupted while joining " + getQuery());
758
                                    closeConnection(getConnection());
759
                                }
760
                            }
761
                        });
762
                    }
763
                } else {
764
                    // try to save the connection since it is used by others
765
                    try {
766
                        // clear the interrupt status set by InterruptedQuery
767
                        // so that we can wait on thread
768
                        Thread.interrupted();
769
                        thread.join(500);
770
                    } catch (InterruptedException e2) {
771
                        System.err.println("ignore, we are already interrupted");
772
                        e2.printStackTrace();
773
                    }
774
                    // remettre le flag pour les méthodes appelantes.
775
                    Thread.currentThread().interrupt();
776
 
777
                    // connection is still stuck
778
                    if (thread.isAlive()) {
779
                        throw new IllegalStateException(getFailedCancelMsg(), e);
780
                    } else
781
                        this.releaseConnection();
782
                }
783
            } else
784
                this.releaseConnection();
785
        }
786
 
787
        void releaseConnection() {
788
            // have we borrowed a connection, otherwise it is not our responsibility to release it.
789
            if (this.privateConnection) {
790
                if (this.changeState)
791
                    // the connection is no longer in a pristine state so close it
792
                    closeConnection(this.getConnection());
793
                else
794
                    // otherwise we can reuse it
795
                    returnConnection(this.getConnection());
796
            }
797
        }
798
 
799
        private final String getFailedCancelMsg() {
800
            return "cancel of " + System.identityHashCode(getConnection()) + " failed for " + getQuery();
801
        }
802
 
142 ilm 803
        final boolean canObtainNewConnection() {
804
            return this.privateConnection;
805
        }
806
 
17 ilm 807
        // an error has occured, try within another connection if possible
67 ilm 808
        final Connection obtainNewConnection() {
142 ilm 809
            if (!this.canObtainNewConnection()) {
17 ilm 810
                return null;
142 ilm 811
            } else {
17 ilm 812
                // ATTN should be sure that our connection was not already closed,
813
                // see #closeConnection()
814
                closeConnection(this.getConnection());
815
                this.c = borrowConnection(true);
816
                return this.getConnection();
817
            }
818
        }
65 ilm 819
 
820
        @Override
821
        public String toString() {
822
            return this.getClass().getSimpleName() + " private connection: " + this.privateConnection + " query: " + this.getQuery();
823
        }
17 ilm 824
    }
825
 
67 ilm 826
    /**
827
     * Whether the current thread has called {@link #useConnection(ConnectionHandler)}.
828
     *
829
     * @return <code>true</code> if within <code>useConnection()</code> and thus safe to call
830
     *         {@link #getConnection()}.
831
     */
832
    public final boolean handlingConnection() {
17 ilm 833
        return this.handlers.containsKey(Thread.currentThread());
834
    }
835
 
836
    private final HandlersStack getHandlersStack() {
837
        return this.handlers.get(Thread.currentThread());
838
    }
839
 
182 ilm 840
    public final <T, X extends Exception> T useConnection(final ITransformerExn<? super SQLDataSource, T, X> run) throws SQLException, X {
841
        return this.useConnection(new ConnectionHandlerNoSetup<T, X>() {
842
            @Override
843
            public T handle(SQLDataSource ds) throws X {
844
                return run.transformChecked(ds);
845
            }
846
        });
847
    }
848
 
17 ilm 849
    /**
850
     * Use a single connection to execute <code>handler</code>.
851
     *
852
     * @param <T> type of return.
853
     * @param <X> type of exception.
854
     * @param handler what to do with the connection.
855
     * @return what <code>handler</code> returned.
856
     * @throws SQLException if an exception happens in setup() or restore().
857
     * @throws X if handle() throws an exception.
858
     * @see ConnectionHandler
859
     */
860
    public final <T, X extends Exception> T useConnection(ConnectionHandler<T, X> handler) throws SQLException, X {
93 ilm 861
        return this.useConnection(handler, false);
862
    }
863
 
864
    private final <T, X extends Exception> T useConnection(ConnectionHandler<T, X> handler, final boolean force) throws SQLException, X {
17 ilm 865
        final HandlersStack h;
93 ilm 866
        boolean connOutsidePool = false;
17 ilm 867
        if (!this.handlingConnection()) {
93 ilm 868
            Connection conn;
869
            try {
870
                conn = this.getNewConnection();
871
            } catch (NoSuchElementException e) {
872
                if (force) {
873
                    conn = this.connectionFactory.createConnection();
874
                    connOutsidePool = true;
875
                } else {
876
                    throw e;
877
                }
878
            }
879
            h = new HandlersStack(this, conn, handler);
132 ilm 880
            this.handlers.put(h.getThread(), h);
17 ilm 881
        } else if (handler.canRestoreState()) {
882
            h = this.getHandlersStack().push(handler);
883
        } else
884
            throw new IllegalStateException("this thread has already called useConnection() and thus expect its state, but the passed handler cannot restore state: " + handler);
885
 
886
        Connection conn = null;
132 ilm 887
        // before or after compute, RuntimeException or SQLException
888
        Exception beforeExn = null, afterExn = null;
889
        // X or SQLException
890
        Exception computeExn = null;
17 ilm 891
        try {
892
            conn = h.getConnection();
893
            h.setChangeAllowed(true);
894
            handler.setup(conn);
895
            h.setChangeAllowed(false);
132 ilm 896
            try {
897
                handler.compute(this);
898
            } catch (Exception e) {
899
                computeExn = e;
900
            }
17 ilm 901
        } catch (Exception e) {
902
            h.setChangeAllowed(false);
132 ilm 903
            beforeExn = e;
17 ilm 904
        }
905
 
906
        // in all cases (thanks to the above catch), try to restore the state
907
        // if conn is null setup() was never called
908
        boolean pristineState = conn == null;
132 ilm 909
        // don't bother trying to restore state if the connection has been invalidated (by a
910
        // recursive call)
911
        if (!pristineState && h.hasValidConnection() && handler.canRestoreState()) {
17 ilm 912
            h.setChangeAllowed(true);
913
            try {
914
                handler.restoreState(conn);
915
                pristineState = true;
916
            } catch (Exception e) {
132 ilm 917
                afterExn = e;
17 ilm 918
            }
919
            h.setChangeAllowed(false);
920
        }
921
 
922
        // ATTN conn can be null (return/closeConnection() accept it)
923
        if (h.pop()) {
924
            // remove if this thread has no handlers left
925
            this.handlers.remove(Thread.currentThread());
93 ilm 926
            if (connOutsidePool) {
927
                conn.close();
132 ilm 928
            } else if (pristineState) {
929
                this.returnConnection(h.getConnection());
930
            } else {
931
                this.closeConnection(h.invalidConnection());
932
            }
17 ilm 933
        } else {
93 ilm 934
            assert !connOutsidePool;
17 ilm 935
            // connection is still used
936
            if (!pristineState) {
132 ilm 937
                this.closeConnection(h.invalidConnection());
17 ilm 938
            }
939
            // else the top handler will release the connection
940
        }
132 ilm 941
        if (beforeExn != null) {
942
            assert computeExn == null : "Compute shouldn't be attempted if setup fails : " + beforeExn + " " + computeExn;
943
            if (afterExn != null) {
944
                throw new SQLException("could not restore state after failed setup : " + ExceptionUtils.getStackTrace(afterExn), beforeExn);
945
            } else {
946
                throw ExceptionUtils.throwExn(beforeExn, SQLException.class, RuntimeException.class);
947
            }
948
        } else if (afterExn != null) {
949
            if (computeExn != null) {
950
                throw new SQLException("could not restore state after successful setup : " + ExceptionUtils.getStackTrace(afterExn), computeExn);
951
            } else {
952
                throw ExceptionUtils.throwExn(afterExn, SQLException.class, RuntimeException.class);
953
            }
954
        } else {
17 ilm 955
            return handler.get();
132 ilm 956
        }
17 ilm 957
    }
958
 
959
    // this method create a Statement, don't forget to close it when you're done
960
    private Object[] executeTwice(QueryInfo queryInfo) throws SQLException {
961
        final String query = queryInfo.getQuery();
962
        Object[] res;
963
        try {
964
            res = executeOnce(query, queryInfo.getConnection());
965
        } catch (SQLException exn) {
966
            if (State.DEBUG)
967
                State.INSTANCE.addFailedRequest(query);
142 ilm 968
            // only retry for transient errors
969
            final boolean retry;
970
            if (exn instanceof SQLTransientException) {
971
                retry = true;
972
            } else if (exn instanceof SQLNonTransientException) {
973
                retry = false;
974
            } else if (getSystem() == SQLSystem.H2) {
975
                // 1. server was killed, maybe it will be restarted
976
                // 2. client network interface was brought down, maybe it will be brought up again
977
                retry = exn.getErrorCode() == ErrorCode.CONNECTION_BROKEN_1;
978
            } else if (getSystem() == SQLSystem.POSTGRESQL) {
979
                // Class 08 — Connection Exception (e.g. SocketException)
980
                // Class 57 — Operator Intervention (e.g. server shutdown)
981
                retry = exn.getSQLState().startsWith("08") || exn.getSQLState().startsWith("57");
982
            } else {
983
                retry = getSystem() == SQLSystem.MYSQL;
984
            }
17 ilm 985
            // maybe this was a network problem, so wait a little
93 ilm 986
            final int retryWait = this.retryWait;
142 ilm 987
            if (!retry || retryWait < 0 || !queryInfo.canObtainNewConnection())
93 ilm 988
                throw exn;
17 ilm 989
            try {
142 ilm 990
                Thread.sleep(retryWait);
17 ilm 991
            } catch (InterruptedException e) {
992
                throw new RTInterruptedException(e.getMessage() + " : " + query, exn);
993
            }
994
            // and try to obtain a new connection
995
            try {
996
                final Connection otherConn = queryInfo.obtainNewConnection();
142 ilm 997
                res = executeOnce(query, otherConn);
17 ilm 998
            } catch (Exception e) {
999
                if (e == exn)
1000
                    throw exn;
1001
                else
1002
                    throw new SQLException("second exec failed: " + e.getLocalizedMessage(), exn);
1003
            }
67 ilm 1004
            // only log if the second succeeds (otherwise it's thrown)
1005
            Log.get().log(Level.INFO, "executeOnce() failed for " + queryInfo, exn);
17 ilm 1006
        }
1007
        return res;
1008
    }
1009
 
174 ilm 1010
    private static final Tuple2<Long, int[]> NO_QUERIES_RES = Tuple2.create(0l, new int[0]);
1011
 
93 ilm 1012
    /**
174 ilm 1013
     * Execute multiple queries in batch.
1014
     *
1015
     * @param queries what to execute.
1016
     * @param atomic <code>true</code> if all queries should be executed in a transaction.
1017
     * @return the total update count (&lt; 0 if unknown), followed by the individual update counts.
1018
     * @throws SQLException if an error occurs.
1019
     * @see Statement#executeBatch()
1020
     */
1021
    public final Tuple2<Long, int[]> executeBatch(final List<String> queries, final boolean atomic) throws SQLException {
1022
        if (queries.isEmpty())
1023
            return NO_QUERIES_RES;
1024
        final long timeMs = System.currentTimeMillis();
1025
        final long time = System.nanoTime();
1026
        final long afterCache = time;
1027
        final AtomicLong afterQueryInfo = new AtomicLong();
1028
        final AtomicLong afterExecute = new AtomicLong();
1029
        final AtomicReference<Connection> conn = new AtomicReference<>();
1030
        final ConnectionHandlerNoSetup<int[], SQLException> handler = new ConnectionHandlerNoSetup<int[], SQLException>() {
1031
            @Override
1032
            public int[] handle(SQLDataSource ds) throws SQLException {
1033
                afterQueryInfo.set(System.nanoTime());
1034
                conn.set(ds.getConnection());
1035
                final int[] res;
1036
                try (final Statement stmt = conn.get().createStatement()) {
1037
                    for (final String s : queries) {
1038
                        stmt.addBatch(s);
1039
                    }
1040
                    if (Thread.currentThread().isInterrupted())
1041
                        throw new RTInterruptedException("Interrupted before executing : " + queries);
1042
                    res = stmt.executeBatch();
1043
                }
1044
                afterExecute.set(System.nanoTime());
1045
                return res;
1046
            }
1047
        };
1048
        final int[] res = atomic ? SQLUtils.executeAtomic(this, handler) : this.useConnection(handler);
1049
        long totalCount = 0;
1050
        int i = 0;
1051
        for (final int count : res) {
1052
            if (count == Statement.SUCCESS_NO_INFO) {
1053
                totalCount = -1;
1054
                break;
1055
            } else {
1056
                if (count < 0)
1057
                    throw new SQLException("Invalid count (" + count + ") for query " + i + " : " + queries.get(i));
1058
                totalCount += count;
1059
            }
1060
            i++;
1061
        }
1062
        if (SQLRequestLog.isEnabled()) {
1063
            final long afterHandle = System.nanoTime();
1064
            SQLRequestLog.log(queries.toString(), "executeBatch", conn.get(), timeMs, time, afterCache, afterQueryInfo.get(), afterExecute.get(), afterHandle, afterHandle, (int) totalCount);
1065
        }
1066
        return Tuple2.create(totalCount, res);
1067
    }
1068
 
1069
    /**
93 ilm 1070
     * Try to execute a {@link #getValidationQuery() simple query} on the database server. This
1071
     * method even works when the pool is exhausted.
1072
     *
1073
     * @throws SQLException if the query couldn't be executed.
1074
     */
1075
    public final void validateDBConnectivity() throws SQLException {
1076
        this.validateDBConnectivity(this.getValidationQueryTimeout());
1077
    }
1078
 
1079
    public final void validateDBConnectivity(final int timeout) throws SQLException {
1080
        this.useConnection(new ConnectionHandlerNoSetup<Object, SQLException>() {
1081
            @Override
1082
            public Object handle(SQLDataSource ds) throws SQLException {
1083
                final Statement stmt = ds.getConnection().createStatement();
1084
                try {
1085
                    stmt.setQueryTimeout(timeout);
1086
                    final ResultSet rs = stmt.executeQuery(ds.getValidationQuery());
1087
                    if (!rs.next())
1088
                        throw new SQLException("No row returned");
1089
                    rs.close();
1090
                } finally {
1091
                    stmt.close();
1092
                }
1093
                return null;
1094
            }
1095
        }, true);
1096
    }
1097
 
17 ilm 1098
    private Object[] executeOnce(String query, Connection c) throws SQLException {
1099
        final Statement stmt = c.createStatement();
1100
        final ResultSet rs = execute(query, stmt);
1101
        return new Object[] { stmt, rs };
1102
    }
1103
 
1104
    /**
1105
     * Exécute la requête et retourne le résultat. Attention le resultSet peut cesser d'être valide
1106
     * a tout moment, de plus cette méthode ne ferme pas le statement qu'elle crée, la méthode
1107
     * préférée est execute()
1108
     *
1109
     * @param query le requête à exécuter.
1110
     * @return le résultat de la requête.
1111
     * @deprecated replaced by execute().
1112
     * @see #execute(String)
1113
     */
1114
    public ResultSet executeRaw(String query) {
1115
        try {
1116
            return execute(query, this.getStatement());
1117
        } catch (SQLException e) {
1118
            try {
1119
                return execute(query, this.getStatement());
1120
            } catch (SQLException ex) {
1121
                ExceptionHandler.handle("Impossible d'executer la query: " + query, ex);
1122
                return null;
1123
            }
1124
        }
1125
    }
1126
 
1127
    /**
1128
     * Retourne un nouveau statement. Attention, la fermeture est à la charge de l'appelant.
1129
     *
1130
     * @return un nouveau statement.
1131
     * @throws SQLException if an error occurs.
1132
     */
1133
    private Statement getStatement() throws SQLException {
1134
        return this.getConnection().createStatement();
1135
    }
1136
 
1137
    /**
1138
     * Execute la requete avec le statement passé. Attention cette méthode ne peut fermer le
1139
     * statement car elle retourne directement le resultSet.
1140
     *
1141
     * @param query le requête à exécuter.
1142
     * @param stmt le statement.
1143
     * @return le résultat de la requête, should never be null according to the spec but Derby don't
1144
     *         care.
1145
     * @throws SQLException si erreur lors de l'exécution de la requête.
1146
     */
1147
    private ResultSet execute(String query, Statement stmt) throws SQLException, RTInterruptedException {
1148
        // System.err.println("\n" + count + "*** " + query + "\n");
1149
 
1150
        if (State.DEBUG)
1151
            State.INSTANCE.beginRequest(query);
1152
 
1153
        // test before calling JDBC methods and creating threads
80 ilm 1154
        boolean interrupted = false;
1155
        if (QUERY_TUNING > 0) {
1156
            try {
1157
                Thread.sleep(QUERY_TUNING);
1158
            } catch (InterruptedException e1) {
1159
                interrupted = true;
1160
            }
1161
        } else {
1162
            interrupted = Thread.currentThread().isInterrupted();
1163
        }
1164
        if (interrupted) {
17 ilm 1165
            throw new RTInterruptedException("request interrupted : " + query);
1166
        }
1167
 
1168
        final long t1 = System.currentTimeMillis();
1169
        ResultSet rs = null;
1170
        try {
1171
            // MAYBE un truc un peu plus formel
80 ilm 1172
            if (query.startsWith("INSERT") || query.startsWith("UPDATE") || query.startsWith("DELETE") || query.startsWith("CREATE") || query.startsWith("ALTER") || query.startsWith("DROP")
1173
                    || query.startsWith("SET")) {
83 ilm 1174
                // MS SQL doesn't support UPDATE
1175
                final boolean returnGenK = query.startsWith("INSERT") && stmt.getConnection().getMetaData().supportsGetGeneratedKeys();
17 ilm 1176
                stmt.executeUpdate(query, returnGenK ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS);
1177
                rs = returnGenK ? stmt.getGeneratedKeys() : null;
1178
            } else {
1179
                // TODO en faire qu'un seul par Connection
1180
                final ExecutorThread thr = new ExecutorThread(stmt, query);
1181
                // on lance l'exécution
1182
                thr.start();
1183
                // et on attend soit qu'elle finisse soit qu'on soit interrompu
1184
                try {
1185
                    rs = thr.getRs();
93 ilm 1186
                } catch (SQLException e) {
1187
                    if (getSystem() == SQLSystem.MYSQL && e.getErrorCode() == 1317) {
1188
                        thr.stopQuery();
1189
                        throw new InterruptedQuery("request interrupted : " + query, e, thr);
1190
                    } else {
1191
                        throw e;
1192
                    }
17 ilm 1193
                } catch (InterruptedException e) {
1194
                    thr.stopQuery();
1195
                    throw new InterruptedQuery("request interrupted : " + query, e, thr);
1196
                }
1197
            }
1198
        } finally {
1199
            if (State.DEBUG)
1200
                State.INSTANCE.endRequest(query);
1201
        }
1202
        long t2 = System.currentTimeMillis();
1203
        // obviously very long queries tend to last longer, that's normal so don't warn
1204
        if (t2 - t1 > 1000 && query.length() < 1000) {
1205
            System.err.println("Warning:" + (t2 - t1) + "ms pour :" + query);
1206
        }
1207
 
1208
        count++;
1209
        return rs;
1210
    }
1211
 
1212
    private final class InterruptedQuery extends RTInterruptedException {
1213
 
1214
        private final ExecutorThread thread;
1215
 
1216
        InterruptedQuery(String message, Throwable cause, ExecutorThread thr) {
1217
            super(message, cause);
1218
            this.thread = thr;
1219
        }
1220
 
1221
        public final ExecutorThread getThread() {
1222
            return this.thread;
1223
        }
1224
    }
1225
 
1226
    private static int executorSerial = 0;
1227
 
1228
    private final class ExecutorThread extends Thread {
1229
 
1230
        private final Statement stmt;
1231
        private final String query;
1232
 
1233
        private ResultSet rs;
1234
        private Exception exn;
1235
        private boolean canceled;
1236
 
1237
        public ExecutorThread(Statement stmt, String query) {
1238
            super(executorSerial++ + " ExecutorThread on " + query);
1239
            this.stmt = stmt;
1240
            this.query = query;
1241
            this.canceled = false;
1242
        }
1243
 
1244
        public void run() {
1245
            synchronized (this) {
1246
                if (this.canceled)
1247
                    return;
1248
            }
1249
 
1250
            ResultSet rs = null;
1251
            try {
1252
                // do not use executeQuery since this.query might contain several statements
1253
                this.stmt.execute(this.query);
1254
                synchronized (this) {
1255
                    if (this.canceled)
1256
                        return;
1257
                }
1258
                rs = this.stmt.getResultSet();
1259
            } catch (Exception e) {
1260
                // can only be SQLException or RuntimeException
1261
                // eg MySQLStatementCancelledException if stopQuery() was called
1262
                this.exn = e;
1263
            }
1264
            this.rs = rs;
1265
        }
1266
 
1267
        public void stopQuery() throws SQLException {
93 ilm 1268
            if (!this.stmt.isClosed())
1269
                this.stmt.cancel();
17 ilm 1270
            synchronized (this) {
1271
                this.canceled = true;
1272
            }
1273
        }
1274
 
1275
        public ResultSet getRs() throws SQLException, InterruptedException {
1276
            this.join();
1277
            // pas besoin de synchronized puisque seule notre thread ecrit les var
1278
            // et qu'elle est maintenant terminée
1279
            if (this.exn != null) {
1280
                if (this.exn instanceof SQLException)
1281
                    throw (SQLException) this.exn;
1282
                else
1283
                    throw (RuntimeException) this.exn;
1284
            }
1285
            return this.rs;
1286
        }
1287
    }
1288
 
1289
    /**
1290
     * All connections obtained with {@link #getConnection()} will be closed immediately, but
1291
     * threads in {@link #useConnection(ConnectionHandler)} will get to keep them. After the last
1292
     * thread returns from {@link #useConnection(ConnectionHandler)} there won't be any connection
73 ilm 1293
     * left open. This instance will be permanently closed, it cannot be reused later.
17 ilm 1294
     *
1295
     * @throws SQLException if a database error occurs
1296
     */
1297
    public synchronized void close() throws SQLException {
80 ilm 1298
        this.sysRoot.rmListener(this.descL);
73 ilm 1299
        @SuppressWarnings("rawtypes")
17 ilm 1300
        final GenericObjectPool pool = this.connectionPool;
1301
        super.close();
1302
        // super close and unset our pool, but we need to keep it
1303
        // to allow used connections to be closed, see #closeConnection(Connection)
1304
        this.connectionPool = pool;
63 ilm 1305
 
17 ilm 1306
        // interrupt to force waiting threads to close their connections
1307
        if (this.exec != null) {
1308
            this.exec.shutdownNow();
1309
            this.exec = null;
1310
        }
1311
 
1312
        // uptodate was cleared by closeConnection()
1313
        // the handlers will clear themselves
1314
        // the cache is expected to be cleared (when all connections are closed)
1315
        if (this.getBorrowedConnectionCount() == 0)
1316
            noConnectionIsOpen();
1317
        // ATTN keep tables to be able to reopen
1318
    }
1319
 
61 ilm 1320
    private synchronized void noConnectionIsOpen() {
80 ilm 1321
        assert this.connectionPool == null || (this.connectionPool.getNumIdle() + this.getBorrowedConnectionCount()) == 0;
17 ilm 1322
        if (this.cache != null)
132 ilm 1323
            this.cache.getSupp().die();
17 ilm 1324
    }
1325
 
1326
    /**
63 ilm 1327
     * Retourne la connection à cette source de donnée.
1328
     *
1329
     * @return la connection à cette source de donnée.
1330
     * @throws IllegalStateException if not called from within useConnection().
1331
     * @see #useConnection(ConnectionHandler)
67 ilm 1332
     * @see #handlingConnection()
17 ilm 1333
     */
63 ilm 1334
    public final Connection getConnection() {
73 ilm 1335
        final HandlersStack res = this.getHandlersStack();
63 ilm 1336
        if (res == null)
1337
            throw new IllegalStateException("useConnection() wasn't called");
1338
        return res.getConnection();
17 ilm 1339
    }
1340
 
73 ilm 1341
    public final TransactionPoint getTransactionPoint() {
1342
        final HandlersStack handlersStack = this.getHandlersStack();
1343
        if (handlersStack == null)
1344
            return null;
1345
        return handlersStack.getLastTxPoint();
1346
    }
1347
 
17 ilm 1348
    /**
63 ilm 1349
     * Retourne une connection à cette source de donnée (generally
93 ilm 1350
     * {@link #useConnection(ConnectionHandler)} should be used). If a connection in the pool fails
1351
     * to {@link #initConnection(Connection) initialize} or if the pool is empty and a new
1352
     * connection fails to get created, this method will try to borrow a connection from the pool a
1353
     * second time.
17 ilm 1354
     * <p>
63 ilm 1355
     * Note : you <b>must</b> return this connection (e.g. use try/finally).
17 ilm 1356
     * <p>
1357
     *
1358
     * @return une connection à cette source de donnée.
93 ilm 1359
     * @throws NoSuchElementException after {@link #getMaxWait()} milliseconds if the pool is
1360
     *         exhausted and {@link #blocksWhenExhausted()}.
63 ilm 1361
     * @see #returnConnection(Connection)
1362
     * @see #closeConnection(Connection)
17 ilm 1363
     */
93 ilm 1364
    protected final Connection getNewConnection() throws NoSuchElementException {
17 ilm 1365
        try {
1366
            return this.borrowConnection(false);
1367
        } catch (RTInterruptedException e) {
1368
            throw e;
1369
        } catch (Exception e) {
93 ilm 1370
            if (e instanceof NoSuchElementException) {
1371
                // no need to try to test all others connections, the pool is just exhausted
1372
                throw (NoSuchElementException) e;
1373
            } else {
1374
                return this.borrowConnection(true);
1375
            }
17 ilm 1376
        }
1377
    }
1378
 
1379
    /**
1380
     * Borrow a new connection from the pool, optionally purging invalid connections with the
1381
     * validation query.
1382
     *
1383
     * @param test if <code>true</code> then testOnBorrow will be set.
1384
     * @return the new connection.
93 ilm 1385
     * @throws NoSuchElementException after {@link #getMaxWait()} milliseconds if the pool is
1386
     *         exhausted and {@link #blocksWhenExhausted()}.
17 ilm 1387
     */
93 ilm 1388
    private final Connection borrowConnection(final boolean test) throws NoSuchElementException {
17 ilm 1389
        if (test) {
142 ilm 1390
            synchronized (this.testLock) {
1391
                // invalidate all bad connections
1392
                setTestOnBorrow(true);
1393
                try {
1394
                    return this._borrowConnection(test);
1395
                } finally {
1396
                    setTestOnBorrow(false);
1397
                }
1398
            }
1399
        } else {
1400
            return this._borrowConnection(test);
17 ilm 1401
        }
142 ilm 1402
    }
1403
 
1404
    private final Connection _borrowConnection(final boolean test) throws NoSuchElementException {
1405
        // when we call borrowConnection() with test, it's because there was an error so this
1406
        // call is already a second try, thus getRawConnection() shouldn't try a third time.
1407
        final Connection res = this.getRawConnection(!test);
17 ilm 1408
        try {
142 ilm 1409
            initConnection(res);
1410
            return res;
1411
        } catch (RuntimeException e) {
1412
            this.closeConnection(res);
1413
            throw e;
17 ilm 1414
        }
1415
    }
1416
 
1417
    // initialize the passed connection if needed
1418
    protected final void initConnection(final Connection res) {
1419
        boolean setSchema = false;
1420
        String schemaToSet = null;
1421
        synchronized (this) {
73 ilm 1422
            if (!this.schemaUptodate.containsKey(res)) {
17 ilm 1423
                if (this.initialShemaSet) {
1424
                    setSchema = true;
1425
                    schemaToSet = this.initialShema;
1426
                }
1427
                // safe to put before setSchema() since res cannot be passed to
1428
                // release/closeConnection()
73 ilm 1429
                this.schemaUptodate.put(res, null);
17 ilm 1430
            }
73 ilm 1431
            // a connection from the pool is up to date since we close all idle connections in
1432
            // invalidateAllConnections() and borrowed ones are closed before they return to the
1433
            // pool
1434
            this.uptodate.put(res, null);
17 ilm 1435
        }
1436
        // warmup the connection (executing a bogus simple query, like "SELECT 1") could help but in
1437
        // general doesn't since we often do getDS().execute() and thus our warm up thread will run
1438
        // after the execute(), making it useless.
1439
        if (setSchema)
1440
            this.setSchema(schemaToSet, res);
1441
    }
1442
 
1443
    private static final String pgInterrupted = GT.tr("Interrupted while attempting to connect.");
1444
 
93 ilm 1445
    private void getRawConnectionThrow(final Exception e1, final Exception e2) throws NoSuchElementException {
1446
        if (e1.getCause() instanceof NoSuchElementException)
1447
            throw (NoSuchElementException) e1.getCause();
1448
        else if (e2 == null)
1449
            throw new IllegalStateException("Impossible d'obtenir une connexion sur " + this, e1);
1450
        else
1451
            throw new IllegalStateException("Impossible d'obtenir une connexion sur " + this + "après 2 essais\nexception 2 :" + e2.getLocalizedMessage(), e1);
1452
    }
1453
 
1454
    private Connection getRawConnection(final boolean retry) throws NoSuchElementException {
132 ilm 1455
        assert !Thread.holdsLock(
1456
                this) : "super.getConnection() might block (see setWhenExhaustedAction()), and since return/closeConnection() need this lock, this method cannot wait while holding the lock";
17 ilm 1457
        Connection result = null;
1458
        try {
1459
            result = super.getConnection();
93 ilm 1460
        } catch (Exception e1) {
17 ilm 1461
            // try to know if interrupt, TODO cleanup : patch pg Driver.java to fill the cause
1462
            if (e1.getCause() instanceof InterruptedException || (e1 instanceof PSQLException && e1.getMessage().equals(pgInterrupted))) {
1463
                throw new RTInterruptedException(e1);
1464
            }
93 ilm 1465
            final int retryWait = retry ? this.retryWait : -1;
142 ilm 1466
            if (retryWait < 0 || e1 instanceof SQLNonTransientException)
93 ilm 1467
                getRawConnectionThrow(e1, null);
17 ilm 1468
            try {
1469
                // on attend un petit peu
93 ilm 1470
                Thread.sleep(retryWait);
17 ilm 1471
                // avant de réessayer
1472
                result = super.getConnection();
1473
            } catch (InterruptedException e) {
1474
                throw new RTInterruptedException("interrupted while waiting for a second try", e);
1475
            } catch (Exception e) {
93 ilm 1476
                getRawConnectionThrow(e1, e);
17 ilm 1477
            }
1478
        }
1479
        if (State.DEBUG)
1480
            State.INSTANCE.connectionCreated();
1481
        return result;
1482
    }
1483
 
1484
    public final int getBorrowedConnectionCount() {
80 ilm 1485
        return this.connectionPool == null ? 0 : this.connectionPool.getNumActive();
17 ilm 1486
    }
1487
 
63 ilm 1488
    public synchronized boolean blocksWhenExhausted() {
1489
        return this.blockWhenExhausted;
17 ilm 1490
    }
1491
 
63 ilm 1492
    public synchronized void setBlockWhenExhausted(boolean block) {
1493
        this.blockWhenExhausted = block;
1494
        if (this.connectionPool != null) {
1495
            this.connectionPool.setWhenExhaustedAction(block ? GenericObjectPool.WHEN_EXHAUSTED_BLOCK : GenericObjectPool.WHEN_EXHAUSTED_GROW);
1496
        }
1497
    }
1498
 
67 ilm 1499
    public synchronized final long getSoftMinEvictableIdleTimeMillis() {
1500
        return this.softMinEvictableIdleTimeMillis;
1501
    }
1502
 
1503
    public synchronized final void setSoftMinEvictableIdleTimeMillis(long millis) {
1504
        this.softMinEvictableIdleTimeMillis = millis;
1505
        if (this.connectionPool != null) {
1506
            this.connectionPool.setSoftMinEvictableIdleTimeMillis(millis);
1507
        }
1508
    }
1509
 
73 ilm 1510
    /**
1511
     * Whether the database defaut transaction isolation is check only once for this instance. If
1512
     * <code>false</code>, every new connection will have its
1513
     * {@link Connection#setTransactionIsolation(int) isolation set}. If <code>true</code> the
1514
     * isolation will only be set if the {@link #setInitialTransactionIsolation(int) requested one}
1515
     * differs from the DB one. In other words, if you want to optimize DB access, the DB
1516
     * configuration must match the datasource configuration.
1517
     *
1518
     * @param checkOnce <code>true</code> to check only once the DB transaction isolation.
1519
     */
1520
    public synchronized final void setTransactionIsolationCheckedOnce(final boolean checkOnce) {
1521
        this.checkOnceDBTxIsolation = checkOnce;
1522
        this.dbTxIsolation = null;
1523
    }
1524
 
1525
    public synchronized final boolean isTransactionIsolationCheckedOnce() {
1526
        return this.checkOnceDBTxIsolation;
1527
    }
1528
 
1529
    // don't use setDefaultTransactionIsolation() in super since it makes extra requests each time a
1530
    // connection is borrowed
1531
    public final void setInitialTransactionIsolation(int level) {
1532
        if (level != Connection.TRANSACTION_READ_UNCOMMITTED && level != Connection.TRANSACTION_READ_COMMITTED && level != Connection.TRANSACTION_REPEATABLE_READ
1533
                && level != Connection.TRANSACTION_SERIALIZABLE)
1534
            throw new IllegalArgumentException("Invalid value :" + level);
1535
        synchronized (this) {
1536
            if (this.txIsolation != level) {
1537
                this.txIsolation = level;
1538
                // perhaps do like setInitialSchema() : i.e. call setTransactionIsolation() on
1539
                // existing connections
1540
                this.invalidateAllConnections(false);
1541
            }
1542
        }
1543
    }
1544
 
1545
    public synchronized final int getInitialTransactionIsolation() {
1546
        return this.txIsolation;
1547
    }
1548
 
1549
    public synchronized final Integer getDBTransactionIsolation() {
1550
        return this.dbTxIsolation;
1551
    }
1552
 
1553
    final synchronized void setTransactionIsolation(Connection conn) throws SQLException {
1554
        if (this.dbTxIsolation == null) {
1555
            this.dbTxIsolation = conn.getTransactionIsolation();
1556
            assert this.dbTxIsolation != null;
1557
        }
1558
        // no need to try to change the level if the DB doesn't support transactions
1559
        if (this.dbTxIsolation != Connection.TRANSACTION_NONE && (!this.checkOnceDBTxIsolation || this.dbTxIsolation != this.txIsolation)) {
1560
            // if not check once, it's the desired action, so don't log
1561
            if (this.checkOnceDBTxIsolation)
1562
                Log.get().config("Setting transaction isolation to " + this.txIsolation);
1563
            conn.setTransactionIsolation(this.txIsolation);
1564
        }
1565
    }
1566
 
1567
    // allow to know transaction states
1568
    private final class TransactionPoolableConnection extends PoolableConnection {
1569
        // perhaps call getAutoCommit() once to have the initial value
1570
        @GuardedBy("this")
1571
        private boolean autoCommit = true;
1572
 
1573
        private TransactionPoolableConnection(Connection conn, @SuppressWarnings("rawtypes") ObjectPool pool, AbandonedConfig config) {
1574
            super(conn, pool, config);
1575
        }
1576
 
1577
        private HandlersStack getNonNullHandlersStack() throws SQLException {
1578
            final HandlersStack res = getHandlersStack();
1579
            if (res == null)
1580
                throw new SQLException("Unsafe transaction, call useConnection() or SQLUtils.executeAtomic()");
1581
            return res;
1582
        }
1583
 
1584
        @Override
1585
        public synchronized void setAutoCommit(boolean autoCommit) throws SQLException {
1586
            if (this.autoCommit != autoCommit) {
1587
                // don't call setAutoCommit() if no stack
1588
                final HandlersStack handlersStack = getNonNullHandlersStack();
1589
                super.setAutoCommit(autoCommit);
1590
                this.autoCommit = autoCommit;
1591
                if (this.autoCommit)
1592
                    // some delegates of the super implementation might have already called our
1593
                    // commit(), but in this case, the following commit will be a no-op
132 ilm 1594
                    handlersStack.commit(null);
73 ilm 1595
                else
1596
                    handlersStack.addTxPoint(new TransactionPoint(this));
1597
            }
1598
        }
1599
 
1600
        @Override
1601
        public synchronized void commit() throws SQLException {
1602
            super.commit();
132 ilm 1603
            assert !this.autoCommit;
73 ilm 1604
            final HandlersStack handlersStack = getNonNullHandlersStack();
132 ilm 1605
            handlersStack.commit(new TransactionPoint(this));
73 ilm 1606
        }
1607
 
1608
        @Override
1609
        public synchronized void rollback() throws SQLException {
1610
            super.rollback();
132 ilm 1611
            assert !this.autoCommit;
73 ilm 1612
            final HandlersStack handlersStack = getNonNullHandlersStack();
132 ilm 1613
            handlersStack.rollback(new TransactionPoint(this));
73 ilm 1614
        }
1615
 
1616
        @Override
1617
        public synchronized Savepoint setSavepoint() throws SQLException {
1618
            // don't call setSavepoint() if no stack
1619
            final HandlersStack handlersStack = getNonNullHandlersStack();
1620
            final Savepoint res = super.setSavepoint();
83 ilm 1621
            // MySQL always create named save points
1622
            handlersStack.addTxPoint(new TransactionPoint(this, res, getSystem() == SQLSystem.MYSQL));
73 ilm 1623
            return res;
1624
        }
1625
 
1626
        @Override
1627
        public synchronized Savepoint setSavepoint(String name) throws SQLException {
1628
            // don't call setSavepoint() if no stack
1629
            final HandlersStack handlersStack = getNonNullHandlersStack();
1630
            final Savepoint res = super.setSavepoint(name);
1631
            handlersStack.addTxPoint(new TransactionPoint(this, res, true));
1632
            return res;
1633
        }
1634
 
1635
        @Override
1636
        public synchronized void rollback(Savepoint savepoint) throws SQLException {
1637
            super.rollback(savepoint);
1638
            getNonNullHandlersStack().rollback(savepoint);
1639
        }
1640
 
1641
        @Override
1642
        public synchronized void releaseSavepoint(Savepoint savepoint) throws SQLException {
1643
            super.releaseSavepoint(savepoint);
132 ilm 1644
            getNonNullHandlersStack().releaseSavepoint(savepoint);
73 ilm 1645
        }
1646
    }
1647
 
17 ilm 1648
    @Override
73 ilm 1649
    protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory, @SuppressWarnings("rawtypes") KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration)
1650
            throws SQLException {
1651
        PoolableConnectionFactory connectionFactory = null;
1652
        try {
1653
            connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, this.connectionPool, statementPoolFactory, this.validationQuery, this.validationQueryTimeout,
1654
                    this.connectionInitSqls, this.defaultReadOnly, this.defaultAutoCommit, this.defaultTransactionIsolation, this.defaultCatalog, configuration) {
17 ilm 1655
                @Override
73 ilm 1656
                public Object makeObject() throws Exception {
1657
                    Connection conn = this._connFactory.createConnection();
1658
                    if (conn == null) {
1659
                        throw new IllegalStateException("Connection factory returned null from createConnection");
17 ilm 1660
                    }
73 ilm 1661
                    initializeConnection(conn);
1662
                    setTransactionIsolation(conn);
1663
                    if (null != this._stmtPoolFactory) {
1664
                        @SuppressWarnings("rawtypes")
1665
                        KeyedObjectPool stmtpool = this._stmtPoolFactory.createPool();
1666
                        conn = new PoolingConnection(conn, stmtpool);
1667
                        stmtpool.setFactory((PoolingConnection) conn);
1668
                    }
1669
                    return new TransactionPoolableConnection(conn, this._pool, this._config);
17 ilm 1670
                }
1671
            };
73 ilm 1672
            validateConnectionFactory(connectionFactory);
1673
        } catch (RuntimeException e) {
1674
            throw e;
81 ilm 1675
        } catch (SQLException e) {
1676
            // only wrap if necessary (calling code can use SQLState)
1677
            throw e;
73 ilm 1678
        } catch (Exception e) {
1679
            throw new SQLException("Cannot create PoolableConnectionFactory", e);
17 ilm 1680
        }
1681
    }
1682
 
73 ilm 1683
    @Override
1684
    protected void createConnectionPool() {
1685
        super.createConnectionPool();
1686
        // methods not defined in superclass and thus not called in super
1687
        this.connectionPool.setLifo(true);
1688
        this.setBlockWhenExhausted(this.blockWhenExhausted);
1689
        this.connectionPool.setSoftMinEvictableIdleTimeMillis(this.softMinEvictableIdleTimeMillis);
1690
    }
1691
 
1692
    @Override
93 ilm 1693
    protected ConnectionFactory createConnectionFactory() throws SQLException {
1694
        final ConnectionFactory res = super.createConnectionFactory();
1695
        this.connectionFactory = res;
1696
        return res;
1697
    }
1698
 
1699
    @Override
73 ilm 1700
    protected void createDataSourceInstance() throws SQLException {
1701
        // PoolingDataSource returns a PoolGuardConnectionWrapper that complicates a lot of
1702
        // things for nothing, so overload to simply return an object of the pool
1703
        this.dataSource = new PoolingDataSource(this.connectionPool) {
1704
 
1705
            // we'll migrate to plain SQLException when our superclass does
1706
            @SuppressWarnings("deprecation")
1707
            @Override
1708
            public Connection getConnection() throws SQLException {
1709
                try {
1710
                    return (Connection) this._pool.borrowObject();
1711
                } catch (SQLException e) {
1712
                    throw e;
1713
                } catch (NoSuchElementException e) {
1714
                    throw new SQLNestedException("Cannot get a connection, pool exhausted", e);
1715
                } catch (RuntimeException e) {
1716
                    throw e;
1717
                } catch (Exception e) {
1718
                    throw new SQLNestedException("Cannot get a connection, general error", e);
1719
                }
1720
            }
1721
 
1722
            @Override
1723
            public Connection getConnection(String username, String password) throws SQLException {
1724
                throw new UnsupportedOperationException();
1725
            }
1726
        };
1727
    }
1728
 
17 ilm 1729
    /**
1730
     * To return a connection to the pool.
1731
     *
1732
     * @param con a connection obtained with getRawConnection(), can be <code>null</code>.
1733
     */
1734
    protected void returnConnection(final Connection con) {
1735
        if (con != null) {
1736
            // if !this.initialShemaSet the out of date cannot be brought up to date
1737
            final boolean unrecoverableOutOfDate;
1738
            synchronized (this) {
73 ilm 1739
                unrecoverableOutOfDate = !this.uptodate.containsKey(con) || !this.initialShemaSet && !this.schemaUptodate.containsKey(con);
17 ilm 1740
            }
1741
            if (isClosed() || unrecoverableOutOfDate)
1742
                // if closed : don't fill the pool (which will have thrown an exception anyway)
1743
                // if we shouldn't set the schema, we must close all previous connections
1744
                // so that we get new ones from the db with the current setting
1745
                this.closeConnection(con);
1746
            else {
1747
                try {
1748
                    // our connectionPool use PoolableConnectionFactory which creates
1749
                    // PoolableConnection whose close() actually does a returnObject()
1750
                    con.close();
1751
                } catch (Exception e) {
1752
                    /* tant pis */
65 ilm 1753
                    Log.get().log(Level.FINE, "Could not return " + con, e);
17 ilm 1754
                }
1755
                if (State.DEBUG)
1756
                    State.INSTANCE.connectionRemoved();
1757
            }
1758
        }
1759
    }
1760
 
1761
    /**
1762
     * To actually close a connection to the db (and remove it from the pool).
1763
     *
1764
     * @param con a connection obtained with getRawConnection(), can be <code>null</code>.
1765
     */
1766
    protected void closeConnection(final Connection con) {
1767
        // Neither BasicDataSource nor PoolingDataSource provide a closeConnection()
1768
        // so we implement one here
1769
        if (con != null) {
1770
            synchronized (this) {
1771
                this.uptodate.remove(con);
73 ilm 1772
                this.schemaUptodate.remove(con);
17 ilm 1773
            }
1774
            try {
1775
                // ATTN this always does _numActive--, so we can't call it multiple times
1776
                // with the same object
1777
                this.connectionPool.invalidateObject(con);
1778
            } catch (Exception e) {
1779
                /* tant pis */
65 ilm 1780
                Log.get().log(Level.FINE, "Could not close " + con, e);
17 ilm 1781
            }
1782
            // the last connection is being returned
1783
            if (this.isClosed() && this.getBorrowedConnectionCount() == 0) {
1784
                noConnectionIsOpen();
1785
            }
1786
        }
1787
    }
1788
 
1789
    /**
73 ilm 1790
     * Invalidates all open connections. This immediately closes idle connections. Borrowed ones are
1791
     * marked as invalid, so that they are closed on return. In other words, after this method
1792
     * returns, no existing connection will be provided.
1793
     */
1794
    public final void invalidateAllConnections() {
1795
        this.invalidateAllConnections(false);
1796
    }
1797
 
1798
    public final void invalidateAllConnections(final boolean preventIdleConnections) {
1799
        // usefull since Evictor of GenericObjectPool might call ensureMinIdle()
1800
        if (preventIdleConnections) {
1801
            this.setMinIdle(0);
1802
            this.setMaxIdle(0);
1803
        }
1804
        synchronized (this) {
1805
            // otherwise nothing to invalidate
1806
            if (this.connectionPool != null) {
1807
                // closes all idle connections
1808
                this.connectionPool.clear();
1809
                // borrowed connections will be closed on return
1810
                this.uptodate.clear();
1811
            }
1812
        }
1813
    }
1814
 
1815
    /**
17 ilm 1816
     * From now on, every new connection will have its default schema set to schemaName.
1817
     *
1818
     * @param schemaName the name of the initial default schema, <code>null</code> to remove any
1819
     *        default schema.
1820
     */
1821
    public void setInitialSchema(String schemaName) {
80 ilm 1822
        if (schemaName != null || this.getSystem().isClearingPathSupported()) {
17 ilm 1823
            this.setInitialSchema(true, schemaName);
80 ilm 1824
        } else if (this.getSystem().isDBPathEmpty()) {
17 ilm 1825
            this.unsetInitialSchema();
1826
        } else
1827
            throw new IllegalArgumentException(this + " cannot have no default schema");
1828
    }
1829
 
65 ilm 1830
    /**
1831
     * From now on, connections won't have their default schema set by this. Of course the SQL
1832
     * server might have set one.
1833
     */
17 ilm 1834
    public void unsetInitialSchema() {
1835
        this.setInitialSchema(false, null);
1836
    }
1837
 
1838
    private final void setInitialSchema(final boolean set, final String schemaName) {
65 ilm 1839
        synchronized (this.setInitialShemaLock) {
1840
            synchronized (this) {
1841
                // even if schemaName no longer exists, and thus the following test would fail, the
1842
                // next initConnection() will correctly fail
1843
                if (this.initialShemaSet == set && CompareUtils.equals(this.initialShema, schemaName))
1844
                    return;
1845
            }
1846
            final Connection newConn;
1847
            if (set) {
1848
                // test if schemaName is valid
1849
                newConn = this.getNewConnection();
1850
                try {
1851
                    this.setSchema(schemaName, newConn);
1852
                } catch (RuntimeException e) {
1853
                    this.closeConnection(newConn);
1854
                    throw e;
1855
                }
1856
                // don't return connection right now otherwise it might be deemed unrecoverable
1857
            } else {
1858
                newConn = null;
1859
            }
1860
            synchronized (this) {
1861
                this.initialShemaSet = set;
1862
                this.initialShema = schemaName;
73 ilm 1863
                this.schemaUptodate.clear();
65 ilm 1864
                if (!set)
1865
                    // by definition we don't want to modify the connection,
1866
                    // so empty the pool, that way new connections will be created
1867
                    // the borrowed ones will be closed when returned
1868
                    this.connectionPool.clear();
1869
                else
73 ilm 1870
                    this.schemaUptodate.put(newConn, null);
65 ilm 1871
            }
1872
            this.returnConnection(newConn);
17 ilm 1873
        }
1874
    }
1875
 
1876
    public synchronized final String getInitialSchema() {
1877
        return this.initialShema;
1878
    }
1879
 
1880
    /**
1881
     * Set the default schema of the current thread's connection. NOTE: pointless if not in
1882
     * {@link #useConnection(ConnectionHandler)} since otherwise a connection will be borrowed then
1883
     * closed.
1884
     *
1885
     * @param schemaName the name of the new default schema.
1886
     */
1887
    public void setSchema(String schemaName) {
1888
        this.setSchema(schemaName, null);
1889
    }
1890
 
1891
    private void setSchema(String schemaName, Connection c) {
1892
        final String q;
1893
        if (this.getSystem() == SQLSystem.MYSQL) {
1894
            if (schemaName == null) {
1895
                if (this.getSchema(c) != null)
1896
                    throw new IllegalArgumentException("cannot unset DATABASE in MySQL");
1897
                else
1898
                    // nothing to do
1899
                    q = null;
1900
            } else
1901
                q = "USE " + schemaName;
73 ilm 1902
        } else if (this.getSystem() == SQLSystem.DERBY) {
17 ilm 1903
            q = "SET SCHEMA " + SQLBase.quoteIdentifier(schemaName);
73 ilm 1904
        } else if (this.getSystem() == SQLSystem.H2) {
1905
            q = "SET SCHEMA " + SQLBase.quoteIdentifier(schemaName);
17 ilm 1906
            // TODO use the line below, but for now it is only used after schema()
1907
            // plus there's no function to read it back
1908
            // q = "set SCHEMA_SEARCH_PATH " + SQLBase.quoteIdentifier(schemaName == null ? "" :
1909
            // schemaName);
1910
        } else if (this.getSystem() == SQLSystem.POSTGRESQL) {
1911
            if (schemaName == null) {
73 ilm 1912
                // SET cannot empty the path
1913
                q = "select set_config('search_path', '', false)";
17 ilm 1914
            } else {
73 ilm 1915
                q = "set session search_path to " + SQLBase.quoteIdentifier(schemaName);
17 ilm 1916
            }
1917
        } else if (this.getSystem() == SQLSystem.MSSQL) {
83 ilm 1918
            if (schemaName == null) {
17 ilm 1919
                throw new IllegalArgumentException("cannot unset default schema in " + this.getSystem());
83 ilm 1920
            } else {
1921
                // ATTN MSSQL apparently hang until the connection that created the schema commits
1922
                q = "ALTER USER " + SQLBase.quoteIdentifier(getUsername()) + " with default_schema = " + SQLBase.quoteIdentifier(schemaName);
1923
            }
73 ilm 1924
        } else {
17 ilm 1925
            throw new UnsupportedOperationException();
73 ilm 1926
        }
17 ilm 1927
 
1928
        if (q != null)
1929
            this.execute(q, null, true, c);
1930
    }
1931
 
1932
    public final String getSchema() {
1933
        return this.getSchema(null);
1934
    }
1935
 
1936
    private String getSchema(Connection c) {
1937
        final String q;
1938
        if (this.getSystem() == SQLSystem.MYSQL)
1939
            q = "select DATABASE(); ";
1940
        else if (this.getSystem() == SQLSystem.DERBY)
1941
            q = "select CURRENT SCHEMA;";
1942
        else if (this.getSystem() == SQLSystem.POSTGRESQL) {
1943
            q = "select (current_schemas(false))[1];";
1944
        } else if (this.getSystem() == SQLSystem.H2) {
1945
            q = "select SCHEMA();";
1946
        } else if (this.getSystem() == SQLSystem.MSSQL) {
1947
            q = "select SCHEMA_NAME();";
1948
        } else
1949
            throw new UnsupportedOperationException();
1950
 
1951
        return (String) this.execute(q, SCALAR_HANDLER, c);
1952
    }
1953
 
132 ilm 1954
    @Override
17 ilm 1955
    public String toString() {
1956
        return this.getUrl();
1957
    }
1958
 
83 ilm 1959
    public final SQLSystem getSystem() {
80 ilm 1960
        return this.sysRoot.getServer().getSQLSystem();
17 ilm 1961
    }
1962
 
1963
    public Object clone() {
80 ilm 1964
        SQLDataSource ds = new SQLDataSource(this.sysRoot);
17 ilm 1965
        ds.setUrl(this.getUrl());
1966
        ds.setUsername(this.getUsername());
1967
        ds.setPassword(this.getPassword());
1968
        ds.setDriverClassName(this.getDriverClassName());
1969
        return ds;
1970
    }
1971
}