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 }