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;
21  
22  import io.netty.util.HashedWheelTimer;
23  import io.netty.util.Timer;
24  import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
25  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
26  import org.waarp.common.database.model.DbModel;
27  import org.waarp.common.database.model.DbModelFactory;
28  import org.waarp.common.database.model.DbType;
29  import org.waarp.common.database.model.EmptyDbModel;
30  import org.waarp.common.guid.GUID;
31  import org.waarp.common.logging.WaarpLogger;
32  import org.waarp.common.logging.WaarpLoggerFactory;
33  import org.waarp.common.utility.WaarpThreadFactory;
34  
35  import java.sql.Connection;
36  import java.sql.SQLException;
37  import java.util.ConcurrentModificationException;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.TimeUnit;
40  
41  /**
42   * Class for access to Database
43   *
44   * Partially deprecated (only in Waarp R66, except Rest, Http and Monitoring
45   * and special cases using PreparedStatements but not other components)
46   */
47  public class DbAdmin {
48    /**
49     * Internal Logger
50     */
51    private static final WaarpLogger logger =
52        WaarpLoggerFactory.getLogger(DbAdmin.class);
53  
54    public static final int RETRYNB = 3;
55  
56    public static final long WAITFORNETOP = 100;
57  
58    /**
59     * Database type
60     */
61    protected final DbType typeDriver;
62  
63    /**
64     * DbModel
65     */
66    private final DbModel dbModel;
67  
68    /**
69     * DB Server
70     */
71    private final String server;
72  
73    /**
74     * DB User
75     */
76    private final String user;
77  
78    /**
79     * DB Password
80     */
81    private final String passwd;
82  
83    /**
84     * Is this DB Admin Read Only
85     */
86    private boolean isReadOnly;
87  
88    /**
89     * session is the Session object for all type of requests
90     */
91    private DbSession session;
92    /**
93     * Number of HttpSession
94     */
95    private static int nbHttpSession;
96  
97    protected static final Timer dbSessionTimer =
98        new HashedWheelTimer(new WaarpThreadFactory("TimerClose"), 50,
99                             TimeUnit.MILLISECONDS, 1024);
100 
101   /**
102    * @return the session
103    */
104   public DbSession getSession() {
105     return session;
106   }
107 
108   /**
109    * @param session the session to set
110    */
111   public final void setSession(final DbSession session) {
112     this.session = session;
113   }
114 
115   /**
116    * @return True if the connection is ReadOnly
117    */
118   public final boolean isReadOnly() {
119     return isReadOnly;
120   }
121 
122   /**
123    * Validate connection
124    *
125    * @throws WaarpDatabaseNoConnectionException
126    */
127   public final void validConnection()
128       throws WaarpDatabaseNoConnectionException {
129     try {
130       dbModel.validConnection(getSession());
131     } catch (final WaarpDatabaseNoConnectionException e) {
132       getSession().setDisActive(true);
133       throw e;
134     }
135     getSession().setDisActive(false);
136   }
137 
138   /**
139    * Use a default server for basic connection. Later on, specific connection
140    * to
141    * database for the scheme that
142    * provides access to the table R66DbIndex for one specific Legacy could be
143    * done.
144    * <p>
145    * A this time, only one driver is possible! If a new driver is needed,
146    * then
147    * we need to create a new DbSession
148    * object. Be aware that DbSession.initialize should be call only once for
149    * each driver, whatever the number of
150    * DbSession objects that could be created (=> need a hashtable for
151    * specific
152    * driver when created). Also, don't
153    * know if two drivers at the same time (two different DbSession) is
154    * allowed
155    * by JDBC.
156    *
157    * @param model
158    * @param server
159    * @param user
160    * @param passwd
161    *
162    * @throws WaarpDatabaseNoConnectionException
163    */
164   public DbAdmin(final DbModel model, final String server, final String user,
165                  final String passwd)
166       throws WaarpDatabaseNoConnectionException {
167     this.server = server;
168     this.user = user;
169     this.passwd = passwd;
170     dbModel = model;
171     typeDriver = model.getDbType();
172     if (typeDriver == null) {
173       logger.error("Cannot find TypeDriver");
174       throw new WaarpDatabaseNoConnectionException(
175           "Cannot find database drive");
176     }
177     setSession(new DbSession(this, false));
178     getSession().setAdmin(this);
179     isReadOnly = false;
180     validConnection();
181     getSession().useConnection(); // default since this is the top connection
182   }
183 
184   /**
185    * Use a default server for basic connection. Later on, specific connection
186    * to
187    * database for the scheme that
188    * provides access to the table R66DbIndex for one specific Legacy could be
189    * done.
190    * <p>
191    * A this time, only one driver is possible! If a new driver is needed,
192    * then
193    * we need to create a new DbSession
194    * object. Be aware that DbSession.initialize should be call only once for
195    * each driver, whatever the number of
196    * DbSession objects that could be created (=> need a hashtable for
197    * specific
198    * driver when created). Also, don't
199    * know if two drivers at the same time (two different DbSession) is
200    * allowed
201    * by JDBC.
202    *
203    * @param model
204    * @param server
205    * @param user
206    * @param passwd
207    * @param write
208    *
209    * @throws WaarpDatabaseSqlException
210    * @throws WaarpDatabaseNoConnectionException
211    */
212   public DbAdmin(final DbModel model, final String server, final String user,
213                  final String passwd, final boolean write)
214       throws WaarpDatabaseNoConnectionException {
215     this.server = server;
216     this.user = user;
217     this.passwd = passwd;
218     dbModel = model;
219     typeDriver = model.getDbType();
220     if (typeDriver == null) {
221       logger.error("Cannot find TypeDriver");
222       throw new WaarpDatabaseNoConnectionException(
223           "Cannot find database driver");
224     }
225     if (write) {
226       for (int i = 0; i < RETRYNB; i++) {
227         try {
228           setSession(new DbSession(this, false));
229         } catch (final WaarpDatabaseNoConnectionException e) {
230           logger.warn("Attempt of connection in error: " + i + " : {}",
231                       e.getMessage());
232           continue;
233         }
234         isReadOnly = false;
235         getSession().setAdmin(this);
236         validConnection();
237         getSession().useConnection(); // default since this is the top
238         // connection
239         return;
240       }
241     } else {
242       for (int i = 0; i < RETRYNB; i++) {
243         try {
244           setSession(new DbSession(this, true));
245         } catch (final WaarpDatabaseNoConnectionException e) {
246           logger.warn("Attempt of connection in error: " + i + " : {}",
247                       e.getMessage());
248           continue;
249         }
250         isReadOnly = true;
251         getSession().setAdmin(this);
252         validConnection();
253         getSession().useConnection(); // default since this is the top
254         // connection
255         return;
256       }
257     }
258     setSession(null);
259     logger.error("Cannot connect to Database!");
260     throw new WaarpDatabaseNoConnectionException("Cannot connect to database");
261   }
262 
263   /**
264    * Empty constructor for no Database support (very thin client)
265    */
266   public DbAdmin() {
267     // not true but to enable pseudo database functions
268     typeDriver = DbType.none;
269     DbModelFactory.classLoaded.add(DbType.none.name());
270     dbModel = new EmptyDbModel();
271     server = null;
272     user = null;
273     passwd = null;
274   }
275 
276   /**
277    * Close the underlying session. Can be call even for connection given from
278    * the constructor
279    * DbAdmin(Connection, boolean).
280    */
281   public final void close() {
282     if (getSession() != null) {
283       getSession().endUseConnection(); // default since this is the top
284       // connection
285       getSession().forceDisconnect();
286       setSession(null);
287     }
288   }
289 
290   /**
291    * Commit on connection (since in autocommit, should not be used)
292    *
293    * @throws WaarpDatabaseNoConnectionException
294    * @throws WaarpDatabaseSqlException
295    */
296   public final void commit()
297       throws WaarpDatabaseSqlException, WaarpDatabaseNoConnectionException {
298     if (getSession() != null) {
299       getSession().commit();
300     }
301   }
302 
303   /**
304    * @return the server
305    */
306   public final String getServer() {
307     return server;
308   }
309 
310   /**
311    * @return the user
312    */
313   public final String getUser() {
314     return user;
315   }
316 
317   /**
318    * @return the passwd
319    */
320   public final String getPasswd() {
321     return passwd;
322   }
323 
324   /**
325    * @return the associated dbModel
326    */
327   public final DbModel getDbModel() {
328     return dbModel;
329   }
330 
331   /**
332    * @return the typeDriver
333    */
334   public final DbType getTypeDriver() {
335     return typeDriver;
336   }
337 
338   @Override
339   public String toString() {
340     return "Admin: " + typeDriver.name() + ':' + server + ':' + user + ':' +
341            (passwd == null? 0 : passwd.length());
342   }
343 
344   /**
345    * List all Connection to enable the close call on them
346    */
347   private static final ConcurrentHashMap<GUID, DbSession> listConnection =
348       new ConcurrentHashMap<org.waarp.common.guid.GUID, DbSession>();
349 
350   /**
351    * Increment nb of Http Connection
352    */
353   public static void incHttpSession() {
354     nbHttpSession++;
355   }
356 
357   /**
358    * Decrement nb of Http Connection
359    */
360   public static void decHttpSession() {
361     nbHttpSession--;
362   }
363 
364   /**
365    * @return the nb of Http Connection
366    */
367   public static int getHttpSession() {
368     return nbHttpSession;
369   }
370 
371   /**
372    * Add a Connection into the list
373    *
374    * @param id
375    * @param session
376    */
377   public static void addConnection(final GUID id, final DbSession session) {
378     listConnection.put(id, session);
379   }
380 
381   /**
382    * Remove a Connection from the list
383    *
384    * @param id Id of the connection
385    */
386   public static void removeConnection(final GUID id) {
387     listConnection.remove(id);
388   }
389 
390   /**
391    * @return the number of connection (so number of network channels)
392    */
393   public static int getNbConnection() {
394     return listConnection.size() - 1;
395   }
396 
397   /**
398    * Close all database connections
399    */
400   public static void closeAllConnection() {
401     logger.debug("DEBUG Close All Connections");
402     for (final DbSession session : listConnection.values()) {
403       logger.debug("Close (all) Db Conn: {}", session.getInternalId());
404       try {
405         final Connection connection = session.getConn();
406         if (connection != null) {
407           connection.close();
408         }
409       } catch (final SQLException ignored) {
410         // nothing
411       } catch (final ConcurrentModificationException ignored) {
412         // nothing
413       }
414     }
415     listConnection.clear();
416     for (final DbModel dbModel : DbModelFactory.dbModels) {
417       if (dbModel != null) {
418         dbModel.releaseResources();
419       }
420     }
421     dbSessionTimer.stop();
422   }
423 
424   /**
425    * Check all database connections and try to reopen them if disActive
426    */
427   public static void checkAllConnections() {
428     for (final DbSession session : listConnection.values()) {
429       try {
430         session.checkConnection();
431       } catch (final WaarpDatabaseNoConnectionException e) {
432         logger.error("Database Connection cannot be reinitialized");
433       }
434     }
435   }
436 
437   /**
438    * @return True if this driver allows Thread Shared Connexion (concurrency
439    *     usage)
440    */
441   public final boolean isCompatibleWithThreadSharedConnexion() {
442     return typeDriver != DbType.MariaDB && typeDriver != DbType.MySQL &&
443            typeDriver != DbType.Oracle && typeDriver != DbType.none;
444   }
445 }