View Javadoc

1   package atg.metier.dao.jdbc;
2   
3   import java.sql.Connection;
4   import java.sql.SQLException;
5   import atg.metier.dao.jdbc.exception.ATGDaoBaseIndisponibleException;
6   import atg.service.constante.AtgConstantes;
7   import atg.service.log.AtgLogManager;
8   
9   /**
10   * <p>Titre : Pool de connexions aux bases de données en JDBCs</p>
11   * <p>Description : Gestion de pool de connexion</p>
12   * <p>Copyright : FERRARI Olivier</p>
13   * @author  BOSC Frédéric repris et modifié par FERRARI Olivier
14   * @version 1.0
15   * Ce logiciel est régi par la licence CeCILL soumise au droit français et
16   * respectant les principes de diffusion des logiciels libres. Vous pouvez
17   * utiliser, modifier et/ou redistribuer ce programme sous les conditions
18   * de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
19   *  sur le site http://www.cecill.info.
20   * 
21   * Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
22   * pris connaissance de la licence CeCILL, et que vous en avez accepté les
23   * termes.
24   */
25  
26  public class ATGDaoPoolDataSource
27      extends atg.util.service.ATGBasicClass {
28  
29  //***************************************************************************
30  //                          Attributs
31  //***************************************************************************
32  
33      /**
34       * Référence du pool par défaut
35       */
36      public static final String CSTE_DEFAULT_REFERENCE = "Reference par defaut";
37  
38    /**
39     * Instances uniques (pour une référence donnée) du pool de connections
40     */
41  
42    private static java.util.Hashtable instances = new java.util.Hashtable();
43  
44    /**
45     *  Commande à executer sur la connexion nouvellement créées
46     */
47    private static java.util.Hashtable connexionRequests = new java.util.
48        Hashtable();
49  
50    /**
51     *  nombre de connections maximum crées sur la base de données
52     */
53    private static final int MAX_CONNECTION = 10;
54  
55    /**
56     *  nombre de connections automatiquement crées au démarrage du pool
57     */
58    private static final int START_CONNECTION = 1;
59  
60    /**
61         *  Delai d'attente pour l'obtention d'une connection, exemple 3000 = 3 secondes
62     */
63    private static final int MAX_WAIT = 3000;
64  
65    /**
66     *  delai au dela duquel une connexion est consideree comme perdue, exemple 60000 = 60 secondes
67     */
68    private static final int MAX_CTRL_TIME = 60000;
69  
70    /**
71     * Attribut de la connexion standard
72     */
73    private String DRIVER_NAME = null;
74    private String USER_NAME = null;
75    private String USER_PWD = null;
76    private String DB_NAME = null;
77  
78    /**
79     * liste des connections créées mais non utilisées
80     */
81    private java.util.Vector freeConnections = null;
82  
83    /**
84     * nombre de connections en cours (demandées mais pas encore libérées)
85     */
86    private int checkedOut = 0;
87  
88    /**
89     * driver permettant d'obtenir une connection
90     */
91    protected java.sql.Driver driver = null;
92  
93    /**
94     * référence du pool de données
95     */
96    protected String reference = null;
97  
98    /**
99     * ensemble des connections creees sur la base de donnees
100    */
101   protected java.util.Hashtable ctrlConnexion = null;
102 
103 //***************************************************************************
104 //                          Constructeurs
105 //***************************************************************************
106 
107     /**
108      * Constructeur.
109      * Récupère les informations relatives aux connections.
110      * @param newReference Référence du pool de connection
111      */
112     private ATGDaoPoolDataSource(String newReference) {
113       reference = newReference;
114       ctrlConnexion = new java.util.Hashtable();
115 
116       if (newReference.equals(CSTE_DEFAULT_REFERENCE)) {
117         DRIVER_NAME = atg.service.constante.AtgConstantesWF.DB_DRIVER_NAME;
118         USER_NAME = atg.service.constante.AtgConstantesWF.DB_USER_NAME;
119         USER_PWD = atg.service.constante.AtgConstantesWF.DB_PWD;
120         DB_NAME = atg.service.constante.AtgConstantesWF.DB_DB_NAME;
121       }
122       else {
123         DRIVER_NAME = atg.service.constante.AtgConstantesWF.getValue(
124             newReference +
125             "_DB_DRIVER_NAME");
126         USER_NAME = atg.service.constante.AtgConstantesWF.getValue(newReference +
127             "_DB_USER_NAME");
128         USER_PWD = atg.service.constante.AtgConstantesWF.getValue(newReference +
129             "_DB_PWD");
130         DB_NAME = atg.service.constante.AtgConstantesWF.getValue(newReference +
131             "_DB_DB_NAME");
132       }
133     }
134 
135 //***************************************************************************
136 //                          Méthodes statiques
137 //***************************************************************************
138 
139     /**
140          * Fixe la commande à executer lors de la création d'une nouvelle connexion.
141      * @param reference Référence du pool de connection
142      * @param connexionRequest Commande sql à executer
143      */
144     static public void setConnexionRequest(String reference,
145                                            String connexionRequest) {
146       connexionRequests.put(reference, connexionRequest);
147     }
148 
149   /**
150    * Fixe la commande à executer lors de la création d'une nouvelle connexion.
151    * La référence de la base correspond à la référence par défaut.
152    * @param connexionRequest Commande sql à executer
153    */
154   static public void setConnexionRequest(String connexionRequest) {
155     connexionRequests.put(CSTE_DEFAULT_REFERENCE, connexionRequest);
156   }
157 
158   /**
159    * Renvoie l'instance unique du pool de connections pour la référence donnée.
160    * @param reference Référence du pool de connection
161    * @return StdPoolDataSource Instance d'un pool d'accès aux données
162    */
163   static public ATGDaoPoolDataSource getInstance(String reference) {
164     if ( (instances.get(reference) != null)) {
165       // l'instance pour cette référence existe déjà
166       return (ATGDaoPoolDataSource) (instances.get(reference));
167     }
168     else {
169       // création de l'instance 'reference'
170       ATGDaoPoolDataSource instance = new ATGDaoPoolDataSource(reference);
171       // initialisation de cette instance
172       instance.initPool();
173       // ajout de l'instance à la liste des instances
174       instances.put(reference, instance);
175 
176       return instance;
177     }
178   }
179 
180   /**
181    * Renvoie l'instance unique du pool de connections par défaut.
182    * @return StdPoolDataSource Instance d'un pool d'accès aux données
183    */
184   static public ATGDaoPoolDataSource getInstance() {
185     return getInstance(CSTE_DEFAULT_REFERENCE);
186   }
187 
188 //***************************************************************************
189 //                          Méthodes publiques
190 //***************************************************************************
191 
192     /**
193      * Renvoie une connection à la base de donnees.
194      * @return java.sql.Connection Connection à la base de données
195      */
196     public java.sql.Connection getConnection() throws
197         ATGDaoBaseIndisponibleException {
198       try {
199         java.sql.Connection conn = getConnection(MAX_WAIT);
200         if (conn != null)
201           addCtrlConnexion(conn);
202 
203         return conn;
204       }
205       catch (ATGDaoBaseIndisponibleException exception) {
206         // ecriture d'une trace + renvoie de la valeur 'null'
207         logSevere("Impossible de fournir une connection correcte : " +
208                   exception.getCause() + " / " + exception.getMessage());
209         throw new ATGDaoBaseIndisponibleException();
210       }
211     }
212 
213 //***************************************************************************
214 //                          Méthodes protected
215 //***************************************************************************
216 
217     /**
218      * Initialisation du pool.
219      * Appelé lors de l'instanciation.
220      */
221     protected void initPool() {
222       freeConnections = new java.util.Vector();
223 
224       // un certain nombre de connections sont créees automatiquement
225       // lors de l'instanciation du pool
226       for (int i = 0; i < START_CONNECTION; i++) {
227         try {
228           freeConnections.addElement(newConnection());
229         }
230         catch (ATGDaoBaseIndisponibleException exception) {
231           logSevere("Impossible d'initialiser le pool de Connection !" +
232                     exception.getCause() + " / " + exception.getMessage());
233         }
234       }
235     }
236 
237   /**
238    * Renvoie une connection à la base de données.
239    * Attend au maximum 'timeout' millisecondes l'obtention de la connection.
240    * @param timeout Temps d'attente maximum de la connection
241    * @throws java.sql.SQLException Exception SQL
242    * @return java.sql.Connection Connection à la base de données
243    */
244   protected synchronized java.sql.Connection getConnection(long timeout) throws
245       ATGDaoBaseIndisponibleException {
246     long startTime = System.currentTimeMillis(); // heure d'appel à la fonction
247     long remaining = timeout; // temps restant
248     java.sql.Connection conn = null; // connection à la base
249 
250     while ( (conn = getPooledConnection()) == null) {
251       // la connection n'a pas ete obtenue...
252       try {
253         // attente du delai souhaité
254         // le processus est reveillé des qu'une connection se libère
255         logFinest("Attente d'une connection (" + timeout + ").");
256         wait(timeout);
257       }
258       catch (InterruptedException exception) {
259         // le processus d'attente est interrompu
260       }
261 
262       // calcul du temps restant - temps que l'on peut encore passer
263       // à attendre
264       remaining = timeout - (System.currentTimeMillis() - startTime);
265 
266       if (remaining <= 0) {
267         // le delai d'attente est expiré
268         throw new ATGDaoBaseIndisponibleException("getConnection() time-out");
269       }
270     }
271     // vérifie si la connexion demeure valide
272     if (!isConnectionOk(conn)) {
273       // problème de connexion => refaire une tentative avec le delai restant
274       return getConnection(remaining);
275     }
276 
277     // mise a jour des informations de suivi
278     checkedOut++;
279 
280     return conn;
281   }
282 
283   /**
284    * Contrôle si la connection specifiée est valide.
285    * @param conn Connection à tester
286    * @return boolean Connection valide ou non
287    */
288   protected boolean isConnectionOk(java.sql.Connection conn) {
289     java.sql.Statement testStmt = null;
290 
291     // deux tests sonr réalisé sur la connection
292     try {
293       // 1 - connection fermée ?
294       if (!conn.isClosed()) {
295         // 2 - est il possible de créer un statement ?
296         testStmt = conn.createStatement();
297         testStmt.close();
298       }
299       else
300         return false;
301     }
302     catch (java.sql.SQLException exceptionConnection) {
303       if (testStmt != null) {
304         try {
305           testStmt.close();
306         }
307         catch (java.sql.SQLException exceptionStatement) {
308           // laissé vide intensionnellement
309         }
310       }
311 
312       return false;
313     }
314 
315     return true;
316   }
317 
318   /**
319    * Renvoie une connection.
320    * @throws ATGDaoBaseIndisponibleException
321    * @return java.sql.Connection Connection à la base de données
322    */
323   protected java.sql.Connection getPooledConnection() throws
324       ATGDaoBaseIndisponibleException {
325     java.sql.Connection conn = null;
326 
327     // gestion des traces
328     logFinest("Une connection est demandee au pool.");
329 
330     // existe t'il une connection de libre ?
331     if (freeConnections.size() > 0) {
332       // la première est récupèrée
333       conn = (java.sql.Connection) freeConnections.firstElement();
334       freeConnections.removeElementAt(0);
335     }
336     else {
337       // le nombre maximum des connexions creees a ete atteint
338       // => essai de liberation
339       if (checkedOut == MAX_CONNECTION) {
340         logWarning(
341             "Attention, le nombre maximum de connexions creees sur la base est atteint : " +
342             MAX_CONNECTION);
343         ckeckCtrlConnexion();
344       }
345 
346       // rmq : pas de 'else' car ckeckCtrlConnexion() peut avoir libere des connexions
347       // et mis a jour 'checkedOut'
348 
349       // il n'existe pas de connection de libre => on en crée une nouvelle
350       // (dans la limite)
351       if (checkedOut < MAX_CONNECTION)
352         conn = newConnection();
353     }
354 
355     return conn;
356   }
357 
358   /**
359    * Création d'une nouvelle connection.
360    * @throws ATGDaoBaseIndisponibleException
361    * @return java.sql.Connection Nouvelle connection à la base de données
362    */
363   protected java.sql.Connection newConnection() throws
364       ATGDaoBaseIndisponibleException {
365     // instantiation du driver (si nécèssaire)
366     if (driver == null) {
367       try {
368         Class clazz = Class.forName(DRIVER_NAME);
369         logFinest("Classe du driver trouvee : " + clazz.toString());
370         driver = (java.sql.Driver) clazz.newInstance();
371       }
372       catch (Throwable exception) {
373         logSevere("Impossible de creer l'instance : " + DRIVER_NAME + "  " +
374                   exception.getMessage());
375         throw new ATGDaoBaseIndisponibleException(exception.getMessage());
376       }
377     }
378 
379     // initialisation des paramètres de connection
380     java.util.Properties props = new java.util.Properties();
381     if (USER_NAME != null)
382       props.put("user", USER_NAME);
383     if (USER_PWD != null)
384       props.put("password", USER_PWD);
385 
386       // obtention d'une connection
387     Connection conn = null;
388     try {
389       conn = driver.connect(DB_NAME, props);
390     }
391     catch (SQLException ex) {
392       ex.printStackTrace();
393       logSevere("Connection JDBC impossible à créer  par le pool " +
394                 DRIVER_NAME + " " + USER_NAME + " " + USER_PWD + " " + DB_NAME);
395       throw new ATGDaoBaseIndisponibleException("Impossible de créer une connexion par pool");
396     }
397 
398     // gestion des traces
399     logFinest("Une nouvelle connection JDBC est creee par le pool " +
400               DRIVER_NAME + " " + USER_NAME + " " + USER_PWD + " " + DB_NAME);
401 
402     if (conn == null)
403       logSevere("Impossible de creer une connection : " + DRIVER_NAME + " " +
404                 USER_NAME + " " + USER_PWD + " " + DB_NAME);
405     else {
406       // la méthode à appliquer sur toute nouvelle connexion est executée
407       // (si nécessaire)
408       try {
409         if (connexionRequests.get(reference) != null) {
410           java.sql.PreparedStatement statement = conn.prepareStatement( (String) (
411               connexionRequests.get(reference)));
412           statement.executeUpdate();
413           logFinest(
414               "La commande d'alteration de la connexion a ete realisee : " +
415               (String) (connexionRequests.get(reference)) + ".");
416         }
417         else
418           logFinest(
419               "Il n'existe pas de commande d'alteration sur la connexion.");
420       }
421       catch (Exception exception) {
422         logSevere(
423             "Impossible d'effectuer la requete d'alteration de la connexion !  " +
424             exception.getMessage());
425       }
426     }
427 
428     return conn;
429   }
430 
431   /**
432    * Libére la connection spécifiée.
433    * @param conn Connection à libérer
434    */
435   public synchronized void release(java.sql.Connection conn) {
436     // gestion des traces
437     logFinest("Une connection JDBC est liberee : " +
438               (freeConnections.size() + 1) + " connections sont libres.");
439 
440     // la connection est recyclee - ajout à la liste des connections libres
441     freeConnections.addElement(conn);
442     checkedOut--;
443 
444     // on signale aux processus qui 'dorment' (attente d'une connection)
445     // qu'une connection vient de se liberer
446     notifyAll();
447   }
448 
449   /**
450    * Retourne le nombre de bases de données.
451    * @return int Nombre de bases de données
452    */
453   public static int getNumberOfDataBase() {
454     if (instances == null)
455       return 0;
456     else
457       return instances.size();
458   }
459 
460   /**
461    * Ajoute une connexion au mécanisme de contrôle.
462    * @param conn Connexion à ajouter
463    */
464   public void addCtrlConnexion(java.sql.Connection conn) {
465     ctrlConnexion.put(conn, new Long(System.currentTimeMillis()));
466   }
467 
468   /**
469    * Vérifie si toutes les connexions ont bien été rendues au pool.
470    */
471   public void ckeckCtrlConnexion() {
472     // liste des connexions
473     java.util.Enumeration conns = ctrlConnexion.keys();
474 
475     // parcours des connexions et verification de leur validité
476     do {
477       Object conn = conns.nextElement();
478       long delta = System.currentTimeMillis() -
479           ( (Long) (ctrlConnexion.get(conn))).longValue();
480       if ( (!freeConnections.contains(conn)) && (delta > MAX_CTRL_TIME)) {
481         // la connexion est consideree comme perdue
482         // => on la libere
483         ctrlConnexion.remove(conn);
484         logSevere("La connexion " + conn +
485             " est consideree comme perdue : elle n'a pas ete rendue au pool.");
486         // le plus important : mise à jour du compteur de connexions
487         checkedOut--;
488 
489         // fermeture de la connexion
490         try {
491           ( (java.sql.Connection) conn).close();
492         }
493         catch (java.sql.SQLException sqlException) {
494           // laissé vide intensionnellement
495         }
496       }
497     }
498     while (conns.hasMoreElements());
499   }
500 
501   /**
502    * Retourne le temps maximum d'attente d'une connexion par défaut.
503    * @return int Temps maximum d'attente d'une connexion par défaut.
504    */
505   public static int getMaxWait() {
506     return MAX_WAIT;
507   }
508 
509   /**
510    * Retourne le nombre maximun de connexions créées par le pool.
511    * @return int Nombre maximun de connexions créées par le pool.
512    */
513   public static int getMaxConnection() {
514     return MAX_CONNECTION;
515   }
516 
517   /**
518    * Retourne le nombre de connexions que créait le pool au démarrage.
519    * @return int Nombre de connexions que créait le pool au démarrage.
520    */
521   public static int getStartConnection() {
522     return START_CONNECTION;
523   }
524 
525   protected static java.util.logging.Logger logger_ = null;
526   /**
527    * Ecriture des logs
528    */
529 
530   protected java.util.logging.Logger getLogger() {
531     if (logger_ == null)
532       logger_ = AtgLogManager.getLog(AtgConstantes.
533                                      ATG_LOG_CATEGORY_METIER_DAO_JDBC);
534 
535     return logger_;
536   }
537 
538 }