View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.database.model;
21  
22  import org.postgresql.util.PSQLException;
23  import org.waarp.common.database.DbAdmin;
24  import org.waarp.common.database.DbConstant;
25  import org.waarp.common.database.DbSession;
26  import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
27  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
28  import org.waarp.common.logging.WaarpLogger;
29  import org.waarp.common.logging.WaarpLoggerFactory;
30  
31  import java.sql.Connection;
32  import java.sql.DriverManager;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.sql.Statement;
36  import java.util.ConcurrentModificationException;
37  
38  /**
39   * This Abstract class regroups common methods for all implementation classes.
40   */
41  public abstract class DbModelAbstract implements DbModel {
42    /**
43     * Max size in Binary mode, store in BASE64 mode (so x2)
44     */
45    public static final int MAX_BINARY = 255;
46    /**
47     * Max size in LONGVARCHAR (except ORACLE = 4000)
48     */
49    public static final int MAX_LONGVARCHAR = 12 * 1024;
50    /**
51     * Max Key size in VARCHAR
52     */
53    public static final int MAX_KEY_VARCHAR = 255;
54    /**
55     * Max VARCHAR size (except ORACLE = 4000)
56     */
57    public static final int MAX_VARCHAR = 8096;
58    protected static DbTypeResolver dbTypeResolver;
59  
60    public abstract static class DbTypeResolver {
61      public abstract String getType(int sqlType);
62  
63      public String getCreateTable() {
64        return "CREATE TABLE ";
65      }
66  
67      public String getPrimaryKey() {
68        return " PRIMARY KEY ";
69      }
70  
71      public String getNotNull() {
72        return " NOT NULL ";
73      }
74  
75      public String getCreateIndex() {
76        return "CREATE INDEX ";
77      }
78  
79      public abstract DbType getDbType();
80    }
81  
82    public DbTypeResolver getDbTypeResolver() {
83      return dbTypeResolver;
84    }
85  
86    /**
87     * Internal Logger
88     */
89    private static final WaarpLogger logger =
90        WaarpLoggerFactory.getLogger(DbModelAbstract.class);
91    private static final String CANNOT_CONNECT_TO_DATABASE =
92        "Cannot connect to database";
93  
94    /**
95     * Recreate the disActive session
96     *
97     * @param dbSession
98     *
99     * @throws WaarpDatabaseNoConnectionException
100    */
101   private void recreateSession(final DbSession dbSession)
102       throws WaarpDatabaseNoConnectionException {
103     DbAdmin admin = dbSession.getAdmin();
104     if (admin == null) {
105       if (dbSession.isAutoCommit()) {
106         admin = DbConstant.admin;
107       } else {
108         admin = DbConstant.noCommitAdmin;
109       }
110     }
111     final DbSession newdbSession;
112     newdbSession = new DbSession(admin, dbSession.isReadOnly());
113     try {
114       if (dbSession.getConn() != null) {
115         dbSession.getConn().close();
116       }
117     } catch (final SQLException ignored) {
118       // nothing
119     } catch (final ConcurrentModificationException ignored) {
120       // nothing
121     }
122     dbSession.setConn(newdbSession.getConn());
123     DbAdmin.addConnection(dbSession.getInternalId(), dbSession);
124     DbAdmin.removeConnection(newdbSession.getInternalId());
125     logger.warn("Database Connection lost: database connection reopened");
126   }
127 
128   /**
129    * Internal use for closing connection while validating it
130    *
131    * @param dbSession
132    */
133   protected final void closeInternalConnection(final DbSession dbSession) {
134     try {
135       if (dbSession.getConn() != null) {
136         dbSession.getConn().close();
137       }
138     } catch (final SQLException ignored) {
139       // nothing
140     } catch (final ConcurrentModificationException ignored) {
141       // nothing
142     }
143     dbSession.setDisActive(true);
144     DbAdmin.removeConnection(dbSession.getInternalId());
145   }
146 
147   @Override
148   public final void validConnection(final DbSession dbSession)
149       throws WaarpDatabaseNoConnectionException {
150     // try to limit the number of check!
151     synchronized (this) {
152       if (dbSession.getConn() == null) {
153         throw new WaarpDatabaseNoConnectionException(
154             CANNOT_CONNECT_TO_DATABASE);
155       }
156       try {
157         if (!dbSession.getConn().isClosed() &&
158             !dbSession.getConn().isValid(DbConstant.VALIDTESTDURATION)) {
159           // Give a try by closing the current connection
160           throw new SQLException(CANNOT_CONNECT_TO_DATABASE);
161         }
162         dbSession.setDisActive(false);
163       } catch (final SQLException e2) {
164         dbSession.setDisActive(true);
165         // Might be unsupported so switch to SELECT 1 way
166         if (e2 instanceof PSQLException) {
167           validConnectionSelect(dbSession);
168           return;
169         }
170         if (subValidationConnection(dbSession, e2)) {
171           return;
172         }
173         closeInternalConnection(dbSession);
174         throw new WaarpDatabaseNoConnectionException(CANNOT_CONNECT_TO_DATABASE,
175                                                      e2);
176       }
177     }
178   }
179 
180   private boolean subValidationConnection(final DbSession dbSession,
181                                           final SQLException e2)
182       throws WaarpDatabaseNoConnectionException {
183     try {
184       try {
185         recreateSession(dbSession);
186       } catch (final WaarpDatabaseNoConnectionException e) {
187         closeInternalConnection(dbSession);
188         throw e;
189       }
190       try {
191         if (!dbSession.getConn().isValid(DbConstant.VALIDTESTDURATION)) {
192           // Not ignored
193           closeInternalConnection(dbSession);
194           throw new WaarpDatabaseNoConnectionException(
195               CANNOT_CONNECT_TO_DATABASE, e2);
196         }
197       } catch (final SQLException e) {
198         closeInternalConnection(dbSession);
199         throw new WaarpDatabaseNoConnectionException(CANNOT_CONNECT_TO_DATABASE,
200                                                      e);
201       }
202       dbSession.setDisActive(false);
203       dbSession.recreateLongTermPreparedStatements();
204       return true;
205     } catch (final WaarpDatabaseSqlException e1) {
206       // ignore and will send a No Connection error
207     }
208     return false;
209   }
210 
211   protected final void validConnectionSelect(final DbSession dbSession)
212       throws WaarpDatabaseNoConnectionException {
213     // try to limit the number of check!
214     synchronized (this) {
215       Statement stmt = null;
216       try {
217         stmt = dbSession.getConn().createStatement();//NOSONAR
218         if (stmt.execute(validConnectionString())) {
219           ResultSet set = null;
220           try {
221             set = stmt.getResultSet();
222             if (!set.next()) {
223               closingStatement(stmt);
224               // Give a try by closing the current connection
225               throw new SQLException(CANNOT_CONNECT_TO_DATABASE);
226             }
227           } finally {
228             if (set != null) {
229               set.close();
230             }
231           }
232         }
233         dbSession.setDisActive(false);
234       } catch (final SQLException e2) {
235         dbSession.setDisActive(true);
236         stmt = subValidConnectionSelect(dbSession, stmt);
237         if (stmt == null) {
238           return;
239         }
240         closeInternalConnection(dbSession);
241         throw new WaarpDatabaseNoConnectionException(CANNOT_CONNECT_TO_DATABASE,
242                                                      e2);
243       } finally {
244         closingStatement(stmt);
245       }
246     }
247   }
248 
249   private void closingStatement(final Statement stmt) {
250     try {
251       if (stmt != null) {
252         stmt.close();
253       }
254     } catch (final SQLException e) {
255       // ignore
256     }
257   }
258 
259   private Statement subValidConnectionSelect(final DbSession dbSession,
260                                              Statement stmt)
261       throws WaarpDatabaseNoConnectionException {
262     try {
263       try {
264         recreateSession(dbSession);
265       } catch (final WaarpDatabaseNoConnectionException e) {
266         closeInternalConnection(dbSession);
267         throw e;
268       }
269       closingStatement(stmt);
270       try {
271         stmt = dbSession.getConn().createStatement();//NOSONAR
272       } catch (final SQLException e) {
273         // Not ignored
274         closeInternalConnection(dbSession);
275         throw new WaarpDatabaseNoConnectionException(CANNOT_CONNECT_TO_DATABASE,
276                                                      e);
277       }
278       try {
279         if (stmt.execute(validConnectionString())) {
280           ResultSet set = null;
281           try {
282             set = stmt.getResultSet();
283             if (!set.next()) {
284               closingStatement(stmt);
285               closeInternalConnection(dbSession);
286               throw new WaarpDatabaseNoConnectionException(
287                   CANNOT_CONNECT_TO_DATABASE);
288             }
289           } finally {
290             if (set != null) {
291               set.close();
292             }
293           }
294         }
295       } catch (final SQLException e) {
296         closingStatement(stmt);
297         closeInternalConnection(dbSession);
298         throw new WaarpDatabaseNoConnectionException(CANNOT_CONNECT_TO_DATABASE,
299                                                      e);
300       }
301       dbSession.setDisActive(false);
302       dbSession.recreateLongTermPreparedStatements();
303       closingStatement(stmt);
304       return null;
305     } catch (final WaarpDatabaseSqlException e1) {
306       // ignore and will send a No Connection error
307     }
308     return stmt;
309   }
310 
311   /**
312    * @return the associated String to validate the connection (as "select 1
313    *     from
314    *     dual")
315    */
316   protected abstract String validConnectionString();
317 
318   @Override
319   public Connection getDbConnection(final String server, final String user,
320                                     final String passwd) throws SQLException {
321     // Default implementation
322     return DriverManager.getConnection(server, user, passwd);
323   }
324 
325   @Override
326   public void releaseResources() {
327   }
328 
329   @Override
330   public int currentNumberOfPooledConnections() {
331     return DbAdmin.getNbConnection();
332   }
333 
334 }