View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de 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.database.AppConnectionService;
37  import fr.paris.lutece.portal.service.database.PluginConnectionService;
38  import fr.paris.lutece.portal.service.plugin.Plugin;
39  import fr.paris.lutece.portal.service.util.AppException;
40  
41  import org.apache.commons.lang.StringUtils;
42  
43  import org.apache.log4j.Logger;
44  
45  import java.sql.Connection;
46  import java.sql.PreparedStatement;
47  import java.sql.SQLException;
48  
49  
50  /**
51   * Transaction
52   */
53  public class Transaction
54  {
55      /**
56       * Status for opened transactions
57       */
58      public static final int OPENED = -1;
59  
60      /**
61       * Status for committed transactions
62       */
63      public static final int COMMITTED = 0;
64  
65      /**
66       * Status for roll backed transactions
67       */
68      public static final int ROLLEDBACK = 1;
69      private static final String DEFAULT_MODULE_NAME = "core";
70      private static final String LOGGER_DEBUG_SQL = "lutece.debug.sql.";
71  
72      /**
73       * The last SQL query executed by this transaction
74       */
75      private String _strSQL = StringUtils.EMPTY;
76  
77      /** JDBC Connection */
78      private Connection _connection;
79  
80      /** Connection Service providing connection from a defined pool */
81      private PluginConnectionService _connectionService;
82  
83      /** Plugin name */
84      private String _strPluginName;
85  
86      /** The debug logger */
87      private Logger _logger;
88      private PreparedStatement _statement;
89      private int _nStatus = OPENED;
90      private boolean _bAutoCommit;
91  
92      /**
93       * Constructor
94       */
95      public Transaction(  )
96      {
97          beginTransaction( null );
98      }
99  
100     /**
101      * Constructor
102      * @param plugin The plugin owner of the transaction
103      */
104     public Transaction( Plugin plugin )
105     {
106         beginTransaction( plugin );
107     }
108 
109     /**
110      * Gets a prepared statement
111      * @param strSQL The SQL statement
112      * @return The prepared statement
113      * @throws SQLException If an SQL error occurs
114      */
115     public PreparedStatement prepareStatement( String strSQL )
116         throws SQLException
117     {
118         // Close the previous statement if exists
119         if ( _statement != null )
120         {
121             _statement.close(  );
122         }
123 
124         // Get a new statement 
125         _strSQL = strSQL;
126 
127         if ( _connection == null )
128         {
129             throw new SQLException( "Plugin : '" + _strPluginName +
130                 "' - Connection has been closed. The new prepared statement can not be created : " + strSQL );
131         }
132 
133         _statement = _connection.prepareStatement( _strSQL );
134 
135         return _statement;
136     }
137 
138     /**
139      * The current prepared statement
140      * @return The current statement
141      */
142     public PreparedStatement getStatement(  )
143     {
144         return _statement;
145     }
146 
147     /**
148      * Execute the current statement
149      * @throws SQLException If an SQL error occurs
150      */
151     public void executeStatement(  ) throws SQLException
152     {
153         _logger.debug( "Plugin : '" + _strPluginName + "' - EXECUTE STATEMENT : " + _strSQL );
154         _statement.executeUpdate(  );
155     }
156 
157     /**
158      * Commit the transaction
159      */
160     public void commit(  )
161     {
162         try
163         {
164             if ( _connection == null )
165             {
166                 throw new SQLException( "Plugin : '" + _strPluginName +
167                     "' - Transaction has already been closed and can not be committed" );
168             }
169 
170             _connection.commit(  );
171             _logger.debug( "Plugin : '" + _strPluginName + "' - COMMIT TRANSACTION" );
172             closeTransaction( COMMITTED );
173         }
174         catch ( SQLException e )
175         {
176             rollback( e );
177         }
178     }
179 
180     /**
181      * Rollback the transaction
182      */
183     public void rollback(  )
184     {
185         rollback( null );
186     }
187 
188     /**
189      * Rollback the transaction
190      * @param e The exception that cause the rollback
191      */
192     public void rollback( Exception e )
193     {
194         if ( e != null )
195         {
196             _logger.error( "Transaction Error - Rollback in progress " + e.getMessage(  ), e.getCause(  ) );
197         }
198 
199         try
200         {
201             if ( _connection != null )
202             {
203                 _connection.rollback(  );
204                 _logger.debug( "Plugin : '" + _strPluginName + "' - ROLLBACK TRANSACTION" );
205             }
206             else
207             {
208                 _logger.debug( "Plugin : '" + _strPluginName + "' - TRANSACTION HAS ALREADY BEEN ROLLED BACK" );
209             }
210         }
211         catch ( SQLException ex )
212         {
213             _logger.error( "Transaction Error - Rollback error : " + ex.getMessage(  ), ex.getCause(  ) );
214         }
215         finally
216         {
217             closeTransaction( ROLLEDBACK );
218         }
219     }
220 
221     /**
222      * Return the transaction status
223      * @return The transaction status
224      */
225     public int getStatus(  )
226     {
227         return _nStatus;
228     }
229 
230     /**
231      * Get the underlying connection.
232      * @return The connection of this transaction. If the transaction has not
233      *         begin, then return null.
234      */
235     protected Connection getConnection(  )
236     {
237         return _connection;
238     }
239 
240     /**
241      * Begin a transaction
242      * @param plugin The plugin owner of the transaction
243      */
244     protected void beginTransaction( Plugin plugin )
245     {
246         if ( plugin != null )
247         {
248             _strPluginName = plugin.getName(  );
249             _connectionService = plugin.getConnectionService(  );
250         }
251         else
252         {
253             _strPluginName = DEFAULT_MODULE_NAME;
254             _connectionService = AppConnectionService.getDefaultConnectionService(  );
255         }
256 
257         if ( _connectionService == null )
258         {
259             throw new AppException( "Database access error. Please check component installations and db.properties." );
260         }
261 
262         _logger = Logger.getLogger( LOGGER_DEBUG_SQL + _strPluginName );
263         _logger.debug( "Plugin : '" + _strPluginName + "' - BEGIN TRANSACTION" );
264 
265         try
266         {
267             _connection = _connectionService.getConnection(  );
268 
269             // Save the autocommit configuration of the connection
270             _bAutoCommit = _connection.getAutoCommit(  );
271             _connection.setAutoCommit( false );
272         }
273         catch ( SQLException e )
274         {
275             rollback( e );
276         }
277     }
278 
279     /**
280      * Close the transaction
281      * @param nStatus The status of the transaction
282      */
283     private void closeTransaction( int nStatus )
284     {
285         _nStatus = nStatus;
286 
287         try
288         {
289             if ( _statement != null )
290             {
291                 _statement.close(  );
292             }
293 
294             // Restore the autocommit configuration of the connection
295             if ( _connection != null )
296             {
297                 _connection.setAutoCommit( _bAutoCommit );
298             }
299         }
300         catch ( SQLException ex )
301         {
302             _logger.error( "Transaction Error - Unable to close transaction " + ex.getMessage(  ), ex.getCause(  ) );
303         }
304         finally
305         {
306             _connectionService.freeConnection( _connection );
307             _connection = null;
308         }
309     }
310 
311     /**
312      * Checks that the transaction has been committed (or rolled back) before
313      * being destroyed and release all transaction resources (statement,
314      * connection, ...) if not. {@inheritDoc }
315      */
316     @Override
317     protected void finalize(  ) throws Throwable
318     {
319         if ( _nStatus == OPENED )
320         {
321             _logger.error( "The transaction has not been commited" );
322             closeTransaction( OPENED );
323         }
324 
325         super.finalize(  );
326     }
327 }