View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.util.sql;
35  
36  import fr.paris.lutece.portal.service.plugin.Plugin;
37  import fr.paris.lutece.portal.service.util.AppLogService;
38  
39  import java.sql.Connection;
40  import java.sql.SQLException;
41  
42  import java.util.HashMap;
43  import java.util.Map;
44  
45  /**
46   * Class to manage transactions
47   */
48  public final class TransactionManager
49  {
50      private static final String DEFAULT_POOL_NAME = "portal";
51      private static ThreadLocal<Map<String, MultiPluginTransaction>> _tlTransactions = new ThreadLocal<>( );
52  
53      /**
54       * Default constructor
55       */
56      private TransactionManager( )
57      {
58          // Do nothing
59      }
60  
61      /**
62       * Begin a transaction for the current thread using the pool of a specific plugin with the default transaction isolation. The default transaction isolation
63       * is {@link Connection#TRANSACTION_READ_COMMITTED }.<br>
64       * Note that only one transaction may be active at a time by pool for each thread.
65       * 
66       * @param plugin
67       *            The plugin to use the pool of, or null to use the default pool.
68       */
69      public static void beginTransaction( Plugin plugin )
70      {
71          beginTransaction( plugin, Connection.TRANSACTION_READ_COMMITTED );
72      }
73  
74      /**
75       * Begin a transaction for the current thread using the pool of a specific plugin
76       * 
77       * @param plugin
78       *            The plugin to use the pool of, or null to use the default pool.
79       * @param nTransactionIsolation
80       *            The transaction isolation. View {@link Connection#setTransactionIsolation(int) } to view the different available transaction isolations.
81       */
82      public static void beginTransaction( Plugin plugin, int nTransactionIsolation )
83      {
84          Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
85          MultiPluginTransaction transaction = null;
86  
87          if ( mapTransactions == null )
88          {
89              mapTransactions = new HashMap<>( );
90              _tlTransactions.set( mapTransactions );
91          }
92          else
93          {
94              transaction = mapTransactions.get( getPluginPool( plugin ) );
95          }
96  
97          if ( ( transaction == null ) || ( transaction.getNbTransactionsOpened( ) <= 0 ) )
98          {
99              transaction = new MultiPluginTransaction( plugin );
100 
101             try
102             {
103                 transaction.getConnection( ).setTransactionIsolation( nTransactionIsolation );
104             }
105             catch( SQLException e )
106             {
107                 AppLogService.error( e.getMessage( ), e );
108             }
109 
110             mapTransactions.put( getPluginPool( plugin ), transaction );
111         }
112         else
113         {
114             // A transaction has already been opened for this pool,
115             // so we save that information to prevent the next call to the commit method to close the transaction.
116             transaction.incrementNbTransactionsOpened( );
117         }
118     }
119 
120     /**
121      * Get the current transaction for the pool of a given plugin.
122      * 
123      * @param plugin
124      *            The plugin to use the pool of, or null to use the default pool.
125      * @return The transaction, or null if no transaction is currently running.
126      */
127     public static MultiPluginTransaction getCurrentTransaction( Plugin plugin )
128     {
129         Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
130 
131         if ( mapTransactions != null )
132         {
133             return mapTransactions.get( getPluginPool( plugin ) );
134         }
135 
136         return null;
137     }
138 
139     /**
140      * Commit the transaction associated to the pool of a given plugin.
141      * 
142      * @param plugin
143      *            The plugin associated to the pool to commit the transaction of, or null to use the default pool.
144      */
145     public static void commitTransaction( Plugin plugin )
146     {
147         Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
148 
149         if ( mapTransactions != null )
150         {
151             String strPoolName = getPluginPool( plugin );
152             MultiPluginTransaction transaction = mapTransactions.get( strPoolName );
153 
154             if ( transaction != null )
155             {
156                 // If the number of transactions opened is 1 or less, then we commit the transaction
157                 if ( transaction.getNbTransactionsOpened( ) <= 1 )
158                 {
159                     transaction.commit( );
160                     mapTransactions.remove( strPoolName );
161                 }
162                 else
163                 {
164                     // Otherwise, we decrement the number
165                     transaction.decrementNbTransactionsOpened( );
166                 }
167             }
168         }
169     }
170 
171     /**
172      * Roll back a transaction associated to the pool of a given plugin. Note that any plugin can roll a transaction back.
173      * 
174      * @param plugin
175      *            The plugin associated to the pool to roll back the transaction of, or null to use the default pool.
176      */
177     public static void rollBack( Plugin plugin )
178     {
179         rollBack( plugin, null );
180     }
181 
182     /**
183      * Roll back a transaction associated to the pool of a given plugin with an exception.
184      * 
185      * @param plugin
186      *            The plugin associated to the pool to roll back the transaction of, or null to use the default pool.
187      * @param e
188      *            The exception to associates with the roll back.
189      */
190     public static void rollBack( Plugin plugin, Exception e )
191     {
192         Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
193 
194         if ( mapTransactions != null )
195         {
196             String strPoolName = getPluginPool( plugin );
197             MultiPluginTransaction transaction = mapTransactions.get( strPoolName );
198 
199             // We roll back the transaction, no matter how much transactions has been opened.
200             if ( transaction != null )
201             {
202                 transaction.rollback( e );
203                 mapTransactions.remove( strPoolName );
204             }
205         }
206     }
207 
208     /**
209      * Roll back every transactions opened by the current thread. This method attempt to prevent connection leak.
210      */
211     public static void rollBackEveryTransaction( )
212     {
213         rollBackEveryTransaction( null );
214     }
215 
216     /**
217      * Roll back every transactions opened by the current thread. This method attempt to prevent connection leak.
218      * 
219      * @param e
220      *            The exception that occurs and that may have prevent transaction from being properly closed (committed or roll backed)
221      */
222     public static void rollBackEveryTransaction( Throwable e )
223     {
224         Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
225 
226         if ( ( mapTransactions != null ) && ( mapTransactions.size( ) > 0 ) )
227         {
228             for ( MultiPluginTransaction transaction : mapTransactions.values( ) )
229             {
230                 transaction.rollback( null );
231             }
232 
233             mapTransactions.clear( );
234         }
235     }
236 
237     /**
238      * Get the name of the pool of a given plugin
239      * 
240      * @param plugin
241      *            The plugin to get the name of the pool, or null to get the name of the default pool.
242      * @return The name of the pool
243      */
244     private static String getPluginPool( Plugin plugin )
245     {
246         return ( plugin != null ) ? plugin.getDbPoolName( ) : DEFAULT_POOL_NAME;
247     }
248 }