View Javadoc

1   /***
2    * Copyright 2007 ATG DUST Project Licensed under the Apache License, Version
3    * 2.0 (the "License"); you may not use this file except in compliance with the
4    * License. You may obtain a copy of the License at
5    * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6    * or agreed to in writing, software distributed under the License is
7    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8    * KIND, either express or implied. See the License for the specific language
9    * governing permissions and limitations under the License.
10   */
11  
12  package atg.adapter.gsa;
13  
14  import java.sql.Connection;
15  import java.sql.DatabaseMetaData;
16  import java.sql.ResultSet;
17  import java.sql.SQLException;
18  import java.sql.Statement;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.StringTokenizer;
24  
25  import javax.sql.DataSource;
26  
27  import atg.core.util.StringUtils;
28  import atg.dtm.TransactionDemarcation;
29  import atg.dtm.TransactionDemarcationException;
30  import atg.nucleus.GenericService;
31  import atg.nucleus.ServiceException;
32  
33  /***
34   * This class is designed to assist with database table manipulation such as
35   * adding tables. Parts copied from atg.service.idgen.?? by mgk
36   * 
37   * @author mf
38   * @version 1.0
39   **/
40  public class SQLProcessorEngine extends GenericService {
41  
42    // Vendor String for Apache Derby
43    public static final String APACHE_DERBY = "Apache Derby";
44  
45    /* =========== CONSTRUCTORS ============= */
46  
47    /***
48     * empty constructor
49     */
50    public SQLProcessorEngine() {
51    };
52  
53    /***
54     * Construct a generator
55     **/
56    public SQLProcessorEngine(GSARepository pRep) {
57      setRepository(pRep);
58      mDataSource = getRepository().getDataSource();
59    }
60  
61    // ---------- Property: DataSource ----------
62    /***
63     * DataSource from which to get DB connections this property is NOT a public
64     * property because it is extracted from the repository property
65     */
66    DataSource mDataSource;
67  
68    private void setDataSource(DataSource pDataSource) {
69      mDataSource = pDataSource;
70    }
71  
72    private DataSource getDataSource() {
73      return mDataSource;
74    }
75  
76    /***
77     * GSARespository from which to get the DataSource and TransactionManager
78     */
79    GSARepository mRepository;
80  
81    public void setRepository(GSARepository pRep) {
82      mRepository = pRep;
83    }
84  
85    public GSARepository getRepository() {
86      return mRepository;
87    }
88  
89    /***
90     * String executed to determine whether a table exists. The table name is
91     * appended to the end of the string before execution occurs.
92     */
93    String mDetermineTableExistsSQL = "SELECT count(*) from";
94  
95    public void setDetermineTableExistsSQL(String pStr) {
96      mDetermineTableExistsSQL = pStr;
97    }
98  
99    public String getDetermineTableExistsSQL() {
100     return mDetermineTableExistsSQL;
101   }
102 
103   /***
104    * String executed to drop a table. The table name is appended to the end of
105    * the string before execution
106    */
107   String mDropTableSQL = "DROP TABLE";
108 
109   public void setDropTableSQL(String pStr) {
110     mDropTableSQL = pStr;
111   }
112 
113   public String getDropTableSQL() {
114     return mDropTableSQL;
115   }
116 
117   /***
118    * String delimiter used to separate the large String passed to createTables()
119    * into an array of individual Create Table statements default value is
120    * "CREATE TABLE" This delimiter _will_ be included in the final create
121    * statements
122    */
123   String mCreateTableBeginDelimiter = "CREATE TABLE";
124 
125   public void setCreateTableBeginDelimiter(String pStr) {
126     mCreateTableBeginDelimiter = pStr;
127   }
128 
129   public String getCreateTableBeginDelimiter() {
130     return mCreateTableBeginDelimiter;
131   }
132 
133   /***
134    * String delimiter used to separate the large String passed to createTables()
135    * into an array of individual Create Table statements default value is ";"
136    * This delimiter _will not_ be included in the final create statements
137    */
138   String mCreateTableEndDelimiter = ";";
139 
140   public void setCreateTableEndDelimiter(String pStr) {
141     mCreateTableEndDelimiter = pStr;
142   }
143 
144   public String getCreateTableEndDelimiter() {
145     return mCreateTableEndDelimiter;
146   }
147 
148   // -------------------------------------
149 
150   /***
151    * method to execute when starting this component
152    */
153   public void doStartService() throws ServiceException {
154     if (getRepository() == null)
155       throw new ServiceException("Repository property is null.");
156 
157     setDataSource(getRepository().getDataSource());
158   }
159 
160   /***
161    * Get a DB connection
162    * 
163    * @return the connection
164    * @exception SQLProcessorException
165    *              if there is DB trouble or DataSource trouble
166    **/
167   Connection getConnection() throws SQLProcessorException {
168     try {
169       DataSource ds = getDataSource();
170       if (ds == null)
171         throw new SQLProcessorException("no DataSource");
172       else
173         return ds.getConnection();
174     } catch (SQLException sqle) {
175       if (isLoggingDebug()) {
176         SQLException next = sqle;
177         while (next != null) {
178           logDebug(next);
179           next = next.getNextException();
180         }
181       }
182       throw new SQLProcessorException(sqle);
183     }
184   }
185 
186   // -------------------------------------
187   /***
188    * Close a DB connection, logging any SQLExceptions. It is okay to pass a null
189    * connection here
190    * 
191    * @param pConnection
192    *          connection to close, may be null
193    **/
194   private final void close(Connection pConnection) {
195     if (pConnection != null) {
196       try {
197         pConnection.close();
198       } catch (SQLException sqle) {
199         if (isLoggingError())
200           logError(sqle);
201       }
202     }
203   }
204 
205   // -------------------------------------
206   /***
207    * Close a result set, logging any SQLExceptions. It is okay to pass a null
208    * here
209    * 
210    * @param pResultSet
211    *          result set to close, may be null
212    **/
213 // private final void close(ResultSet pResultSet)
214 // {
215 // if (pResultSet != null)
216 // {
217 // try
218 // {
219 // pResultSet.close();
220 // }
221 // catch (SQLException sqle)
222 // {
223 // if (isLoggingError())
224 // logError(sqle);
225 // }
226 // }
227 // }
228 
229   // -------------------------------------
230   /***
231    * Close a statement, logging any SQLExceptions. It is okay to pass a null
232    * here.
233    * 
234    * @param pStatement
235    *          statement to close, may be null
236    **/
237   private final void close(Statement pStatement) {
238     if (pStatement != null) {
239       try {
240         pStatement.close();
241       } catch (SQLException sqle) {
242         if (isLoggingError())
243           logError(sqle);
244       }
245     }
246   }
247 
248   // -------------------------------------
249   /***
250    * Perform the specified SQL statement in a new transaction which is commited.
251    * 
252    * @param pSQL
253    *          SQL to execute
254    * @return the # of rows affected
255    * @exception SQLProcessorException
256    *              if there is DB or xact trouble
257    **/
258   private int performSQL(String pSQL) throws SQLProcessorException {
259     TransactionDemarcation td = new TransactionDemarcation();
260     SQLProcessorException error = null;
261     int rows = 0;
262     try {
263       td.begin(mRepository.getTransactionManager(),
264           TransactionDemarcation.REQUIRES_NEW);
265       Connection c = null;
266       Statement s = null;
267       try {
268         // get DB connection
269         c = getConnection();
270 
271         /*
272          * * most of this method is annoying try/catch/finally blocks* inflicted
273          * on us by JTA. the real work is here.
274          */
275         s = c.createStatement();
276         // rows = s.executeUpdate(pSQL);
277         s.execute(pSQL);
278       } catch (SQLException sqle) {
279         error = new SQLProcessorException(sqle);
280       } finally {
281         close(s);
282         close(c);
283       }
284     } catch (TransactionDemarcationException e1) {
285       if (error == null)
286         error = new SQLProcessorException(e1);
287       else if (isLoggingError())
288         logError(e1);
289     } finally {
290       try {
291         td.end();
292       } catch (TransactionDemarcationException e2) {
293         if (error == null)
294           error = new SQLProcessorException(e2);
295         else if (isLoggingError())
296           logError(e2);
297       }
298     }
299 
300     if (error != null)
301       throw error;
302     else
303       return rows;
304   }
305 
306   /***
307    * This method is used to create tables in a database. It takes a String that
308    * contains all of the table creation statements and is of the format: CREATE
309    * TABLE foo ( <field specifications> ); ... CREATE TABLE bar ( <field
310    * specifications> ); Specifically, this is the format output by the GSA when
311    * a call is made to generateSQL(); Before making the tables, this large
312    * String will be split apart into an array of individual CREATE TABLE
313    * statements using the createTableBeginDelimiter and createTableEndDelimiter
314    * properties. By default, createTableBeginDelimiter = "CREATE TABLE" and
315    * createTableEndDelimiter = ";"
316    * 
317    * @param String
318    *          pStr - the String containing the CREATE TABLE statements
319    * @param boolean pDrop - indicates whether to drop tables and recreate them
320    *        if the tables already exist in the database
321    * @return boolean true if any tables were created ( or dropped and created )
322    * @exception SQLProcessorException
323    *              if an error occurs trying to create the tables
324    */
325   public boolean createTables(List<String> pStatements, boolean pDrop)
326       throws SQLProcessorException {
327     boolean createdTables = false;
328 
329     // get the create statements to execute and make sure they are
330     // in the proper order with regard to 'references' clauses
331     if (isLoggingDebug())
332       logDebug("Reordering CREATE TABLE statements so references don't fail...");
333     List<String> statements = reorderCreateStatements(pStatements);
334 
335     // if dropping tables, do that before trying to create them
336     // throws exception if all tables can't be dropped
337     List<String> tableNames = getTableNames(statements);
338     if (pDrop) {
339       if (isLoggingInfo())
340         logInfo("Dropping tables...");
341       dropTables(tableNames);
342     }
343 
344     // we can assume that if a table still exists it is because we
345     // didn't try to drop it. If we did try to drop it but couldn't,
346     // dropTables would throw an exception and this code would never
347     // be executed
348     if (isLoggingInfo())
349       logInfo("Creating tables...");
350     Iterator<String> iter = statements.iterator();
351     while (iter.hasNext()) {
352       String statement = iter.next();
353       String name = getTableName(statement);
354       boolean exists = tableExists(name);
355 
356       if (name != null && !exists) {
357         if (isLoggingDebug())
358           logDebug("Creating table: " + name);
359         if (this.getRepository() instanceof InitializingGSA) {
360           if (!isLoggingDebug()
361               && ((InitializingGSA) this.getRepository())
362                   .isLoggingCreateTables())
363             logDebug(statement);
364         }
365         if (this.getRepository() instanceof InitializingVersionRepository) {
366           if (!isLoggingDebug()
367               && ((InitializingVersionRepository) this.getRepository())
368                   .isLoggingCreateTables())
369             logDebug(statement);
370         }
371         if (isDerby())
372           statement = stripNull(statement);
373         createTable(statement);
374         createdTables = true;
375       } else if (name != null && !pDrop) {
376         if (isLoggingInfo())
377           logInfo("Table already exists and dropTablesIfExist is false - not creating: "
378               + name);
379         // dropExistingTables must be false or else table would have been
380         // dropped
381       } else {
382         // throw new SQLProcessorException("The table " + name +
383         // " was not created because name was null or table couldn't be dropped.");
384         logWarning("The table "
385             + name
386             + " was not created because name was null or table couldn't be dropped.");
387       }
388     }
389 
390     return createdTables;
391   }
392 
393   /***
394    * Removes any occurrence of the string "NULL" from a create statement if it
395    * is not preceded by the word "NOT".
396    * 
397    * @param statement
398    * @return
399    */
400   private String stripNull(String statement) {
401     // first make this all uppercase
402 
403     StringBuffer subStatements = new StringBuffer();
404     String tempStatement = statement.toUpperCase();
405     StringTokenizer st = new StringTokenizer(tempStatement, ",");
406     while (st.hasMoreTokens()) {
407       String tok = st.nextToken();
408       int notNullIndex = tok.indexOf("NOT NULL");
409       if (notNullIndex > -1) {
410         // safe to return this unmodified
411         subStatements.append(tok + ",\n");
412       } else if (tok.indexOf("NULL") > -1) {
413         // need to strip this one.
414         // we assume that we can just remove the five characters above
415         String temp = StringUtils.replace(tok, "NULL", "");
416         // we also have to remove all the trailing spaces
417         subStatements.append(temp.trim() + ",\n");
418       } else {
419         // safe to return. no null at all.
420         if (st.hasMoreTokens())
421           subStatements.append(tok + ",\n");
422         else
423           // End of statement, so no comma
424           subStatements.append(tok);
425       }
426     }
427     return subStatements.toString();
428   }
429 
430   private boolean mIsDerby    = false;
431   private boolean mIsDerbySet = false;
432 
433   /***
434    * Returns true if the current database is Apache Derby. The first invocation
435    * to this method will cache its answer.
436    */
437   public boolean isDerby() throws SQLProcessorException {
438     if (!mIsDerbySet) {
439       mIsDerby = isDerbyUncached();
440       mIsDerbySet = true;
441     }
442     return mIsDerby;
443   }
444 
445   /***
446    * Returns true if the current database is Apache Derby This method call is
447    * not cached and will make a database connection attempt on each invocation.
448    * 
449    * @return
450    * @throws SQLProcessorException
451    */
452   private boolean isDerbyUncached() throws SQLProcessorException {
453     boolean isDerby = false;
454     Connection c = null;
455     try {
456       c = getConnection();
457       DatabaseMetaData meta = c.getMetaData();
458       if (APACHE_DERBY.equals(meta.getDatabaseProductName())) {
459         isDerby = true;
460       }
461       return isDerby;
462     } catch (SQLException e) {
463       throw new SQLProcessorException(e);
464     } finally {
465       if (c != null) {
466         try {
467           c.close();
468         } catch (SQLException e) {
469           ; // eat it
470         }
471       }
472     }
473   }
474 
475   /***
476    * This is a method that is used to execute a 'CREATE TABLE' call. The String
477    * you pass in is expected to be of the format CREATE TABLE ( ..... )
478    * 
479    * @return void
480    * @exception SQLProcessorException
481    *              thrown if an error occurs creating the table
482    */
483   private void createTable(String pStr) throws SQLProcessorException {
484     try {
485       performSQL(pStr);
486     } catch (SQLProcessorException spe) {
487       throw new SQLProcessorException(
488           "Caught exception executing create table statement \"" + pStr + "\"",
489           spe);
490     }
491   }
492 
493   /***
494    * This method is used to iteratively drop tables. The iterative effort is
495    * necessary because tables may have references. ASSUMPTION: references only
496    * exist for tables that are defined within this repository. If references
497    * exist from tables outside this repository, this method will throw a
498    * SQLProcessorException
499    * 
500    * @param Vector
501    *          of CREATE TABLE statements indicating which tables to drop
502    * @exception SQLProcessorException
503    *              thrown if all tables can not be dropped
504    */
505   public void dropTablesFromCreateStatements(List<String> pCreateStatements)
506       throws SQLProcessorException {
507     List<String> names = getTableNames(pCreateStatements);
508     dropTables(names);
509   }
510 
511   /***
512    * This method is used to iteratively drop tables. The iterative effort is
513    * necessary because tables may have references. ASSUMPTION: references only
514    * exist for tables that are defined within this repository. If references
515    * exist from tables outside this repository, this method will throw a
516    * SQLProcessorException
517    * 
518    * @param Vector
519    *          of names of tables to be dropped
520    * @exception SQLProcessorException
521    *              thrown if all tables can not be dropped
522    */
523   private void dropTables(List<String> pNames) throws SQLProcessorException {
524     // assuming only one table can be dropped each time, this should take
525     // at most n iterations where n is the nbr of tables being dropped
526     int maxIterations = pNames.size();
527 
528     // every table is tried at least once
529     List<String> tablesToDrop = pNames;
530 
531     List<String> remainingTables;
532     int attempt = 0;
533     do {
534       remainingTables = new ArrayList<String>();
535       Iterator<String> tables = tablesToDrop.iterator();
536       while (tables.hasNext()) {
537         String table = tables.next();
538         if (tableExists(table)) {
539           try {
540             logInfo("Attempting to drop table: " + table);
541             dropTable(table);
542             logInfo("Dropped table: " + table);
543           } catch (SQLProcessorException spe) {
544             // if this is the last iteration, throw an exception
545             if (attempt + 1 >= maxIterations)
546               throw spe;
547 
548             // otherwise track this table for the next try
549             remainingTables.add(table);
550           }
551         }
552       }
553       tablesToDrop = remainingTables;
554 
555     } while ((attempt++ < maxIterations) && (!remainingTables.isEmpty()));
556   }
557 
558   /***
559    * This is a method that is used to execute a 'Drop Table' call. The method
560    * creates the drop table statement by appending the name passed as a method
561    * with the SQL that has been set as the dropTableSQL property. By default,
562    * this property is set to "Drop table"
563    * 
564    * @param String
565    *          - the name of the table to drop
566    * @exception SQLProcessorException
567    *              thrown if an error occurs trying to drop the table
568    */
569   private void dropTable(String pName) throws SQLProcessorException {
570     String sql = getDropTableSQL() + " " + pName;
571 
572     try {
573 
574       logDebug("Attempting to drop table: " + pName);
575 
576       performSQL(sql);
577 
578     } catch (SQLProcessorException spe) {
579       throw new SQLProcessorException(
580           "Caught exception executing drop table statement \"" + sql + "\"",
581           spe);
582     }
583   }
584 
585   /***
586    * This method is used to extract table names from a Vector of CREATE
587    * statements returned by either a call to getCreateStatements() or
588    * getOrderedCreateStatements()
589    * 
590    * @return Vector of table names
591    */
592   private List<String> getTableNames(List<String> pStatements) {
593     if (isLoggingDebug())
594       logDebug("Getting table names...");
595 
596     List<String> names = new ArrayList<String>();
597 
598     // split the big string into a bunch of create table statements
599     List<String> createStatements = pStatements;
600 
601     // now get the table name from each statement
602     Iterator<String> iter = createStatements.iterator();
603     while (iter.hasNext()) {
604       String thisName = getTableName(iter.next());
605 
606       if (thisName != null && !names.contains(thisName)) {
607         names.add(thisName);
608         if (isLoggingDebug())
609           logDebug("Found table name: " + thisName);
610       }
611     }
612 
613     return names;
614   }
615 
616   /***
617    * This is a method used to extract the table name from a CREATE TABLE
618    * statement. It operates by finding the createTableBeginDelimiter and
619    * extracting the next word after the delimiter.
620    * 
621    * @param String
622    *          - the create table statement
623    * @return String - the name of the table; null if name can't be found
624    */
625   private String getTableName(String pStr) {
626     String STATEMENT_BEGIN = getCreateTableBeginDelimiter();
627 
628     if (isLoggingDebug() && (this.getRepository().getDebugLevel() > 6)) {
629       logDebug("Extracting table name from create table statement: " + pStr);
630       logDebug("Name is taken as word after createTableBeginDelimiter.  Delimiter is set to: "
631           + getCreateTableBeginDelimiter());
632     }
633 
634     int index = pStr.indexOf(STATEMENT_BEGIN);
635     if (index == -1) {
636       if (isLoggingWarning())
637         logWarning("Could not extract name because start delimiter could not be found.  Returning null.");
638       return null;
639     }
640 
641     pStr = pStr.substring(index + STATEMENT_BEGIN.length());
642     // loop to get rid of any spaces immediately after the
643     // start delimiter
644     while (pStr.startsWith(" ") && (pStr.length() > 1)) {
645       pStr = pStr.substring(1);
646     }
647 
648     int first_blank = pStr.indexOf(" ");
649     if (!(first_blank > 0)) {
650       if (isLoggingDebug())
651         logDebug("Could not extract name because no word was found after the start delimiter.  Returning null.");
652       return null;
653     }
654 
655     String name = pStr.substring(0, first_blank);
656     if (isLoggingDebug())
657       logDebug("Extracted table name: " + name);
658     return name;
659   }
660 
661   /***
662    * This method is used to break the large string passed to createTables() into
663    * an array of CREATE TABLE statements. The string is split apart using the
664    * createTableBeginDelimiter and createTableEndDelimiter Strings. These can be
665    * set as the createTableBeginDelimiter and createTableEndDelimiter
666    * properties.
667    * 
668    * @param String
669    *          - String containing all the Create Table statements
670    * @return Vector of CREATE TABLE statements
671    */
672   /*
673    * private Vector getCreateStatements( String pStr ) { String STATEMENT_BEGIN
674    * = getCreateTableBeginDelimiter(); String STATEMENT_END =
675    * getCreateTableEndDelimiter();
676    * 
677    * Vector statements = new Vector();
678    * 
679    * // we need to make sure we strip off the potential 'missing tableinfos...'
680    * // error, so we do that by initially recopying the string from the first //
681    * CREATE TABLE. also with this check we catch the condition where the //
682    * string being processed has no CREATE TABLE clauses int index =
683    * pStr.indexOf( STATEMENT_BEGIN );
684    * 
685    * // now loop through and extract all of the CREATE statements String
686    * remaining = pStr.substring( index ); while ( index != -1 ) { int stop =
687    * remaining.indexOf( STATEMENT_END ); if ( stop == -1 ) { // error - this
688    * string is malformed wrt what we expected because no end delimiter was found
689    * if ( isLoggingError() )
690    * logError("malformed string passed to getCreateStatements - an end delimiter '"
691    * + STATEMENT_END +
692    * "' could not be found. Abandoning parsing of Create table statements.");
693    * break; }
694    * 
695    * String thisCreate = remaining.substring(0,stop); if ( isLoggingDebug() )
696    * logDebug("Parsed create statement: " + thisCreate ); statements.add(
697    * thisCreate );
698    * 
699    * // now see if there are any more statements remaining =
700    * remaining.substring( stop + 1 ); index = remaining.indexOf( STATEMENT_BEGIN
701    * ); // need to put this here so that we skip everything between the end of
702    * the previous // CREATE TABLE and the beginning of the next if ( index != -1
703    * ) remaining = remaining.substring( index ); }
704    * 
705    * if ( isLoggingDebug() ) logDebug("Found " + statements.size() +
706    * " create statements.");
707    * 
708    * return statements; }
709    */
710 
711   /***
712    * This method is used to order CREATE TABLE statments such that we do not try
713    * to create a table before any tables that it references. NOTE: if a
714    * reference exists for a table outside of this repository we will print a
715    * warning, but will _not_ throw an exception. If the referenced table doesn't
716    * exist, an exception will be thrown when the referencing table is created.
717    * 
718    * @param String
719    *          containing all of the CREATE TABLE statements as generated by a
720    *          call to GSARepository.generateSQL()
721    * @return Vector of individual CREATE statements that are in the proper order
722    *         to execute
723    * @exception SQLProcessorException
724    *              if we detect a bad loop trying to resolve references
725    */
726   private List<String> reorderCreateStatements(List<String> pStatements)
727       throws SQLProcessorException {
728     List<String> statements = pStatements;
729     List<String> names = getTableNames(statements);
730     List<String> orderedStatements = new ArrayList<String>();
731 
732     // hashmap containing one entry for every table that references
733     // another, and holds Vector of those tables it is waiting to be made
734     HashMap<String, List<String>> refersTo = new HashMap<String, List<String>>();
735     // hashmap containing one entry for every table that is references by
736     // another, and holds Vector of all the tables that reference it
737     HashMap<String, List<String>> referencedBy = new HashMap<String, List<String>>();
738 
739     // setup the tables so we know who makes which references
740     Iterator<String> iter = statements.iterator();
741     while (iter.hasNext()) {
742       String statement = iter.next();
743       String tableName = getTableName(statement);
744       List<String> references = getTableReferences(statement, tableName);
745 
746       if (references.size() < 1) {
747         orderedStatements.add(statement);
748       } else {
749         // organize the references this table has
750         if (!checkReferencesInRepository(names, references)) {
751           if (isLoggingWarning())
752             logWarning("Table " + tableName
753                 + " references a table outside the repository.");
754         }
755 
756         // create an entry in 'refersTo' for this table
757         refersTo.put(tableName, references);
758 
759         // update referencedBy to include this table
760         Iterator<String> refs = references.iterator();
761         while (refs.hasNext()) {
762           String ref = refs.next();
763           List<String> v;
764           if (!referencedBy.containsKey(ref)) {
765             v = new ArrayList<String>();
766             v.add(tableName);
767             referencedBy.put(ref, v);
768           } else {
769             v = referencedBy.get(ref);
770             v.add(tableName);
771           }
772         }
773       }
774     }
775 
776     // removed all of the previously the ordered statements
777     iter = orderedStatements.iterator();
778     while (iter.hasNext()) {
779       String statement = iter.next();
780       statements.remove(statement);
781     }
782 
783     // now that we know all the references, order them appropriately
784     // assuming we add one table per loop, this should take at most
785     // n iterations where n is the starting number of statements to add
786     int maxTries = statements.size();
787     int attempt = 0;
788     while (statements.size() > 0) {
789       Iterator<String> iterator = statements.iterator();
790       List<String> newlyAdded = new ArrayList<String>();
791       while (iterator.hasNext()) {
792         String statement = iterator.next();
793         String tableName = getTableName(statement);
794 
795         // is this table isn't waiting for another table, add it
796         if (!refersTo.containsKey(tableName)) {
797           // this would be an error condition !!
798         } else {
799           List<String> waitingOnTables = refersTo.get(tableName);
800           boolean okToAdd = true;
801           Iterator<String> i = waitingOnTables.iterator();
802           while (i.hasNext()) {
803             String waitingOn = i.next();
804             if (refersTo.containsKey(waitingOn)) {
805               okToAdd = false;
806             }
807           }
808 
809           if (okToAdd) {
810             orderedStatements.add(statement);
811             newlyAdded.add(statement);
812             // let the other tables know this one is made
813             if (referencedBy.containsKey(tableName)) {
814               List<String> tablesWaiting = referencedBy.get(tableName);
815               Iterator<String> j = tablesWaiting.iterator();
816               while (j.hasNext()) {
817                 String table = j.next();
818                 List<String> v = refersTo.get(table);
819                 v.remove(tableName);
820               }
821             }
822           }
823         }
824       }
825 
826       // after each iteration, remove the newlyAdded statements from the list
827       Iterator<String> k = newlyAdded.iterator();
828       while (k.hasNext()) {
829         String s = k.next();
830         statements.remove(s);
831       }
832 
833       // make sure we aren't looping infinitely
834       if (attempt++ > maxTries) {
835         if (isLoggingError()) {
836           logError("Still trying to resolve: ");
837           Iterator<String> left = statements.iterator();
838           while (left.hasNext()) {
839             String table = left.next();
840             logError(table);
841           }
842         }
843         throw new SQLProcessorException(
844             "Could not order tables appropriately...failing.  Turn on loggingDebug for more info.");
845       }
846     }
847 
848     return orderedStatements;
849   }
850 
851   /***
852    * This method is used to extract the names of other tables that a table
853    * references. expected format is: CREATE TABLE foo ( x int not null
854    * references bar(id), y varchar null references doo(id), ... )
855    * 
856    * @param String
857    *          the CREATE TABLE statement
858    * @param String
859    *          the name of the table
860    * @return Vector containing names of referenced tables
861    * @exception SQLProcessorException
862    *              if the table has a reference to itself
863    */
864   private List<String> getTableReferences(String pStr, String tableName)
865       throws SQLProcessorException {
866     String REFERENCES = " references ";
867     List<String> refs = new ArrayList<String>();
868 
869     int start = pStr.toLowerCase().indexOf(REFERENCES);
870     while (start != -1) {
871       pStr = pStr.substring(start + REFERENCES.length());
872       String ref = pStr;
873       // stop at a '('
874       int stop = ref.indexOf("(");
875       ref = ref.substring(0, stop);
876       // remove spaces
877       ref = ref.trim();
878 
879       // bail if the table references itself - i think that is just wrong
880       /*
881        * actually, jeff and mike said this should be allowed, so i won't throw
882        * an exception, i just won't add it to the list of references either...
883        */
884       if (ref.equalsIgnoreCase(tableName)) {
885         // do nothing
886         // throw new SQLProcessorException("The create statement for table " +
887         // tableName +
888         // " contains a reference to itself.");
889       } else if ((ref.length() > 0) && !refs.contains(ref))
890         refs.add(ref);
891 
892       start = pStr.toLowerCase().indexOf(REFERENCES);
893     }
894 
895     if (isLoggingDebug()) {
896       Iterator<String> i = refs.iterator();
897       while (i.hasNext()) {
898         String s = i.next();
899         logDebug("Found reference: " + s);
900       }
901     }
902 
903     return refs;
904   }
905 
906   /***
907    * This method is used to determine whether all the items in the second Vector
908    * are contained in the first Vector.
909    * 
910    * @param Vector
911    *          of the names of all the tables in the repository
912    * @param Vector
913    *          of the names of all the tables to check for
914    * @return boolean true if all items are in the Vector; false otherwise
915    */
916   private boolean checkReferencesInRepository(List<String> pRepositoryTables,
917       List<String> pCheckTables) {
918     Iterator<String> iter = pCheckTables.iterator();
919     while (iter.hasNext()) {
920       String name = iter.next();
921       if (!pRepositoryTables.contains(name))
922         return false;
923     }
924     return true;
925   }
926 
927   /***
928    * Method to determine whether a table already exists in the database. The
929    * method operates by appending the name passed as a parameter to the String
930    * that has been set in the determineTableExistsSQL property
931    * 
932    * @param String
933    *          - name of table to check for existence of
934    * @return boolean - true if table exists; false otherwise
935    */
936   private boolean tableExists(String pTableName) {
937     // don't bother with query if name is invalid
938     if (pTableName == null || pTableName.length() == 0)
939       return false;
940 
941     // create sql
942     String sql = getDetermineTableExistsSQL() + " " + pTableName;
943 
944     // execute and check for an exception
945     try {
946 
947       performSQL(sql);
948 
949     } catch (SQLProcessorException spe) {
950       // we should only get an exception here if the table does NOT
951       // exist. in that case, don't throw the exception - just return false
952       if (isLoggingDebug()) {
953         logDebug("Table existence is determined by whether an exception is received when querying the table.");
954         logDebug("Caught exception checking whether table exists, so table doesn't exist.");
955         logDebug("Checked for existence with this statement \"" + sql + "\"");
956         logDebug("Set repository debugLevel > 6 to see full exception.");
957         if (this.getRepository().getDebugLevel() > 6)
958           logDebug(spe);
959       }
960 
961       return false;
962     }
963 
964     return true;
965   }
966 
967   /***
968    * Returns true if there is at least one table in this schema
969    * This is handy for Derby since it will throw an error if one
970    * attempts to try a select statement to determine if a table
971    * exists and the schema has not yet been created.
972    * @return
973    */
974   private boolean hasAnyTables() {
975     boolean foundTables = false;
976     Connection c = null;
977     try {
978       c = getConnection();
979       DatabaseMetaData metadata = null;
980       metadata = c.getMetaData();
981       String[] names = { "TABLE" };
982       ResultSet tableNames = metadata.getTables(null, null, null, names);
983       while (tableNames.next()) {
984         String tab = tableNames.getString("TABLE_NAME");
985         foundTables = true;
986         break;
987       }
988       tableNames.close();
989     } catch (SQLProcessorException e) {
990       e.printStackTrace();
991     } catch (SQLException e) {
992       e.printStackTrace();
993     } finally {
994       try {
995         c.close();
996       } catch (SQLException e) {
997         ;
998       }
999     }
1000     return foundTables;
1001   }
1002 
1003   /* These methods are really used - they were just part of mgk's original class */
1004 
1005   // ---------- Property: (read-only) InsertSQL ----------
1006   /*** SQL to insert a new id space into the DB table */
1007   transient String mInsertSQL;
1008 
1009   /***
1010    * Get property <code>InsertSQL</code>. The SQL is lazily generated.
1011    * 
1012    * @beaninfo description: SQL to insert a new id space into the DB table
1013    * @return InsertSQL
1014    **/
1015 // private String getInsertSQL()
1016 // {
1017 // // build SQL string if needed
1018 // if (mInsertSQL == null)
1019 // {
1020 // StringBuffer buf = new StringBuffer(300);
1021 // buf.append("INSERT INTO ");
1022 // /*
1023 // buf.append(getTableName());
1024 // buf.append('(');
1025 // buf.append(getNameColumn()).append(',');
1026 // buf.append(getSeedColumn()).append(',');
1027 // buf.append(getBatchSizeColumn()).append(',');
1028 // buf.append(getPrefixColumn()).append(',');
1029 // buf.append(getSuffixColumn());
1030 // */
1031 //
1032 // buf.append(')').append('\n');
1033 // buf.append("VALUES (?, ?, ?, ?, ?)\n");
1034 //
1035 // mInsertSQL = buf.toString();
1036 // }
1037 //
1038 // return mInsertSQL;
1039 // }
1040 
1041   // ---------- Property: (read-only) UpdateSQL ----------
1042   /*** SQL to execute to update a specific id space in the DB */
1043   transient String mUpdateSQL;
1044 
1045   /***
1046    * Get property <code>UpdateSQL</code>. The SQL is lazily generated.
1047    * 
1048    * @beaninfo description: SQL to execute to update a specific id space int the
1049    *           D0B
1050    * @return UpdateSQL
1051    **/
1052 // private String getUpdateSQL()
1053 // {
1054 // // generate SQL if needed
1055 // if (mUpdateSQL == null)
1056 // {
1057 // StringBuffer buf = new StringBuffer(300);
1058 // buf.append("UPDATE ");
1059 // /*
1060 // buf.append(getTableName());
1061 // buf.append(" SET ");
1062 // buf.append(getSeedColumn()).append('=');
1063 // buf.append(getSeedColumn()).append('+');
1064 // buf.append(getBatchSizeColumn());
1065 // buf.append(" WHERE ");
1066 // buf.append(getNameColumn()).append(" = ?");
1067 //
1068 // */
1069 // mUpdateSQL = buf.toString();
1070 // }
1071 //
1072 // return mUpdateSQL;
1073 // }
1074 
1075   // ---------- Property: (read-only) SelectSQL ----------
1076   /*** SQL to execute to load a specific id space from the DB */
1077   transient String mSelectSQL;
1078 
1079   /***
1080    * Get property <code>SelectSQL</code>. The SQL is lazily generated.
1081    * 
1082    * @beaninfo description: SQL to execute to load a specific id space from the
1083    *           DB
1084    * @return SelectSQL
1085    **/
1086 // private String getSelectSQL()
1087 // {
1088 // // generate SQL if needed
1089 // if (mSelectSQL == null)
1090 // {
1091 // StringBuffer buf = new StringBuffer(300);
1092 // buf.append("SELECT ");
1093 // /*
1094 // buf.append(getSeedColumn()).append(',');
1095 // buf.append(getBatchSizeColumn()).append(',');
1096 // buf.append(getPrefixColumn()).append(',');
1097 // buf.append(getSuffixColumn());
1098 // buf.append("  FROM ");
1099 // buf.append(getTableName());
1100 // buf.append(" WHERE ");
1101 // buf.append(getNameColumn()).append(" = ?");
1102 // */
1103 //
1104 // mSelectSQL = buf.toString();
1105 // }
1106 //
1107 // return mSelectSQL;
1108 // }
1109 
1110 }