TransactionManager.java
/*
* Copyright (c) 2002-2022, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.util.sql;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.util.AppLogService;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* Class to manage transactions
*/
public final class TransactionManager
{
private static final String DEFAULT_POOL_NAME = "portal";
private static ThreadLocal<Map<String, MultiPluginTransaction>> _tlTransactions = new ThreadLocal<>( );
/**
* Default constructor
*/
private TransactionManager( )
{
// Do nothing
}
/**
* Begin a transaction for the current thread using the pool of a specific plugin with the default transaction isolation. The default transaction isolation
* is {@link Connection#TRANSACTION_READ_COMMITTED }.<br>
* Note that only one transaction may be active at a time by pool for each thread.
*
* @param plugin
* The plugin to use the pool of, or null to use the default pool.
*/
public static void beginTransaction( Plugin plugin )
{
beginTransaction( plugin, Connection.TRANSACTION_READ_COMMITTED );
}
/**
* Begin a transaction for the current thread using the pool of a specific plugin
*
* @param plugin
* The plugin to use the pool of, or null to use the default pool.
* @param nTransactionIsolation
* The transaction isolation. View {@link Connection#setTransactionIsolation(int) } to view the different available transaction isolations.
*/
public static void beginTransaction( Plugin plugin, int nTransactionIsolation )
{
Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
MultiPluginTransaction transaction = null;
if ( mapTransactions == null )
{
mapTransactions = new HashMap<>( );
_tlTransactions.set( mapTransactions );
}
else
{
transaction = mapTransactions.get( getPluginPool( plugin ) );
}
if ( ( transaction == null ) || ( transaction.getNbTransactionsOpened( ) <= 0 ) )
{
transaction = new MultiPluginTransaction( plugin );
try
{
transaction.getConnection( ).setTransactionIsolation( nTransactionIsolation );
}
catch( SQLException e )
{
AppLogService.error( e.getMessage( ), e );
}
mapTransactions.put( getPluginPool( plugin ), transaction );
}
else
{
// A transaction has already been opened for this pool,
// so we save that information to prevent the next call to the commit method to close the transaction.
transaction.incrementNbTransactionsOpened( );
}
}
/**
* Get the current transaction for the pool of a given plugin.
*
* @param plugin
* The plugin to use the pool of, or null to use the default pool.
* @return The transaction, or null if no transaction is currently running.
*/
public static MultiPluginTransaction getCurrentTransaction( Plugin plugin )
{
Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
if ( mapTransactions != null )
{
return mapTransactions.get( getPluginPool( plugin ) );
}
return null;
}
/**
* Commit the transaction associated to the pool of a given plugin.
*
* @param plugin
* The plugin associated to the pool to commit the transaction of, or null to use the default pool.
*/
public static void commitTransaction( Plugin plugin )
{
Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
if ( mapTransactions != null )
{
String strPoolName = getPluginPool( plugin );
MultiPluginTransaction transaction = mapTransactions.get( strPoolName );
if ( transaction != null )
{
// If the number of transactions opened is 1 or less, then we commit the transaction
if ( transaction.getNbTransactionsOpened( ) <= 1 )
{
transaction.commit( );
mapTransactions.remove( strPoolName );
}
else
{
// Otherwise, we decrement the number
transaction.decrementNbTransactionsOpened( );
}
}
}
}
/**
* Roll back a transaction associated to the pool of a given plugin. Note that any plugin can roll a transaction back.
*
* @param plugin
* The plugin associated to the pool to roll back the transaction of, or null to use the default pool.
*/
public static void rollBack( Plugin plugin )
{
rollBack( plugin, null );
}
/**
* Roll back a transaction associated to the pool of a given plugin with an exception.
*
* @param plugin
* The plugin associated to the pool to roll back the transaction of, or null to use the default pool.
* @param e
* The exception to associates with the roll back.
*/
public static void rollBack( Plugin plugin, Exception e )
{
Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
if ( mapTransactions != null )
{
String strPoolName = getPluginPool( plugin );
MultiPluginTransaction transaction = mapTransactions.get( strPoolName );
// We roll back the transaction, no matter how much transactions has been opened.
if ( transaction != null )
{
transaction.rollback( e );
mapTransactions.remove( strPoolName );
}
}
}
/**
* Roll back every transactions opened by the current thread. This method attempt to prevent connection leak.
*/
public static void rollBackEveryTransaction( )
{
rollBackEveryTransaction( null );
}
/**
* Roll back every transactions opened by the current thread. This method attempt to prevent connection leak.
*
* @param e
* The exception that occurs and that may have prevent transaction from being properly closed (committed or roll backed)
*/
public static void rollBackEveryTransaction( Throwable e )
{
Map<String, MultiPluginTransaction> mapTransactions = _tlTransactions.get( );
if ( ( mapTransactions != null ) && ( mapTransactions.size( ) > 0 ) )
{
for ( MultiPluginTransaction transaction : mapTransactions.values( ) )
{
transaction.rollback( null );
}
mapTransactions.clear( );
}
}
/**
* Get the name of the pool of a given plugin
*
* @param plugin
* The plugin to get the name of the pool, or null to get the name of the default pool.
* @return The name of the pool
*/
private static String getPluginPool( Plugin plugin )
{
return ( plugin != null ) ? plugin.getDbPoolName( ) : DEFAULT_POOL_NAME;
}
}