Transaction.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.database.AppConnectionService;
import fr.paris.lutece.portal.service.database.PluginConnectionService;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.util.AppException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Transaction
*/
public class Transaction
{
/**
* Status for opened transactions
*/
public static final int OPENED = -1;
/**
* Status for committed transactions
*/
public static final int COMMITTED = 0;
/**
* Status for roll backed transactions
*/
public static final int ROLLEDBACK = 1;
private static final String MESSAGE_PLUGIN = "Plugin : '";
private static final String DEFAULT_MODULE_NAME = "core";
private static final String LOGGER_DEBUG_SQL = "lutece.debug.sql.";
/**
* The last SQL query executed by this transaction
*/
private String _strSQL = StringUtils.EMPTY;
/** JDBC Connection */
private Connection _connection;
/** Connection Service providing connection from a defined pool */
private PluginConnectionService _connectionService;
/** Plugin name */
private String _strPluginName;
/** The debug logger */
private Logger _logger;
private PreparedStatement _statement;
private int _nStatus = OPENED;
private boolean _bAutoCommit;
/**
* Constructor
*/
public Transaction( )
{
beginTransaction( null );
}
/**
* Constructor
*
* @param plugin
* The plugin owner of the transaction
*/
public Transaction( Plugin plugin )
{
beginTransaction( plugin );
}
/**
* Gets a prepared statement
*
* @param strSQL
* The SQL statement
* @return The prepared statement
* @throws SQLException
* If an SQL error occurs
*/
public PreparedStatement prepareStatement( String strSQL ) throws SQLException
{
return prepareStatement( strSQL, null, true );
}
/**
* Gets a prepared statement
*
* @param strSQL
* The SQL statement
* @return The prepared statement
* @throws SQLException
* If an SQL error occurs
*/
public PreparedStatement prepareStatement( String strSQL, Integer autoGeneratedKeys ) throws SQLException
{
return prepareStatement( strSQL, autoGeneratedKeys, true );
}
/**
* Gets a prepared statement
*
* @param strSQL
* The SQL statement
* @return The prepared statement
* @throws SQLException
* If an SQL error occurs
*/
public PreparedStatement prepareStatement( String strSQL, boolean bLogQueries ) throws SQLException
{
return prepareStatement( strSQL, null, bLogQueries );
}
/**
* Gets a prepared statement
*
* @since 6.0.0
* @param strSQL
* The SQL statement
* @param autoGeneratedKeys
* a flag indicating whether auto-generated keys should be returned; For example one of <code>Statement.RETURN_GENERATED_KEYS</code> or
* <code>Statement.NO_GENERATED_KEYS</code>. See {@link PreparedStatement#prepareStatement(String, int)}
* @return The prepared statement
* @throws SQLException
* If an SQL error occurs
*/
public PreparedStatement prepareStatement( String strSQL, Integer autoGeneratedKeys, boolean bLogQueries ) throws SQLException
{
// Close the previous statement if exists
if ( _statement != null )
{
_statement.close( );
}
// Get a new statement
_strSQL = strSQL;
if ( _connection == null )
{
throw new SQLException( MESSAGE_PLUGIN + _strPluginName + "' - Connection has been closed. The new prepared statement can not be created : "
+ ( bLogQueries ? _strSQL : "(query log disabled)" ) );
}
if ( autoGeneratedKeys != null )
{
_statement = _connection.prepareStatement( _strSQL, autoGeneratedKeys );
}
else
{
_statement = _connection.prepareStatement( _strSQL );
}
return _statement;
}
/**
* The current prepared statement
*
* @return The current statement
*/
public PreparedStatement getStatement( )
{
return _statement;
}
/**
* Execute the current statement
*
* @throws SQLException
* If an SQL error occurs
*/
public void executeStatement( ) throws SQLException
{
_logger.debug( "{} {}' - EXECUTE STATEMENT : {}", MESSAGE_PLUGIN, _strPluginName, _strSQL );
_statement.executeUpdate( );
}
/**
* Commit the transaction
*/
public void commit( )
{
try
{
if ( _connection == null )
{
throw new SQLException( MESSAGE_PLUGIN + _strPluginName + "' - Transaction has already been closed and can not be committed" );
}
_connection.commit( );
_logger.debug( "{} {}' - COMMIT TRANSACTION", MESSAGE_PLUGIN, _strPluginName );
closeTransaction( COMMITTED );
}
catch( SQLException e )
{
rollback( e );
}
}
/**
* Rollback the transaction
*/
public void rollback( )
{
rollback( null );
}
/**
* Rollback the transaction
*
* @param e
* The exception that cause the rollback
*/
public void rollback( Exception e )
{
if ( e != null )
{
_logger.error( "Transaction Error - Rollback in progress {}", e.getMessage( ), e.getCause( ) );
}
try
{
if ( _connection != null )
{
_connection.rollback( );
_logger.debug( "{} {}' - ROLLBACK TRANSACTION", MESSAGE_PLUGIN, _strPluginName );
}
else
{
_logger.debug( "{} {}' - TRANSACTION HAS ALREADY BEEN ROLLED BACK", MESSAGE_PLUGIN, _strPluginName );
}
}
catch( SQLException ex )
{
_logger.error( "Transaction Error - Rollback error : {}", ex.getMessage( ), ex );
}
finally
{
closeTransaction( ROLLEDBACK );
}
}
/**
* Return the transaction status
*
* @return The transaction status
*/
public int getStatus( )
{
return _nStatus;
}
/**
* Get the underlying connection.
*
* @return The connection of this transaction. If the transaction has not begin, then return null.
*/
protected Connection getConnection( )
{
return _connection;
}
/**
* Begin a transaction
*
* @param plugin
* The plugin owner of the transaction
*/
protected void beginTransaction( Plugin plugin )
{
if ( plugin != null )
{
_strPluginName = plugin.getName( );
_connectionService = plugin.getConnectionService( );
}
else
{
_strPluginName = DEFAULT_MODULE_NAME;
_connectionService = AppConnectionService.getDefaultConnectionService( );
}
if ( _connectionService == null )
{
throw new AppException( "Database access error. Please check component installations and db.properties." );
}
_logger = LogManager.getLogger( LOGGER_DEBUG_SQL + _strPluginName );
_logger.debug( "{}{}' - BEGIN TRANSACTION", MESSAGE_PLUGIN, _strPluginName );
try
{
_connection = _connectionService.getConnection( );
// Save the autocommit configuration of the connection
_bAutoCommit = _connection.getAutoCommit( );
_connection.setAutoCommit( false );
}
catch( SQLException e )
{
rollback( e );
}
}
/**
* Close the transaction
*
* @param nStatus
* The status of the transaction
*/
private void closeTransaction( int nStatus )
{
_nStatus = nStatus;
try
{
if ( _statement != null )
{
_statement.close( );
}
// Restore the autocommit configuration of the connection
if ( _connection != null )
{
_connection.setAutoCommit( _bAutoCommit );
}
}
catch( SQLException ex )
{
_logger.error( "Transaction Error - Unable to close transaction {}", ex.getMessage( ), ex );
}
finally
{
_connectionService.freeConnection( _connection );
_connection = null;
}
}
/**
* Checks that the transaction has been committed (or rolled back) before being destroyed and release all transaction resources (statement, connection, ...)
* if not. {@inheritDoc }
*/
@Override
protected void finalize( ) throws Throwable
{
if ( _nStatus == OPENED )
{
_logger.error( "The transaction has not been commited" );
closeTransaction( OPENED );
}
super.finalize( );
}
}