View Javadoc

1   /***
2    * Copyright 2007 ATG DUST Project
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * 
7    * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8    * 
9    * Unless required by applicable law or agreed to in writing, software 
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and limitations under the License.
13   */
14  
15  package atg.adapter.gsa ;
16  
17  import java.sql.Connection;
18  import java.sql.ResultSet;
19  import java.sql.SQLException;
20  import java.sql.Statement;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  
27  import javax.sql.DataSource;
28  import javax.transaction.TransactionManager;
29  
30  import org.apache.log4j.Logger;
31  
32  import atg.dtm.TransactionDemarcation;
33  import atg.dtm.TransactionDemarcationException;
34  import atg.nucleus.GenericService;
35  import atg.service.jdbc.BasicDataSource;
36  
37  /*** A generic class to execute SQL actions against a database.
38   *
39   * Parts copied from atg.service.idgen.?? by mgk
40   *
41   * @author mf
42   * @version 1.0
43   **/
44  public
45  class SQLProcessor
46  {
47    // =============== MEMBER VARIABLES =================
48    
49    private static Logger log = Logger.getLogger(SQLProcessor.class);
50  
51    DataSource mDataSource;
52    /*** sets the DataSource from which to get DB connections
53     **/
54    public void setDataSource(DataSource pDataSource) {
55  	mDataSource = pDataSource; }
56    /*** returns the DataSource from which db connections are obtained */
57    public DataSource getDataSource() {
58  	return mDataSource;}
59  
60    TransactionManager mTxManager;
61    /*** sets the TransactionManager that should be used to monitor transactions */
62    public void setTransactionManager( TransactionManager pManager ) {
63        mTxManager = pManager; }
64    /*** returns the TransactionManager that should be used to monitor transaction */
65    public TransactionManager getTransactionManager() {
66        return mTxManager; }
67  
68    String mDetermineTableExistsSQL = "SELECT * from ";
69    /*** sets String executed to determine whether a table exists. The table
70     * name is appended to the end of the string before execution occurs.
71     */
72    public void setDetermineTableExistsSQL( String pStr ) {
73  	  mDetermineTableExistsSQL = pStr; }
74    /*** returns String executed to determine whether a table exists. The table
75     * name is appended to the end of the string before execution occurs.
76     */
77    public String getDetermineTableExistsSQL() {
78  	  return mDetermineTableExistsSQL; }
79  
80    String mDropTableSQL = "DROP TABLE ";
81    /*** sets String executed to drop a table.  The table name is appended to the
82     * end of the string before execution
83     */
84    public void setDropTableSQL( String pStr ) {
85  	  mDropTableSQL = pStr; }
86    /*** returns String executed to drop a table.  The table name is appended to the
87     * end of the string before execution
88     */
89    public String getDropTableSQL() {
90  	  return mDropTableSQL; }
91  
92    /*** String delimiter used to separate a large String passed to
93     * createTables() into an array of individual Create Table statements
94     * default value is "CREATE TABLE"
95     * This delimiter _will_ be included in the final create statements
96     */
97    String mCreateTableBeginDelimiter = "CREATE TABLE ";
98    public void setCreateTableBeginDelimiter( String pStr ) {
99  	  mCreateTableBeginDelimiter = pStr; }
100   public String getCreateTableBeginDelimiter() {
101 	  return mCreateTableBeginDelimiter; }
102 
103   /*** String delimiter used to separate a large String passed to
104    * createTables() into an array of individual Create Table statements
105    * default value is ";"
106    * This delimiter _will not_ be included in the final create statements
107    */
108   String mCreateTableEndDelimiter = ";";
109   public void setCreateTableEndDelimiter( String pStr ) {
110 	  mCreateTableEndDelimiter = pStr; }
111   public String getCreateTableEndDelimiter() {
112 	  return mCreateTableEndDelimiter; }
113 
114    /*** an optional GenericService component whose logging services should be used by
115     *  this component.
116     */
117    private GenericService mLogger;
118    public void setLoggingManager( GenericService pLogger ) {
119       mLogger = pLogger; }
120    public GenericService getLoggingManager() {
121       return mLogger; }
122 
123   // indicates whether to set autoCommit(true) on connections
124   private boolean mAutoCommit = false;
125   /*** if set to true, then autoCommit will be set to true on all connections used to
126    *  execute SQL.  otherwise, autoCommit will not be altered from what is set by the
127    *  DataSource.
128    */
129   public void setAutoCommit( boolean pCommit ) {
130       mAutoCommit = pCommit; }
131   /*** returns true if autoCommit should be set to true on all connections used to execute
132    *  SQL.
133    */
134   public boolean isSetAutoCommit() {
135       return mAutoCommit; }
136 
137   /* =========== CONSTRUCTORS ============= */
138 
139   /*** Construct with specified DataSource
140    *
141    *  @param TransactionManager manager - the TransactionManager to use to monitor transactions
142    *  @param DataSource dataSource - the DataSource to use for db connections
143    **/
144   public SQLProcessor( TransactionManager pTxManager, DataSource pDataSource )
145   {
146       setDataSource( pDataSource );
147       setTransactionManager( pTxManager );
148   }
149 
150   /*** Constructor with specified user/password/driver/URL.  specified parameters are used
151    *  to create a DataSource connection to the database.
152    *
153    *  @param TransactionManager manager - the TransactionManager to use to monitor transactions
154    *  @param String username - name of user to connect to db
155    *  @param String password - pwd to connectc to db
156    *  @param String driver - driver specification to connect to db
157    *  @param String url - url to connect to db
158    *  @exception SQLException if an error occurs creating the DataSource
159    */
160   public SQLProcessor( TransactionManager pTxManager, String pUsername, String pPassword, String pDriver, String pURL )
161       throws SQLException
162   {
163       setDataSource( createBasicDataSource( pUsername, pPassword, pDriver, pURL ) );
164       setTransactionManager( pTxManager );
165   }
166 
167   // ==================== PUBLIC METHODS ===========================
168 
169   /*** creates and returns a DataSource based on the user/pwd/driver/url info
170    *  supplied.
171    */
172    public static DataSource createBasicDataSource( String pUsername,
173                                               String pPassword,
174                                               String pDriver,
175                                               String pURL )
176    {
177         BasicDataSource datasource = new BasicDataSource();
178         datasource.setUser( pUsername );
179         datasource.setPassword( pPassword );
180         datasource.setDriver( pDriver );
181         datasource.setURL( pURL );
182 
183         return datasource;
184    }
185 
186   /***
187    * Perform the specified SQL statement in a new transaction which is commited.  Autocommit
188    * on the connection is set to true if isSetAutoCommit() is true.
189    *
190    * @param pSQL SQL to execute
191    *
192    * @exception SQLException if there is DB problem
193    * @exception TransactionDemarcationException if there is a tx problem
194    **/
195     public void executeSQL(String pSQL)
196       throws SQLException, TransactionDemarcationException
197     {
198       TransactionDemarcation td = new TransactionDemarcation();
199       try
200         {
201           td.begin ( getTransactionManager(), TransactionDemarcation.REQUIRES_NEW);
202           Connection c = null;
203           Statement s = null;
204           try
205             {
206               // get DB connection
207               c = getConnection();
208               if ( isSetAutoCommit() )
209                 c.setAutoCommit( true );
210 
211               //most of this method is annoying try/catch/finally blocks
212               //inflicted on us by JTA. the real work is here.
213               s = c.createStatement();
214               debug("Executing SQL [" + pSQL + "]");
215               s.execute(pSQL);
216             }
217            finally
218             {
219               close(s);
220               close(c);
221             }
222         }
223        finally
224         {
225               td.end();
226         }
227     }
228 
229     /*** executes the specified query and returns a List of values for the specified column name.
230      *  for example, executeQuery( "select * from user", "first_name" ) would return a List of
231      *  the first names of all entries in the user table.
232      *
233      *  @return List of Object values
234      *  @exception SQLException if a sql error occurs
235      *  @exception TransactionDemarcationException if a tx error occurs
236      */
237      public List<?> executeQuery( String pQuery, String pColumnName )
238         throws SQLException, TransactionDemarcationException
239      {
240         List<Object> results = new LinkedList<Object>();
241         TransactionDemarcation td = new TransactionDemarcation();
242         //int rows = 0;
243         try
244           {
245             td.begin ( getTransactionManager(), TransactionDemarcation.REQUIRES_NEW);
246             Connection c = null;
247             Statement s = null;
248             ResultSet rs = null;
249             try
250               {
251                 // get DB connection
252                 c = getConnection();
253 
254                 //most of this method is annoying try/catch/finally blocks
255                 //inflicted on us by JTA. the real work is here.
256                 s = c.createStatement();
257                 debug("Executing query [" + pQuery + "]");
258                 rs = s.executeQuery( pQuery );
259 
260                 while ( rs.next() ) {
261                     results.add( rs.getObject( pColumnName) );
262                 }
263               }
264              finally
265               {
266                 close(rs);
267                 close(s);
268                 close(c);
269               }
270           }
271          finally
272           {
273                 td.end();
274           }
275           return results;
276      }
277 
278     /*** Method that iteratively attempts to drop tables.  An iterative
279      * effort is utilized in case references exist between tables.
280      *
281      * ASSUMPTION: references only exist between tables specified in the
282      * List.  If references exist from tables outside the List, then some
283      * tables may not be able to be dropped and this method will throw a
284      * SQLException
285      *
286      * @param Collection of names of tables to be dropped
287      * @param boolean cascadeConstraints.  true if 'CASCADE CONSTRAINTS' should be used in
288      * drop statement.
289      * @param boolean preview. if true then iterative behavior is disabled and method simply
290      * prints one drop statement that would be executed for each table.  iterative behavior has
291      * to be disabled since it doesn't make sense if drops are not being executed.
292      * @exception SQLException thrown if all tables can not be dropped
293      */
294     public void dropTables( Collection<String> pNames, boolean pCascadeConstraints, boolean pPreview )
295             throws SQLException, TransactionDemarcationException
296     {
297       // just show drops once if preview is true
298       if ( pPreview ) {
299           Iterator<String> tables = pNames.iterator();
300           while ( tables.hasNext() ) {
301               dropTable(tables.next(), pCascadeConstraints, pPreview );
302           }
303           return;
304       }
305 
306       // assuming only one table can be dropped each time, this should take
307       // at most n iterations where n is the nbr of tables being dropped
308       int maxIterations = pNames.size();
309 
310       // every table is tried at least once
311       Collection<String> tablesToDrop = pNames;
312 
313       List<String> remainingTables;
314       int attempt = 0;
315       do {
316               remainingTables = new ArrayList<String>();
317               Iterator<String> tables = tablesToDrop.iterator();
318               while ( tables.hasNext() ) {
319                       String table =  tables.next();
320                       if ( tableExists( table ) ) {
321                               try {
322                                       dropTable( table, pCascadeConstraints, pPreview );
323                                       debug( "Dropped table: " + table);
324                               } catch ( SQLException se ) {
325                                       // if this is the last iteration, throw an exception
326                                       if ( attempt+1 >= maxIterations )
327                                               throw se;
328 
329                                       // otherwise track this table for the next try
330                                       remainingTables.add( table );
331                               }
332                       }
333               }
334               tablesToDrop = remainingTables;
335 
336       } while ( ( attempt++ < maxIterations )
337                        && ( ! remainingTables.isEmpty() ) );
338     }
339 
340     // ====================== PRIVATE METHODS ==========================
341 
342       /***
343        * Get a DB connection
344        * @return the connection
345        * @exception SQLProcessorException if there is DB trouble or
346        * DataSource trouble
347        **/
348       Connection getConnection()
349         throws SQLException
350       {
351             if (getDataSource() == null)
352                             throw new SQLException("DataSource is null.");
353 
354             return getDataSource().getConnection();
355       }
356 
357       /***
358        * Close a DB connection. It is okay to pass a null connection here
359        *
360        * @param pConnection connection to close, may be null
361        * @exception SQLException if an error occurs trying to close a non-null connection
362        **/
363       private final void close(Connection pConnection)
364           throws SQLException
365       {
366         if (pConnection != null)
367             pConnection.close();
368       }
369 
370       /***
371        * Close a result set. It is okay to pass a null here
372        *
373        * @param pResultSet result set to close, may be null
374        * @exception SQLException if an error occurs closing a non-null ResultSet
375        **/
376       private final void close(ResultSet pResultSet)
377           throws SQLException
378       {
379         if (pResultSet != null)
380             pResultSet.close();
381       }
382 
383       /***
384        * Close a statement. It is okay to pass a null here.
385        *
386        * @param pStatement statement to close, may be null
387        * @exception SQLException if an error occurs closing a non-null Statement
388        **/
389       private final void close(Statement pStatement)
390           throws SQLException
391       {
392         if (pStatement != null)
393             pStatement.close();
394       }
395 
396       /*** This method is used to execute a 'Drop Table' call. The
397        * method creates the drop table statement by appending the name
398        * passed as a method with the SQL that has been set as the dropTableSQL
399        * property.  By default, this property is set to "Drop table"
400        *
401        * @param String - the name of the table to drop
402        * @param boolean cascadeConstraints.  true if 'CASCADE CONSTRAINTS' should be used in
403        * drop statement.
404        * @exception SQLException thrown if an error occurs trying
405        * to drop the table
406        */
407         private void dropTable( String pName, boolean pCascadeConstraints, boolean pPreview )
408             throws SQLException, TransactionDemarcationException
409         {
410           String sql = getDropTableSQL() + " " + pName;
411           if ( pCascadeConstraints ) sql = sql + " CASCADE CONSTRAINTS";
412 
413           if ( pPreview ) log.info( sql );
414           else executeSQL( sql );
415        }
416 
417       /***
418        * Method to determine whether a table already exists in the
419        * database.  The method operates by appending the name passed
420        * as a parameter to the String that has been set in the
421        * determineTableExistsSQL property
422        *
423        * @param String - name of table to check for existence of
424        * @return boolean - true if table exists; false otherwise
425        * @exception TransactionDemarcationException if a tx error occurs
426        */
427       private boolean tableExists( String pTableName )
428           throws TransactionDemarcationException
429       {
430               // don't bother with query if name is invalid
431               if ( pTableName == null ||
432                        pTableName.length() == 0 )
433                       return false;
434 
435               // create sql
436               String sql = getDetermineTableExistsSQL() + " " + pTableName;
437 
438               // execute and check for an exception
439               try {
440                       executeSQL( sql );
441               } catch ( SQLException spe ) {
442                     // we should only get an exception here if the table doesn't exist
443                     // so just return false
444                     return false;
445               }
446 
447               return true;
448       }
449 
450       /*** a utility method to assist with logging */
451       private void debug( Object pMsg ) {
452           if ( getLoggingManager() != null && getLoggingManager().isLoggingDebug() )
453               getLoggingManager().logDebug("SQLProcessor: " + pMsg.toString());
454       }
455 }