Transaction.java

  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. import fr.paris.lutece.portal.service.database.AppConnectionService;
  36. import fr.paris.lutece.portal.service.database.PluginConnectionService;
  37. import fr.paris.lutece.portal.service.plugin.Plugin;
  38. import fr.paris.lutece.portal.service.util.AppException;

  39. import org.apache.commons.lang3.StringUtils;
  40. import org.apache.logging.log4j.LogManager;
  41. import org.apache.logging.log4j.Logger;

  42. import java.sql.Connection;
  43. import java.sql.PreparedStatement;
  44. import java.sql.SQLException;

  45. /**
  46.  * Transaction
  47.  */
  48. public class Transaction
  49. {
  50.     /**
  51.      * Status for opened transactions
  52.      */
  53.     public static final int OPENED = -1;

  54.     /**
  55.      * Status for committed transactions
  56.      */
  57.     public static final int COMMITTED = 0;

  58.     /**
  59.      * Status for roll backed transactions
  60.      */
  61.     public static final int ROLLEDBACK = 1;
  62.     private static final String MESSAGE_PLUGIN = "Plugin : '";
  63.     private static final String DEFAULT_MODULE_NAME = "core";
  64.     private static final String LOGGER_DEBUG_SQL = "lutece.debug.sql.";

  65.     /**
  66.      * The last SQL query executed by this transaction
  67.      */
  68.     private String _strSQL = StringUtils.EMPTY;

  69.     /** JDBC Connection */
  70.     private Connection _connection;

  71.     /** Connection Service providing connection from a defined pool */
  72.     private PluginConnectionService _connectionService;

  73.     /** Plugin name */
  74.     private String _strPluginName;

  75.     /** The debug logger */
  76.     private Logger _logger;
  77.     private PreparedStatement _statement;
  78.     private int _nStatus = OPENED;
  79.     private boolean _bAutoCommit;

  80.     /**
  81.      * Constructor
  82.      */
  83.     public Transaction( )
  84.     {
  85.         beginTransaction( null );
  86.     }

  87.     /**
  88.      * Constructor
  89.      *
  90.      * @param plugin
  91.      *            The plugin owner of the transaction
  92.      */
  93.     public Transaction( Plugin plugin )
  94.     {
  95.         beginTransaction( plugin );
  96.     }

  97.     /**
  98.      * Gets a prepared statement
  99.      *
  100.      * @param strSQL
  101.      *            The SQL statement
  102.      * @return The prepared statement
  103.      * @throws SQLException
  104.      *             If an SQL error occurs
  105.      */
  106.     public PreparedStatement prepareStatement( String strSQL ) throws SQLException
  107.     {
  108.         return prepareStatement( strSQL, null, true );
  109.     }

  110.     /**
  111.      * Gets a prepared statement
  112.      *
  113.      * @param strSQL
  114.      *            The SQL statement
  115.      * @return The prepared statement
  116.      * @throws SQLException
  117.      *             If an SQL error occurs
  118.      */
  119.     public PreparedStatement prepareStatement( String strSQL, Integer autoGeneratedKeys ) throws SQLException
  120.     {
  121.         return prepareStatement( strSQL, autoGeneratedKeys, true );
  122.     }

  123.     /**
  124.      * Gets a prepared statement
  125.      *
  126.      * @param strSQL
  127.      *            The SQL statement
  128.      * @return The prepared statement
  129.      * @throws SQLException
  130.      *             If an SQL error occurs
  131.      */
  132.     public PreparedStatement prepareStatement( String strSQL, boolean bLogQueries ) throws SQLException
  133.     {
  134.         return prepareStatement( strSQL, null, bLogQueries );
  135.     }

  136.     /**
  137.      * Gets a prepared statement
  138.      *
  139.      * @since 6.0.0
  140.      * @param strSQL
  141.      *            The SQL statement
  142.      * @param autoGeneratedKeys
  143.      *            a flag indicating whether auto-generated keys should be returned; For example one of <code>Statement.RETURN_GENERATED_KEYS</code> or
  144.      *            <code>Statement.NO_GENERATED_KEYS</code>. See {@link PreparedStatement#prepareStatement(String, int)}
  145.      * @return The prepared statement
  146.      * @throws SQLException
  147.      *             If an SQL error occurs
  148.      */
  149.     public PreparedStatement prepareStatement( String strSQL, Integer autoGeneratedKeys, boolean bLogQueries ) throws SQLException
  150.     {
  151.         // Close the previous statement if exists
  152.         if ( _statement != null )
  153.         {
  154.             _statement.close( );
  155.         }

  156.         // Get a new statement
  157.         _strSQL = strSQL;

  158.         if ( _connection == null )
  159.         {
  160.             throw new SQLException( MESSAGE_PLUGIN + _strPluginName + "' - Connection has been closed. The new prepared statement can not be created : "
  161.                     + ( bLogQueries ? _strSQL : "(query log disabled)" ) );
  162.         }

  163.         if ( autoGeneratedKeys != null )
  164.         {
  165.             _statement = _connection.prepareStatement( _strSQL, autoGeneratedKeys );
  166.         }
  167.         else
  168.         {
  169.             _statement = _connection.prepareStatement( _strSQL );
  170.         }

  171.         return _statement;
  172.     }

  173.     /**
  174.      * The current prepared statement
  175.      *
  176.      * @return The current statement
  177.      */
  178.     public PreparedStatement getStatement( )
  179.     {
  180.         return _statement;
  181.     }

  182.     /**
  183.      * Execute the current statement
  184.      *
  185.      * @throws SQLException
  186.      *             If an SQL error occurs
  187.      */
  188.     public void executeStatement( ) throws SQLException
  189.     {
  190.         _logger.debug( "{} {}' - EXECUTE STATEMENT : {}", MESSAGE_PLUGIN, _strPluginName, _strSQL );
  191.         _statement.executeUpdate( );
  192.     }

  193.     /**
  194.      * Commit the transaction
  195.      */
  196.     public void commit( )
  197.     {
  198.         try
  199.         {
  200.             if ( _connection == null )
  201.             {
  202.                 throw new SQLException( MESSAGE_PLUGIN + _strPluginName + "' - Transaction has already been closed and can not be committed" );
  203.             }

  204.             _connection.commit( );
  205.             _logger.debug( "{} {}' - COMMIT TRANSACTION", MESSAGE_PLUGIN, _strPluginName );
  206.             closeTransaction( COMMITTED );
  207.         }
  208.         catch( SQLException e )
  209.         {
  210.             rollback( e );
  211.         }
  212.     }

  213.     /**
  214.      * Rollback the transaction
  215.      */
  216.     public void rollback( )
  217.     {
  218.         rollback( null );
  219.     }

  220.     /**
  221.      * Rollback the transaction
  222.      *
  223.      * @param e
  224.      *            The exception that cause the rollback
  225.      */
  226.     public void rollback( Exception e )
  227.     {
  228.         if ( e != null )
  229.         {
  230.             _logger.error( "Transaction Error - Rollback in progress {}", e.getMessage( ), e.getCause( ) );
  231.         }

  232.         try
  233.         {
  234.             if ( _connection != null )
  235.             {
  236.                 _connection.rollback( );
  237.                 _logger.debug( "{} {}' - ROLLBACK TRANSACTION", MESSAGE_PLUGIN, _strPluginName );
  238.             }
  239.             else
  240.             {
  241.                 _logger.debug( "{} {}' - TRANSACTION HAS ALREADY BEEN ROLLED BACK", MESSAGE_PLUGIN, _strPluginName );
  242.             }
  243.         }
  244.         catch( SQLException ex )
  245.         {
  246.             _logger.error( "Transaction Error - Rollback error : {}", ex.getMessage( ), ex );
  247.         }
  248.         finally
  249.         {
  250.             closeTransaction( ROLLEDBACK );
  251.         }
  252.     }

  253.     /**
  254.      * Return the transaction status
  255.      *
  256.      * @return The transaction status
  257.      */
  258.     public int getStatus( )
  259.     {
  260.         return _nStatus;
  261.     }

  262.     /**
  263.      * Get the underlying connection.
  264.      *
  265.      * @return The connection of this transaction. If the transaction has not begin, then return null.
  266.      */
  267.     protected Connection getConnection( )
  268.     {
  269.         return _connection;
  270.     }

  271.     /**
  272.      * Begin a transaction
  273.      *
  274.      * @param plugin
  275.      *            The plugin owner of the transaction
  276.      */
  277.     protected void beginTransaction( Plugin plugin )
  278.     {
  279.         if ( plugin != null )
  280.         {
  281.             _strPluginName = plugin.getName( );
  282.             _connectionService = plugin.getConnectionService( );
  283.         }
  284.         else
  285.         {
  286.             _strPluginName = DEFAULT_MODULE_NAME;
  287.             _connectionService = AppConnectionService.getDefaultConnectionService( );
  288.         }

  289.         if ( _connectionService == null )
  290.         {
  291.             throw new AppException( "Database access error. Please check component installations and db.properties." );
  292.         }

  293.         _logger = LogManager.getLogger( LOGGER_DEBUG_SQL + _strPluginName );
  294.         _logger.debug( "{}{}' - BEGIN TRANSACTION", MESSAGE_PLUGIN, _strPluginName );

  295.         try
  296.         {
  297.             _connection = _connectionService.getConnection( );

  298.             // Save the autocommit configuration of the connection
  299.             _bAutoCommit = _connection.getAutoCommit( );
  300.             _connection.setAutoCommit( false );
  301.         }
  302.         catch( SQLException e )
  303.         {
  304.             rollback( e );
  305.         }
  306.     }

  307.     /**
  308.      * Close the transaction
  309.      *
  310.      * @param nStatus
  311.      *            The status of the transaction
  312.      */
  313.     private void closeTransaction( int nStatus )
  314.     {
  315.         _nStatus = nStatus;

  316.         try
  317.         {
  318.             if ( _statement != null )
  319.             {
  320.                 _statement.close( );
  321.             }

  322.             // Restore the autocommit configuration of the connection
  323.             if ( _connection != null )
  324.             {
  325.                 _connection.setAutoCommit( _bAutoCommit );
  326.             }
  327.         }
  328.         catch( SQLException ex )
  329.         {
  330.             _logger.error( "Transaction Error - Unable to close transaction {}", ex.getMessage( ), ex );
  331.         }
  332.         finally
  333.         {
  334.             _connectionService.freeConnection( _connection );
  335.             _connection = null;
  336.         }
  337.     }

  338.     /**
  339.      * Checks that the transaction has been committed (or rolled back) before being destroyed and release all transaction resources (statement, connection, ...)
  340.      * if not. {@inheritDoc }
  341.      */
  342.     @Override
  343.     protected void finalize( ) throws Throwable
  344.     {
  345.         if ( _nStatus == OPENED )
  346.         {
  347.             _logger.error( "The transaction has not been commited" );
  348.             closeTransaction( OPENED );
  349.         }

  350.         super.finalize( );
  351.     }
  352. }