EMMA Coverage Report (generated Tue Nov 24 15:49:41 EST 2009)
[all classes][atg.adapter.gsa]

COVERAGE SUMMARY FOR SOURCE FILE [SQLProcessorEngine.java]

nameclass, %method, %block, %line, %
SQLProcessorEngine.java0%   (0/1)0%   (0/34)0%   (0/1383)0%   (0/370)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SQLProcessorEngine0%   (0/1)0%   (0/34)0%   (0/1383)0%   (0/370)
SQLProcessorEngine (): void 0%   (0/1)0%   (0/21)0%   (0/8)
SQLProcessorEngine (GSARepository): void 0%   (0/1)0%   (0/29)0%   (0/10)
checkReferencesInRepository (List, List): boolean 0%   (0/1)0%   (0/19)0%   (0/7)
close (Connection): void 0%   (0/1)0%   (0/13)0%   (0/7)
close (Statement): void 0%   (0/1)0%   (0/13)0%   (0/7)
createTable (String): void 0%   (0/1)0%   (0/22)0%   (0/5)
createTables (List, boolean): boolean 0%   (0/1)0%   (0/144)0%   (0/35)
doStartService (): void 0%   (0/1)0%   (0/14)0%   (0/4)
dropTable (String): void 0%   (0/1)0%   (0/44)0%   (0/7)
dropTables (List): void 0%   (0/1)0%   (0/72)0%   (0/20)
dropTablesFromCreateStatements (List): void 0%   (0/1)0%   (0/8)0%   (0/3)
getConnection (): Connection 0%   (0/1)0%   (0/33)0%   (0/11)
getCreateTableBeginDelimiter (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getCreateTableEndDelimiter (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getDataSource (): DataSource 0%   (0/1)0%   (0/3)0%   (0/1)
getDetermineTableExistsSQL (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getDropTableSQL (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getRepository (): GSARepository 0%   (0/1)0%   (0/3)0%   (0/1)
getTableName (String): String 0%   (0/1)0%   (0/101)0%   (0/21)
getTableNames (List): List 0%   (0/1)0%   (0/50)0%   (0/13)
getTableReferences (String, String): List 0%   (0/1)0%   (0/83)0%   (0/21)
hasAnyTables (): boolean 0%   (0/1)0%   (0/68)0%   (0/22)
isDerby (): boolean 0%   (0/1)0%   (0/13)0%   (0/4)
isDerbyUncached (): boolean 0%   (0/1)0%   (0/42)0%   (0/13)
performSQL (String): int 0%   (0/1)0%   (0/140)0%   (0/31)
reorderCreateStatements (List): List 0%   (0/1)0%   (0/267)0%   (0/77)
setCreateTableBeginDelimiter (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
setCreateTableEndDelimiter (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
setDataSource (DataSource): void 0%   (0/1)0%   (0/4)0%   (0/2)
setDetermineTableExistsSQL (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
setDropTableSQL (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
setRepository (GSARepository): void 0%   (0/1)0%   (0/4)0%   (0/2)
stripNull (String): String 0%   (0/1)0%   (0/84)0%   (0/17)
tableExists (String): boolean 0%   (0/1)0%   (0/61)0%   (0/15)

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 
12package atg.adapter.gsa;
13 
14import java.sql.Connection;
15import java.sql.DatabaseMetaData;
16import java.sql.ResultSet;
17import java.sql.SQLException;
18import java.sql.Statement;
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.List;
23import java.util.StringTokenizer;
24 
25import javax.sql.DataSource;
26 
27import atg.core.util.StringUtils;
28import atg.dtm.TransactionDemarcation;
29import atg.dtm.TransactionDemarcationException;
30import atg.nucleus.GenericService;
31import 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 **/
40public 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}

[all classes][atg.adapter.gsa]
EMMA 2.0.5312 (C) Vladimir Roubtsov