KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > triactive > jdo > store > StoreManager


1 /*
2  * Copyright 2004 (C) TJDO.
3  * All rights reserved.
4  *
5  * This software is distributed under the terms of the TJDO License version 1.0.
6  * See the terms of the TJDO License in the documentation provided with this software.
7  *
8  * $Id: StoreManager.java,v 1.18 2004/01/18 03:01:06 jackknifebarber Exp $
9  */

10
11 package com.triactive.jdo.store;
12
13 import com.triactive.jdo.ClassNotPersistenceCapableException;
14 import com.triactive.jdo.PersistenceManager;
15 import com.triactive.jdo.PersistenceManagerFactoryImpl;
16 import com.triactive.jdo.SchemaManager;
17 import com.triactive.jdo.StateManager;
18 import com.triactive.jdo.model.ClassMetaData;
19 import com.triactive.jdo.model.FieldMetaData;
20 import com.triactive.jdo.model.MetaData;
21 import com.triactive.jdo.util.MacroString;
22 import com.triactive.jdo.util.SoftValueMap;
23 import java.sql.Connection JavaDoc;
24 import java.sql.DatabaseMetaData JavaDoc;
25 import java.sql.ResultSet JavaDoc;
26 import java.sql.SQLException JavaDoc;
27 import java.sql.SQLWarning JavaDoc;
28 import java.sql.Statement JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Collections JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.HashSet JavaDoc;
33 import java.util.Iterator JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.ListIterator JavaDoc;
36 import java.util.Map JavaDoc;
37 import javax.jdo.Extent;
38 import javax.jdo.JDODataStoreException;
39 import javax.jdo.JDOException;
40 import javax.jdo.JDOFatalException;
41 import javax.jdo.JDOUserException;
42 import javax.sql.DataSource JavaDoc;
43 import org.apache.log4j.Category;
44
45
46 /**
47  * Manages the contents of a data store (aka database schema) on behalf of a
48  * particular PersistenceManagerFactory and all its persistent instances.
49  * <p>
50  * The store manager's responsibilities include:
51  * <ul>
52  * <li>Creating and/or validating database tables according to the persistent
53  * classes being accessed by the application.</li>
54  * <li>Serving as the primary intermediary between StateManagers and the
55  * database (implements insert(), fetch(), update(), delete()).</li>
56  * <li>Managing TJDO's schema table (JDO_TABLE).
57  * <li>Serving as the base Extent and Query factory.</li>
58  * <li>Providing cached access to JDBC database metadata.</li>
59  * <li>Resolving SQL identifier macros to actual SQL identifiers.</li>
60  * </ul>
61  * <p>
62  * A store manager's knowledge of its schema's contents is not necessarily
63  * complete.
64  * It is aware of only those tables whose classes have somehow been accessed,
65  * directly or indirectly, by the application during the life of the store
66  * manager object.
67  *
68  * @author <a HREF="mailto:mmartin5@austin.rr.com">Mike Martin</a>
69  * @version $Revision: 1.18 $
70  */

71
72 public class StoreManager implements SchemaManager
73 {
74     private static final Category LOG = Category.getInstance(StoreManager.class);
75
76     /**
77      * The amount of time before we expire any cached column info. Hardcoded to
78      * five minutes.
79      */

80     private static final int COLUMN_INFO_EXPIRATION_MS = 5 * 60 * 1000;
81
82     private final DataSource JavaDoc ds;
83     private final String JavaDoc userName;
84     private final String JavaDoc password;
85     private final DatabaseAdapter dba;
86     private final int tableValidationFlags;
87     private final int constraintValidationFlags;
88     private final String JavaDoc schemaName;
89
90     /*
91      * Access to fields below is synchronized on the StoreManager object.
92      * Note: if DB work must also be done, the DB connection must be
93      * acquired *before* locking the StoreManager in order to avoid deadlocks.
94      */

95     private ArrayList JavaDoc allTables = new ArrayList JavaDoc();
96     private ArrayList JavaDoc tablesByTableID = new ArrayList JavaDoc();
97     private HashMap JavaDoc tablesByName = new HashMap JavaDoc();
98     private HashMap JavaDoc tablesByJavaID = new HashMap JavaDoc();
99     private SchemaTable schemaTable = null;
100     private Map columnInfoByTableName = new HashMap JavaDoc();
101     private long columnInfoReadTimestamp = -1;
102
103     /**
104      * The cache of database requests. Access is synchronized on the map
105      * object itself.
106      */

107     private Map requestsByID = Collections.synchronizedMap(new SoftValueMap());
108
109     /**
110      * The active class adder transaction, if any.
111      * Some StoreManager methods are called recursively in the course of adding
112      * new classes.
113      * This field allows such methods to coordinate with the active ClassAdder
114      * transaction.
115      * Recursive methods include:
116      * <ul>
117      * <li>addClasses()</li>
118      * <li>newSetTable()</li>
119      * <li>newMapTable()</li>
120      * </ul>
121      * Access is synchronized on the StoreManager itself.
122      * Invariant: classAdder == null if StoreManager is unlocked.
123      */

124     private ClassAdder classAdder = null;
125
126
127     /**
128      * Constructs a new StoreManager.
129      * On successful return the new StoreManager will have successfully
130      * connected to the database with the given credentials and determined the
131      * schema name, but will not have inspected the schema contents any further.
132      * The contents (tables, views, etc.) will be subsequently created and/or
133      * validated on-demand as the application accesses persistent classes.
134      * <p>
135      * To avoid creating unnecessary redundant StoreManagers, new StoreManagers
136      * should always be obtained from the {@link StoreManagerFactory}, rather
137      * than constructed directly.
138      *
139      * @param pmf
140      * The corresponding PersistenceManagerFactory. This factory's
141      * non-transactional data source will be used to get database
142      * connections as needed to perform management functions.
143      * @param userName
144      * The database user name.
145      * @param password
146      * The database user's password.
147      *
148      * @exception JDODataStoreException
149      * If the database could not be accessed or the name of the schema
150      * could not be determined.
151      *
152      * @see StoreManagerFactory
153      */

154
155     StoreManager(PersistenceManagerFactoryImpl pmf, String JavaDoc userName, String JavaDoc password)
156     {
157         this.ds = pmf.getNontransactionalDataSource();
158         this.userName = userName;
159         this.password = password;
160
161         int validateTables = pmf.getValidateTables() ? Table.VALIDATE : 0;
162         int validateConstraints = pmf.getValidateConstraints() ? Table.VALIDATE : 0;
163         int autoCreate = pmf.getAutoCreateTables() ? Table.AUTO_CREATE : 0;
164
165         tableValidationFlags = validateTables | autoCreate;
166         constraintValidationFlags = validateConstraints | autoCreate;
167
168         try
169         {
170             Connection JavaDoc conn;
171             
172             if (userName == null)
173                 conn = ds.getConnection();
174             else
175                 conn = ds.getConnection(userName, password);
176
177             try
178             {
179                 dba = DatabaseAdapter.getInstance(conn);
180                 String JavaDoc name;
181                 
182                 try
183                 {
184                     name = dba.getSchemaName(conn);
185                 }
186                 catch (UnsupportedOperationException JavaDoc e)
187                 {
188                     /*
189                      * The DatabaseAdapter doesn't know how to determine the
190                      * current schema name. As a fallback, create a temporary
191                      * probe table which can look itself up in the JDBC metadata
192                      * and thereby find out what schema we're in.
193                      */

194                     ProbeTable pt = new ProbeTable(this);
195                     pt.initialize();
196                     pt.create(conn);
197
198                     try
199                     {
200                         name = pt.findSchemaName(conn);
201                     }
202                     finally
203                     {
204                         pt.drop(conn);
205                     }
206                 }
207
208                 schemaName = name;
209             }
210             finally
211             {
212                 conn.close();
213             }
214         }
215         catch (SQLException JavaDoc e)
216         {
217             throw new JDODataStoreException("Failed initializing database", e);
218         }
219     }
220
221
222     /**
223      * Clears all knowledge of tables, cached requests, metadata, etc and resets
224      * the store manager to its initial state.
225      */

226
227     public synchronized void reset()
228     {
229         allTables.clear();
230         tablesByTableID.clear();
231         tablesByName.clear();
232         tablesByJavaID.clear();
233         schemaTable = null;
234
235         columnInfoByTableName.clear();
236         columnInfoReadTimestamp = -1;
237
238         requestsByID.clear();
239     }
240
241
242     /**
243      * Asserts that the schema has been initialized, meaning the schema table
244      * (JDO_TABLE) exists and is valid, and the schemaTable field is non-null.
245      * <p>
246      * This method must be called with the StoreManager's monitor unlocked,
247      * since it may invoke a management transaction.
248      */

249
250     private void checkSchemaInitialized()
251     {
252         //assert !Thread.holdsLock(this); // only possible with 1.4
253

254         synchronized (this)
255         {
256             if (schemaTable != null)
257                 return;
258         }
259
260         MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_SERIALIZABLE)
261         {
262             public String JavaDoc toString()
263             {
264                 return "Initialize schema table for " + schemaName;
265             }
266
267             protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
268             {
269                 initializeSchemaTable(true, conn);
270             }
271         };
272
273         mtx.execute();
274     }
275
276
277     /**
278      * Initializes the schemaTable field.
279      *
280      * @param validate
281      * <code>true</code> to validate the table even if it doesn't exist.
282      * If auto-create mode is on this will cause it to be created.
283      * @param conn
284      * The connection to use.
285      *
286      * @return
287      * <code>true</code> if the schemaTable field has been set or was
288      * already set,
289      * <code>false</code> if validation is false and the table doesn't
290      * exist.
291      */

292     private synchronized boolean initializeSchemaTable(boolean validate, Connection JavaDoc conn) throws SQLException JavaDoc
293     {
294         if (schemaTable != null)
295             return true;
296
297         try
298         {
299             SchemaTable st = new SchemaTable(this);
300             st.initialize();
301
302             if (validate || st.exists(conn))
303             {
304                 st.validate(tableValidationFlags, conn);
305
306                 LOG.info("Schema initialized: " + schemaName);
307
308                 schemaTable = st;
309                 return true;
310             }
311             else
312                 return false;
313         }
314         finally
315         {
316             if (schemaTable == null)
317                 reset();
318         }
319     }
320
321
322     /**
323      * Returns the database adapter used by this store manager.
324      *
325      * @return The database adapter used by this store manager.
326      */

327
328     public DatabaseAdapter getDatabaseAdapter()
329     {
330         return dba;
331     }
332
333
334     public String JavaDoc getSchemaName()
335     {
336         return schemaName;
337     }
338
339
340     /**
341      * Logs SQL warnings to the common log. Should be called after any
342      * operation on a JDBC <tt>Statement</tt> or <tt>ResultSet</tt>
343      * object as:
344      *
345      * <blockquote><pre>
346      * storeMgr.logSQLWarnings(obj.getWarnings());
347      * </pre></blockquote>
348      *
349      * If any warnings were generated, the entire list of them are logged
350      * to <tt>System.err</tt>.
351      *
352      * @param warning the value returned from getWarnings().
353      */

354
355     public void logSQLWarnings(SQLWarning JavaDoc warning)
356     {
357         while (warning != null)
358         {
359             LOG.warn("SQL warning: " + warning);
360
361             warning = warning.getNextWarning();
362         }
363     }
364
365
366     public void logSQLWarnings(Connection JavaDoc conn)
367     {
368         try
369         {
370             logSQLWarnings(conn.getWarnings());
371         }
372         catch (SQLException JavaDoc e)
373         {
374             throw dba.newDataStoreException("Error obtaining warnings from connection " + conn, e);
375         }
376     }
377
378
379     public void logSQLWarnings(Statement JavaDoc stmt)
380     {
381         try
382         {
383             logSQLWarnings(stmt.getWarnings());
384         }
385         catch (SQLException JavaDoc e)
386         {
387             throw dba.newDataStoreException("Error obtaining warnings from statement " + stmt, e);
388         }
389     }
390
391
392     public void logSQLWarnings(ResultSet JavaDoc rs)
393     {
394         try
395         {
396             logSQLWarnings(rs.getWarnings());
397         }
398         catch (SQLException JavaDoc e)
399         {
400             throw dba.newDataStoreException("Error obtaining warnings from result set " + rs, e);
401         }
402     }
403
404
405     /**
406      * Adds the given persistence-capable classes to the store manager's set
407      * of active classes ready for persistence.
408      * The act of adding a class to the store manager causes any necessary
409      * database objects (tables, views, constraints, indexes, etc.) to be
410      * created.
411      * <p>
412      * Other StoreManager methods may cause classes to be added as a side
413      * effect.
414      *
415      * @param classes The class(es) to be added.
416      */

417
418     public void addClasses(Class JavaDoc[] classes)
419     {
420         checkSchemaInitialized();
421
422         synchronized (this)
423         {
424             if (classAdder != null)
425             {
426                 /*
427                  * addClasses() has been recursively re-entered: just add table
428                  * objects for the requested classes and return.
429                  */

430                 classAdder.addClasses(classes);
431                 return;
432             }
433         }
434
435         new ClassAdder(classes).execute();
436     }
437
438
439     /**
440      * Called by Mapping objects in the midst of StoreManager.addClasses()
441      * to request the creation of a set table.
442      *
443      * @param cbt The base table for the class containing the set.
444      * @param fmd The field metadata describing the set field.
445      */

446
447     synchronized SetTable newSetTable(ClassBaseTable cbt, FieldMetaData fmd)
448     {
449         if (classAdder == null)
450             throw new IllegalStateException JavaDoc("SetTables can only be created as a side effect of adding a new class");
451
452         return classAdder.newSetTable(cbt, fmd);
453     }
454
455
456     /**
457      * Called by Mapping objects in the midst of StoreManager.addClasses()
458      * to request the creation of a map table.
459      *
460      * @param cbt The base table for the class containing the map.
461      * @param fmd The field metadata describing the map field.
462      */

463
464     synchronized MapTable newMapTable(ClassBaseTable cbt, FieldMetaData fmd)
465     {
466         if (classAdder == null)
467             throw new IllegalStateException JavaDoc("MapTables can only be created as a side effect of adding a new class");
468
469         return classAdder.newMapTable(cbt, fmd);
470     }
471
472
473     public void dropTablesFor(final Class JavaDoc[] classes)
474     {
475         MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_READ_COMMITTED)
476         {
477             public String JavaDoc toString()
478             {
479                 return "Drop tables for selected classes from schema " + schemaName;
480             }
481
482             protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
483             {
484                 synchronized (StoreManager.this)
485                 {
486                     try
487                     {
488                         if (initializeSchemaTable(false, conn))
489                             schemaTable.dropTablesFor(classes, conn);
490                     }
491                     finally
492                     {
493                         reset();
494                     }
495                 }
496             }
497         };
498
499         mtx.execute();
500     }
501
502
503     public void dropAllTables()
504     {
505         MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_READ_COMMITTED)
506         {
507             public String JavaDoc toString()
508             {
509                 return "Drop all tables from schema " + schemaName;
510             }
511
512             protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
513             {
514                 synchronized (StoreManager.this)
515                 {
516                     try
517                     {
518                         if (initializeSchemaTable(false, conn))
519                         {
520                             schemaTable.dropAllTables(conn);
521                             schemaTable.drop(conn);
522                         }
523                     }
524                     finally
525                     {
526                         reset();
527                     }
528                 }
529             }
530         };
531
532         mtx.execute();
533     }
534
535
536     /**
537      * Returns the JDO table having the given SQL name, if any.
538      * Returns <code>null</code> if no such table is (yet) known to the store
539      * manager.
540      *
541      * @param name The name of the table.
542      *
543      * @return The corresponding JDO table, or <code>null</code>.
544      */

545
546     public synchronized JDOTable getTable(SQLIdentifier name)
547     {
548         return (JDOTable)tablesByName.get(name);
549     }
550
551
552     /**
553      * Returns the JDO table having the given table ID.
554      * Returns <code>null</code> if no such table is (yet) known to the store
555      * manager.
556      *
557      * @param tableID
558      * The table ID of the table to be returned.
559      *
560      * @return The corresponding JDO table, or <code>null</code>.
561      */

562
563     public synchronized JDOTable getTable(int tableID)
564     {
565         if (tableID < 0 || tableID >= tablesByTableID.size())
566             return null;
567         else
568             return (JDOTable)tablesByTableID.get(tableID);
569     }
570
571
572     /**
573      * Returns the JDO table having the given metadata, if any.
574      * Returns <code>null</code> if no such table is (yet) known to the store
575      * manager.
576      *
577      * @param md The metadata for the table.
578      *
579      * @return The corresponding JDO table, or <code>null</code>.
580      */

581
582     synchronized JDOTable getTable(MetaData md)
583     {
584         return (JDOTable)tablesByJavaID.get(md.getJavaName());
585     }
586
587
588     /**
589      * Returns the primary table serving as backing for the given class.
590      * If the class is not yet known to the store manager, {@link #addClasses}
591      * is called to add it.
592      *
593      * @param c The class whose table is be returned.
594      *
595      * @return The corresponding class table.
596      *
597      * @exception NoExtentException
598      * If the given class is not persistence-capable.
599      */

600
601     public ClassTable getTable(Class JavaDoc c)
602     {
603         ClassTable ct;
604
605         synchronized (this)
606         {
607             ct = (ClassTable)tablesByJavaID.get(c.getName());
608         }
609
610         if (ct == null)
611         {
612             addClasses(new Class JavaDoc[] { c });
613
614             /* Retry. */
615             synchronized (this)
616             {
617                 ct = (ClassTable)tablesByJavaID.get(c.getName());
618             }
619
620             if (ct == null)
621                 throw new NoExtentException(c);
622         }
623
624         return ct;
625     }
626
627
628     /**
629      * Returns the primary table serving as backing for the given class.
630      * If the class is not yet known to the store manager, {@link #addClasses}
631      * is called to add it.
632      * <p>
633      * This method is functionally equivalent to {@link #getTable(Class)}
634      * except that it will only return base tables (not views).
635      *
636      * @param c The class whose table is be returned.
637      *
638      * @return The corresponding class table.
639      *
640      * @exception NoExtentException
641      * If the given class is not persistence-capable.
642      * @exception ViewNotSupportedException
643      * If the given class is backed by a view.
644      */

645
646     public ClassBaseTable getClassBaseTable(Class JavaDoc c)
647     {
648         ClassTable t = getTable(c);
649
650         if (!(t instanceof ClassBaseTable))
651             throw new ViewNotSupportedException(c); // must be a ClassView
652

653         return (ClassBaseTable)t;
654     }
655
656
657     /**
658      * Returns the Java name of the JDO table having the given table ID.
659      * Returns <code>null</code> if no such table exists in the schema.
660      * <p>
661      * If the table is not (yet) known to the store manager, this method does
662      * <em>not</em> initialize it.
663      *
664      * @param tableID
665      * The table ID of the table to be returned.
666      *
667      * @return The corresponding Java name, or <code>null</code>.
668      */

669
670     public String JavaDoc getJavaName(final int tableID)
671     {
672         JDOTable table = getTable(tableID);
673
674         if (table != null)
675             return table.getJavaName();
676         else
677         {
678             checkSchemaInitialized();
679
680             final String JavaDoc s[] = new String JavaDoc[1];
681
682             MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_READ_COMMITTED)
683             {
684                 public String JavaDoc toString()
685                 {
686                     return "Query schema table for schema " + schemaName;
687                 }
688
689                 protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
690                 {
691                     synchronized (StoreManager.this)
692                     {
693                         if (schemaTable != null)
694                             s[0] = schemaTable.getJavaName(tableID, conn);
695                     }
696                 }
697             };
698
699             mtx.execute();
700
701             String JavaDoc javaName = s[0];
702
703             if (javaName == null)
704                 throw new JDOUserException("Unknown table ID = " + tableID);
705
706             return javaName;
707         }
708     }
709
710     public Extent getExtent(PersistenceManager pm, Class JavaDoc c, boolean subclasses)
711     {
712         ClassTable t = getTable(c);
713
714         return t.newExtent(pm, subclasses);
715     }
716
717     public Query getQuery(PersistenceManager pm, Object JavaDoc query)
718     {
719         return getQuery("javax.jdo.query.JDOQL", pm, query);
720     }
721
722     public Query getQuery(String JavaDoc language, PersistenceManager pm, Object JavaDoc query)
723     {
724         Query q;
725
726         if (language.equals("javax.jdo.query.JDOQL"))
727         {
728             if (query != null && !(query instanceof JDOQLQuery))
729                 throw new JDOUserException("Invalid query argument, " + query + ", should be an object of type " + JDOQLQuery.class.getName());
730
731             q = new JDOQLQuery(pm, this, (JDOQLQuery)query);
732         }
733         else if (language.equals("javax.jdo.query.TJDOSQL"))
734         {
735             if (query == null || !(query instanceof String JavaDoc))
736                 throw new JDOUserException("Invalid query argument, " + query + ", should be a String containing a SQL SELECT statement, optionally using embedded macros");
737
738             q = new TJDOSQLQuery(pm, this, (String JavaDoc)query);
739         }
740         else
741             throw new JDOUserException("Unknown query language: " + language);
742
743         return q;
744     }
745
746
747     /**
748      * Returns a new, unique ID for an object of the given class.
749      *
750      * @param c The class of the object.
751      *
752      * @return A new object ID.
753      */

754
755     public Object JavaDoc newObjectID(Class JavaDoc c)
756     {
757         ClassMetaData cmd = ClassMetaData.forClass(c);
758
759         if (cmd == null)
760             throw new ClassNotPersistenceCapableException(c);
761
762         if (cmd.requiresExtent())
763             return getTable(c).newOID();
764         else
765             return new SCOID(c);
766     }
767
768
769     /**
770      * Returns the next OID high-order value for IDs of the given class.
771      *
772      * @param classID The class ID number of the class.
773      *
774      * @return The next high-order OID value.
775      *
776      * @exception JDODataStoreException
777      * If an error occurs in accessing or updating the schema table.
778      */

779
780     int getNextOIDHiValue(final int classID)
781     {
782         final int[] nextHiValue = new int[] { -1 };
783         MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_SERIALIZABLE)
784         {
785             public String JavaDoc toString()
786             {
787                 return "Obtain next ID value for class ID " + classID;
788             }
789
790             protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
791             {
792                 nextHiValue[0] = schemaTable.getNextOIDHiValue(classID, conn);
793             }
794         };
795
796         mtx.execute();
797
798         return nextHiValue[0];
799     }
800
801
802     /**
803      * Inserts a persistent object into the database.
804      *
805      * @param sm The state manager of the object to be inserted.
806      */

807
808     public void insert(StateManager sm)
809     {
810         getClassBaseTable(sm.getObject().getClass()).insert(sm);
811     }
812
813
814     /**
815      * Confirms that a persistent object exists in the database.
816      *
817      * @param sm The state manager of the object to be fetched.
818      */

819
820     public void lookup(StateManager sm)
821     {
822         getClassBaseTable(sm.getObject().getClass()).lookup(sm);
823     }
824
825
826     /**
827      * Fetches a persistent object from the database.
828      *
829      * @param sm The state manager of the object to be fetched.
830      * @param fieldNumbers The numbers of the fields to be fetched.
831      */

832
833     public void fetch(StateManager sm, int fieldNumbers[])
834     {
835         getClassBaseTable(sm.getObject().getClass()).fetch(sm, fieldNumbers);
836     }
837
838
839     /**
840      * Updates a persistent object in the database.
841      *
842      * @param sm The state manager of the object to be updated.
843      * @param fieldNumbers The numbers of the fields to be updated.
844      */

845
846     public void update(StateManager sm, int fieldNumbers[])
847     {
848         getClassBaseTable(sm.getObject().getClass()).update(sm, fieldNumbers);
849     }
850
851
852     /**
853      * Deletes a persistent object from the database.
854      *
855      * @param sm The state manager of the object to be deleted.
856      */

857
858     public void delete(StateManager sm)
859     {
860         getClassBaseTable(sm.getObject().getClass()).delete(sm);
861     }
862
863
864     /**
865      * Returns a request object that will insert a row in the given table.
866      * The store manager will cache the request object for re-use by subsequent
867      * requests to the same table.
868      *
869      * @param cbt The table into which to insert.
870      *
871      * @return An insertion request object.
872      */

873
874     InsertRequest getInsertRequest(ClassBaseTable cbt)
875     {
876         RequestIdentifier reqID = new RequestIdentifier(cbt, null, RequestIdentifier.Type.INSERT);
877         InsertRequest req;
878
879         req = (InsertRequest)requestsByID.get(reqID);
880
881         if (req == null)
882         {
883             req = new InsertRequest(cbt);
884             requestsByID.put(reqID, req);
885         }
886
887         return req;
888     }
889
890
891     /**
892      * Returns a request object that will lookup a row in the given table.
893      * The store manager will cache the request object for re-use by subsequent
894      * requests to the same table.
895      *
896      * @param cbt The table in which to lookup.
897      *
898      * @return A lookup request object.
899      */

900
901     LookupRequest getLookupRequest(ClassBaseTable cbt)
902     {
903         RequestIdentifier reqID = new RequestIdentifier(cbt, null, RequestIdentifier.Type.LOOKUP);
904         LookupRequest req;
905
906         req = (LookupRequest)requestsByID.get(reqID);
907
908         if (req == null)
909         {
910             req = new LookupRequest(cbt);
911             requestsByID.put(reqID, req);
912         }
913
914         return req;
915     }
916
917
918     /**
919      * Returns a request object that will fetch a row from the given table.
920      * The store manager will cache the request object for re-use by subsequent
921      * requests to the same table.
922      *
923      * @param cbt The table from which to fetch.
924      * @param fieldNumbers The field numbers corresponding to the columns to be
925      * fetched. Field numbers whose columns exist in
926      * supertables will be ignored.
927      *
928      * @return A fetch request object.
929      */

930
931     FetchRequest getFetchRequest(ClassBaseTable cbt, int[] fieldNumbers)
932     {
933         RequestIdentifier reqID = new RequestIdentifier(cbt, fieldNumbers, RequestIdentifier.Type.FETCH);
934         FetchRequest req;
935
936         req = (FetchRequest)requestsByID.get(reqID);
937
938         if (req == null)
939         {
940             req = new FetchRequest(cbt, fieldNumbers);
941             requestsByID.put(reqID, req);
942         }
943
944         return req;
945     }
946
947
948     /**
949      * Returns a request object that will update a row in the given table.
950      * The store manager will cache the request object for re-use by subsequent
951      * requests to the same table.
952      *
953      * @param cbt The table in which to update.
954      * @param fieldNumbers The field numbers corresponding to the columns to be
955      * updated. Field numbers whose columns exist in
956      * supertables will be ignored.
957      *
958      * @return An update request object.
959      */

960
961     UpdateRequest getUpdateRequest(ClassBaseTable cbt, int[] fieldNumbers)
962     {
963         RequestIdentifier reqID = new RequestIdentifier(cbt, fieldNumbers, RequestIdentifier.Type.UPDATE);
964         UpdateRequest req;
965
966         req = (UpdateRequest)requestsByID.get(reqID);
967
968         if (req == null)
969         {
970             req = new UpdateRequest(cbt, fieldNumbers);
971             requestsByID.put(reqID, req);
972         }
973
974         return req;
975     }
976
977
978     /**
979      * Returns a request object that will delete a row from the given table.
980      * The store manager will cache the request object for re-use by subsequent
981      * requests to the same table.
982      *
983      * @param cbt The table from which to delete.
984      *
985      * @return A deletion request object.
986      */

987
988     DeleteRequest getDeleteRequest(ClassBaseTable cbt)
989     {
990         RequestIdentifier reqID = new RequestIdentifier(cbt, null, RequestIdentifier.Type.DELETE);
991         DeleteRequest req;
992
993         req = (DeleteRequest)requestsByID.get(reqID);
994
995         if (req == null)
996         {
997             req = new DeleteRequest(cbt);
998             requestsByID.put(reqID, req);
999         }
1000
1001        return req;
1002    }
1003
1004
1005    /**
1006     * Returns the type of a database table.
1007     *
1008     * @param tableName The name of the table (or view).
1009     * @param conn A JDBC connection to the database.
1010     *
1011     * @return one of the TABLE_TYPE_* values from {@link Table}.
1012     *
1013     * @see Table
1014     */

1015
1016    public int getTableType(SQLIdentifier tableName, Connection JavaDoc conn) throws SQLException JavaDoc
1017    {
1018        String JavaDoc tableType = null;
1019        String JavaDoc tableNameSQL = tableName.getSQLIdentifier();
1020        DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1021
1022        ResultSet JavaDoc rs = dmd.getTables(null, schemaName, tableNameSQL, null);
1023
1024        try
1025        {
1026            while (rs.next())
1027            {
1028                if (tableNameSQL.equalsIgnoreCase(rs.getString(3)))
1029                {
1030                    tableType = rs.getString(4).toUpperCase();
1031                    break;
1032                }
1033            }
1034        }
1035        finally
1036        {
1037            rs.close();
1038        }
1039
1040        if (tableType == null)
1041            return Table.TABLE_TYPE_MISSING;
1042        else if (tableType.equals("TABLE"))
1043            return Table.TABLE_TYPE_BASE_TABLE;
1044        else if (tableType.equals("VIEW"))
1045            return Table.TABLE_TYPE_VIEW;
1046        else
1047            return Table.TABLE_TYPE_UNKNOWN;
1048    }
1049
1050
1051    /**
1052     * Returns the column info for a database table. This should be used
1053     * instead of making direct calls to DatabaseMetaData.getColumns().
1054     *
1055     * <p>Where possible, this method loads and caches column info for more than
1056     * just the table being requested, improving performance by reducing the
1057     * overall number of calls made to DatabaseMetaData.getColumns() (each of
1058     * which usually results in one or more database queries).
1059     *
1060     * @param tableName The name of the table (or view).
1061     * @param conn A JDBC connection to the database.
1062     *
1063     * @return A list of ColumnInfo objects describing the columns of the
1064     * table. The list is in the same order as was supplied by
1065     * getColumns(). If no column info is found for the given table,
1066     * an empty list is returned.
1067     *
1068     * @see ColumnInfo
1069     */

1070
1071    List JavaDoc getColumnInfo(SQLIdentifier tableName, Connection JavaDoc conn) throws SQLException JavaDoc
1072    {
1073        List JavaDoc cols = null;
1074
1075        if (schemaTable == null)
1076        {
1077            /*
1078             * There's no SchemaTable yet. We can't yet employ the smart
1079             * caching stuff below, so we just make a direct JDBC metadata
1080             * query to get the info for this one table. This should only be
1081             * the case when we're validating the SchemaTable itself.
1082             */

1083            cols = new ArrayList JavaDoc();
1084            DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1085            ResultSet JavaDoc rs = dmd.getColumns(null, schemaName, tableName.getSQLIdentifier(), null);
1086
1087            try
1088            {
1089                while (rs.next())
1090                    cols.add(dba.newColumnInfo(rs));
1091            }
1092            finally
1093            {
1094                rs.close();
1095            }
1096        }
1097        else
1098        {
1099            synchronized (this)
1100            {
1101                long now = System.currentTimeMillis();
1102
1103                /*
1104                 * If we have cached column info that hasn't expired yet, see
1105                 * if it contains info for the given table.
1106                 */

1107                if (now >= columnInfoReadTimestamp && now < columnInfoReadTimestamp + COLUMN_INFO_EXPIRATION_MS)
1108                    cols = (List JavaDoc)columnInfoByTableName.get(tableName);
1109
1110                /*
1111                 * If we have no info for that table, or stale info overall,
1112                 * refresh the column info cache.
1113                 */

1114                if (cols == null)
1115                {
1116                    /*
1117                     * Determine the set of known JDO tables according to what's
1118                     * recorded in the schema table.
1119                     */

1120                    HashSet JavaDoc knownTableNames = new HashSet JavaDoc();
1121                    Iterator JavaDoc i = schemaTable.getAllTableMetadata(false, conn).iterator();
1122
1123                    while (i.hasNext())
1124                        knownTableNames.add(((TableMetadata)i.next()).tableName);
1125
1126                    /*
1127                     * Query for column info on all tables in the schema,
1128                     * discarding info for any tables that aren't JDO tables,
1129                     * or that are JDO tables but are already validated.
1130                     */

1131                    HashMap JavaDoc cim = new HashMap JavaDoc();
1132                    DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1133                    ResultSet JavaDoc rs = dmd.getColumns(null, schemaName, null, null);
1134
1135                    try
1136                    {
1137                        while (rs.next())
1138                        {
1139                            SQLIdentifier tblName = new SQLIdentifier(dba, rs.getString(3));
1140
1141                            if (knownTableNames.contains(tblName))
1142                            {
1143                                Table tbl = (Table)tablesByName.get(tblName);
1144
1145                                if (tbl == null || !tbl.isValidated())
1146                                {
1147                                    List JavaDoc l = (List JavaDoc)cim.get(tblName);
1148
1149                                    if (l == null)
1150                                    {
1151                                        l = new ArrayList JavaDoc();
1152                                        cim.put(tblName, l);
1153                                    }
1154
1155                                    l.add(dba.newColumnInfo(rs));
1156                                }
1157                            }
1158                        }
1159                    }
1160                    finally
1161                    {
1162                        rs.close();
1163                    }
1164
1165                    if (LOG.isDebugEnabled())
1166                        LOG.debug("Column info loaded for " + schemaName + ", " + cim.size() + " tables, time = " + (System.currentTimeMillis() - now) + " ms");
1167
1168                    /* Replace the old cache (if any) with the new one. */
1169                    columnInfoByTableName = cim;
1170                    columnInfoReadTimestamp = now;
1171
1172                    /*
1173                     * Finally, lookup info for the desired table in the new
1174                     * cache.
1175                     */

1176                    cols = (List JavaDoc)columnInfoByTableName.get(tableName);
1177
1178                    if (cols == null)
1179                    {
1180                        cols = Collections.EMPTY_LIST;
1181                        LOG.warn("No column info found for " + tableName);
1182                    }
1183                }
1184            }
1185        }
1186
1187        return cols;
1188    }
1189
1190
1191    /**
1192     * Returns the foreign key info for a database table.
1193     * This should be used instead of making direct calls to
1194     * DatabaseMetaData.getImportedKeys() or DatabaseMetaData.getExportedKeys().
1195     *
1196     * @param tableName The name of the table (or view).
1197     * @param conn A JDBC connection to the database.
1198     *
1199     * @return
1200     * A list of ForeignKeyInfo objects describing the columns of the
1201     * table's foreign keys.
1202     * The list is in the same order as was supplied by get??portedKeys().
1203     * If no column info is found for the given table, an empty list is
1204     * returned.
1205     *
1206     * @see ForeignKeyInfo
1207     */

1208
1209    List JavaDoc getForeignKeyInfo(SQLIdentifier tableName, Connection JavaDoc conn) throws SQLException JavaDoc
1210    {
1211        List JavaDoc fkCols = new ArrayList JavaDoc();
1212        DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1213        ResultSet JavaDoc rs = dmd.getImportedKeys(null, schemaName, tableName.getSQLIdentifier());
1214
1215        try
1216        {
1217            while (rs.next())
1218            {
1219                ForeignKeyInfo fki = dba.newForeignKeyInfo(rs);
1220
1221                /*
1222                 * The contains() test is necessary only because some drivers
1223                 * have been known to be so confused (PostgreSQL, I'm looking at
1224                 * you) as to return duplicate rows from getImportedKeys().
1225                 */

1226                if (!fkCols.contains(fki))
1227                    fkCols.add(fki);
1228            }
1229        }
1230        finally
1231        {
1232            rs.close();
1233        }
1234
1235        return fkCols;
1236    }
1237
1238
1239    /**
1240     * Tests if a database table exists.
1241     *
1242     * @param tableName The name of the table (or view).
1243     * @param conn A JDBC connection to the database.
1244     *
1245     * @return <tt>true</tt> if the table exists in the database,
1246     * <tt>false</tt> otherwise.
1247     */

1248
1249    public boolean tableExists(SQLIdentifier tableName, Connection JavaDoc conn) throws SQLException JavaDoc
1250    {
1251        return getTableType(tableName, conn) != Table.TABLE_TYPE_MISSING;
1252    }
1253
1254
1255    /**
1256     * Resolves an identifier macro. The public fields <var>clazz</var>,
1257     * <var>fieldName</var>, and <var>subfieldName</var> of the given macro are
1258     * taken as inputs, and the public <var>value</var> field is set to the SQL
1259     * identifier of the corresponding database table or column.
1260     *
1261     * @param im The macro to resolve.
1262     */

1263
1264    public void resolveIdentifierMacro(MacroString.IdentifierMacro im)
1265    {
1266        ClassTable ct = getTable(im.clazz);
1267
1268        if (im.fieldName == null)
1269        {
1270            im.value = ct.getName().toString();
1271            return;
1272        }
1273
1274        ColumnMapping cm;
1275
1276        if (im.fieldName.equals("this"))
1277        {
1278            if (!(ct instanceof ClassBaseTable))
1279                throw new JDOUserException("Table for class " + im.clazz.getName() + " has no ID column");
1280
1281            if (im.subfieldName != null)
1282                throw new JDOUserException("Field " + im.clazz.getName() + ".this has no table of its own in which to look for " + im.subfieldName);
1283
1284            cm = ((ClassBaseTable)ct).getIDMapping();
1285        }
1286        else
1287        {
1288            ClassMetaData cmd = ClassMetaData.forClass(im.clazz);
1289            FieldMetaData fmd = cmd.getFieldRelative(cmd.getRelativeFieldNumber(im.fieldName));
1290
1291            if (im.subfieldName == null)
1292            {
1293                Mapping m = ct.getFieldMapping(im.fieldName);
1294
1295                if (m instanceof ColumnMapping)
1296                    cm = (ColumnMapping)m;
1297                else
1298                {
1299                    JDOTable t = getTable(fmd);
1300
1301                    if (t == null)
1302                        throw new JDOUserException("Invalid pseudo-field name " + im.subfieldName + " in macro " + im + ", has no backing table or column");
1303
1304                    im.value = t.getName().toString();
1305                    return;
1306                }
1307            }
1308            else
1309            {
1310                JDOTable t = getTable(fmd);
1311
1312                if (t instanceof SetTable)
1313                {
1314                    SetTable st = (SetTable)t;
1315
1316                    if (im.subfieldName.equals("owner"))
1317                        cm = st.getOwnerMapping();
1318                    else if (im.subfieldName.equals("element"))
1319                        cm = st.getElementMapping();
1320                    else
1321                        throw new JDOUserException("Invalid pseudo-field name " + im.subfieldName + " in macro " + im + ", must be \"owner\" or \"element\"");
1322                }
1323                else if (t instanceof MapTable)
1324                {
1325                    MapTable mt = (MapTable)t;
1326
1327                    if (im.subfieldName.equals("owner"))
1328                        cm = mt.getOwnerMapping();
1329                    else if (im.subfieldName.equals("key"))
1330                        cm = mt.getKeyMapping();
1331                    else if (im.subfieldName.equals("value"))
1332                        cm = mt.getValueMapping();
1333                    else
1334                        throw new JDOUserException("Invalid pseudo-field name " + im.subfieldName + " in macro " + im + ", must be \"owner\", \"key\", or \"value\"");
1335                }
1336                else
1337                    throw new JDOUserException("Field " + im.clazz.getName() + '.' + im.fieldName + " has no table of its own in which to look for " + im.subfieldName);
1338            }
1339        }
1340
1341        im.value = cm.getColumn().getName().toString();
1342    }
1343
1344
1345    /*----------------------------- Inner classes ----------------------------*/
1346
1347
1348    /**
1349     * An abstract base class for StoreManager transactions that perform some
1350     * management function on the database.
1351     * <p>
1352     * Management transactions may be retried in the face of SQL exceptions to
1353     * work around failures caused by transient conditions, such as DB deadlocks.
1354     */

1355
1356    private abstract class MgmtTransaction
1357    {
1358        public static final String JavaDoc MAX_RETRIES_PROPERTY = "com.triactive.jdo.store.maxRetries";
1359
1360        protected final int isolationLevel;
1361        protected final int maxRetries;
1362
1363
1364        /**
1365         * Constructs a new management transaction having the given isolation
1366         * level.
1367         *
1368         * @param isolationLevel
1369         * One of the isolation level constants from java.sql.Connection.
1370         */

1371
1372        public MgmtTransaction(int isolationLevel)
1373        {
1374            this.isolationLevel = isolationLevel;
1375
1376            String JavaDoc s = System.getProperty(MAX_RETRIES_PROPERTY, "3");
1377            int mr;
1378
1379            try
1380            {
1381                mr = Integer.parseInt(s);
1382            }
1383            catch (NumberFormatException JavaDoc e)
1384            {
1385                LOG.warn("Failed parsing " + MAX_RETRIES_PROPERTY + " property, value was " + s);
1386                mr = 3;
1387            }
1388
1389            maxRetries = mr;
1390        }
1391
1392
1393        /**
1394         * Returns a description of the management transaction.
1395         * Subclasses should override this method so that transaction failures
1396         * are given an appropriate exception message.
1397         *
1398         * @return A description of the management transaction.
1399         */

1400
1401        public abstract String JavaDoc toString();
1402
1403
1404        /**
1405         * Implements the body of the transaction.
1406         *
1407         * @param conn
1408         * A connection to the database. If the selected isolation level
1409         * is Connection.TRANSACTION_NONE the connection has auto-commit
1410         * set to true, otherwise auto-commit is false.
1411         *
1412         * @exception SQLException
1413         * If the transaction fails due to a database error that should
1414         * allow the entire transaction to be retried.
1415         */

1416
1417        protected abstract void execute(Connection JavaDoc conn) throws SQLException JavaDoc;
1418
1419
1420        /**
1421         * Executes the transaction.
1422         * <p>
1423         * A database connection is acquired and the {@link #execute(Connection)}
1424         * method is invoked.
1425         * If the selected isolation level is not Connection.TRANSACTION_NONE,
1426         * then commit() or rollback() is called on the connection according to
1427         * whether the invocation succeeded or not.
1428         * If the invocation failed the sequence is repeated, up to a maximum of
1429         * <var>maxRetries</var> times, configurable by the system property
1430         * com.triactive.jdo.store.maxRetries.
1431         *
1432         * @exception JDODataStoreException
1433         * If a SQL exception occurred even after <var>maxRetries</var>
1434         * attempts.
1435         */

1436
1437        public final void execute()
1438        {
1439            int attempts = 0;
1440
1441            for (;;)
1442            {
1443                try
1444                {
1445                    Connection JavaDoc conn = dba.getConnection(ds, userName, password, isolationLevel);
1446
1447                    try
1448                    {
1449                        boolean succeeded = false;
1450
1451                        try
1452                        {
1453                            execute(conn);
1454                            succeeded = true;
1455                        }
1456                        finally
1457                        {
1458                            if (isolationLevel != Connection.TRANSACTION_NONE)
1459                            {
1460                                if (succeeded)
1461                                    conn.commit();
1462                                else
1463                                    conn.rollback();
1464                            }
1465                        }
1466                    }
1467                    finally
1468                    {
1469                        dba.closeConnection(conn);
1470                    }
1471
1472                    break;
1473                }
1474                catch (SQLException JavaDoc e)
1475                {
1476                    SQLState state = dba.getSQLState(e);
1477
1478                    if ((state != null && !state.isWorthRetrying()) || ++attempts >= maxRetries)
1479                        throw new JDODataStoreException("SQL exception: " + this, e);
1480                }
1481            }
1482        }
1483    }
1484
1485
1486    /**
1487     * A management transaction that adds a set of classes to the StoreManager,
1488     * making them usable for persistence.
1489     * <p>
1490     * This class embodies the work necessary to activate a persistent class and
1491     * ready it for storage management.
1492     * It is the primary mutator of a StoreManager.
1493     * <p>
1494     * Adding classes is an involved process that includes the creation and/or
1495     * validation in the database of tables, views, and table constraints, and
1496     * their corresponding Java objects maintained by the StoreManager.
1497     * Since it's a management transaction, the entire process is subject to
1498     * retry on SQL exceptions.
1499     * It is responsible for ensuring that the procedure either adds <i>all</i>
1500     * of the requested classes successfully, or adds none of them and preserves
1501     * the previous state of the StoreManager exactly as it was.
1502     */

1503
1504    private class ClassAdder extends MgmtTransaction
1505    {
1506        private final Class JavaDoc[] classes;
1507        private Connection JavaDoc schemaConnection = null;
1508
1509
1510        /**
1511         * Constructs a new class adder transaction that will add the given
1512         * classes to the StoreManager.
1513         *
1514         * @param classes The class(es) to be added.
1515         */

1516
1517        public ClassAdder(Class JavaDoc[] classes)
1518        {
1519            super(Connection.TRANSACTION_SERIALIZABLE);
1520
1521            this.classes = classes;
1522        }
1523
1524
1525        public String JavaDoc toString()
1526        {
1527            return "Add classes to schema " + schemaName;
1528        }
1529
1530
1531        protected void execute(Connection JavaDoc conn) throws SQLException JavaDoc
1532        {
1533            synchronized (StoreManager.this)
1534            {
1535                classAdder = this;
1536                schemaConnection = conn;
1537
1538                try
1539                {
1540                    try
1541                    {
1542                        addClassTablesAndValidate(classes);
1543                    }
1544                    catch (NestedSQLException e)
1545                    {
1546                        throw e.getSQLException();
1547                    }
1548                }
1549                finally
1550                {
1551                    schemaConnection = null;
1552                    classAdder = null;
1553                }
1554            }
1555        }
1556
1557
1558        /**
1559         * Called by StoreManager.addClasses() when it has been recursively
1560         * re-entered.
1561         * This just adds table objects for the requested classes and returns.
1562         *
1563         * @param classes The class(es) to be added.
1564         *
1565         * @exception NestedSQLException
1566         * If a SQL exception occurs it is wrapped within one of these.
1567         * The assumption is that the top-level ClassAdder.execute() will
1568         * pick it out and rethrow it as a SQLException so that the entire
1569         * ClassAdder transaction can be retried, if appropriate.
1570         */

1571
1572        public void addClasses(Class JavaDoc[] classes)
1573        {
1574            if (schemaConnection == null)
1575                throw new IllegalStateException JavaDoc("Add classes transaction is not active");
1576
1577            try
1578            {
1579                addClassTables(classes);
1580            }
1581            catch (SQLException JavaDoc e)
1582            {
1583                throw new NestedSQLException(e);
1584            }
1585        }
1586
1587
1588        /**
1589         * Adds a new table object (ie ClassBaseTable or ClassView) for every class
1590         * in the given list that 1) requires an extent and 2) does not yet have an
1591         * extent (ie table) initialized in the store manager.
1592         *
1593         * <p>This doesn't initialize or validate the tables, it just adds the table
1594         * objects to the StoreManager's internal data structures.
1595         *
1596         * @param classes The class(es) whose tables are to be added.
1597         */

1598
1599        private void addClassTables(Class JavaDoc[] classes) throws SQLException JavaDoc
1600        {
1601            Iterator JavaDoc i = getReferencedClasses(classes).iterator();
1602
1603            while (i.hasNext())
1604            {
1605                ClassMetaData cmd = (ClassMetaData)i.next();
1606
1607                if (getTable(cmd) == null && cmd.requiresExtent())
1608                {
1609                    TableMetadata tmd = schemaTable.getTableMetadata(cmd, schemaConnection);
1610                    ClassTable t;
1611
1612                    if (cmd.getViewDefinition(dba.getVendorID()) != null)
1613                        t = new ClassView(tmd, cmd, StoreManager.this);
1614                    else
1615                        t = new ClassBaseTable(tmd, cmd, StoreManager.this);
1616
1617                    addTable(t);
1618                }
1619            }
1620
1621        }
1622
1623
1624        /**
1625         * Returns a List of {@link ClassMetaData} objects representing the set of
1626         * classes consisting of the requested classes plus any and all other
1627         * classes they may reference, directly or indirectly.
1628         * The returned list is ordered by dependency.
1629         *
1630         * @param classes An array of persistence-capable classes.
1631         *
1632         * @return A List of <tt>ClassMetaData</tt> objects.
1633         *
1634         * @exception ClassNotPersistenceCapableException
1635         * If any of the given classes is not persistence-capable.
1636         */

1637
1638        private List JavaDoc getReferencedClasses(Class JavaDoc[] classes)
1639        {
1640            List JavaDoc cmds = new ArrayList JavaDoc();
1641
1642            for (int i = 0; i < classes.length; ++i)
1643            {
1644                ClassMetaData cmd = ClassMetaData.forClass(classes[i]);
1645
1646                if (cmd == null)
1647                    throw new ClassNotPersistenceCapableException(classes[i]);
1648
1649                cmds.addAll(cmd.getReferencedClasses(dba.getVendorID()));
1650            }
1651
1652            return cmds;
1653        }
1654
1655
1656        /**
1657         * Adds a new table object to the StoreManager's internal data structures.
1658         *
1659         * @param t The table to be added.
1660         */

1661
1662        private void addTable(JDOTable t)
1663        {
1664            allTables.add(t);
1665
1666            int tableID = t.getTableID();
1667
1668            while (tablesByTableID.size() <= tableID)
1669                tablesByTableID.add(null);
1670
1671            tablesByTableID.set(tableID, t);
1672            tablesByName.put(t.getName(), t);
1673            tablesByJavaID.put(t.getJavaName(), t);
1674        }
1675
1676
1677        /**
1678         * Adds a new table object (ie ClassBaseTable or ClassView) for every class
1679         * in the given list that 1) requires an extent and 2) does not yet have an
1680         * extent (ie table) initialized in the store manager.
1681         *
1682         * <p>After all of the table objects, including any other tables they might
1683         * reference, have been added, each table is initialized and validated in
1684         * the database.
1685         *
1686         * <p>If any error occurs along the way, any table(s) that were created are
1687         * dropped and the state of the StoreManager is rolled back to the point at
1688         * which this method was called.
1689         *
1690         * @param classes The class(es) whose tables are to be added.
1691         */

1692
1693        private void addClassTablesAndValidate(Class JavaDoc[] classes) throws SQLException JavaDoc
1694        {
1695            ArrayList JavaDoc allTablesPrev = (ArrayList JavaDoc)allTables.clone();
1696            ArrayList JavaDoc tablesByTableIDPrev = (ArrayList JavaDoc)tablesByTableID.clone();
1697            HashMap JavaDoc tablesByNamePrev = (HashMap JavaDoc)tablesByName.clone();
1698            HashMap JavaDoc tablesByJavaIDPrev = (HashMap JavaDoc)tablesByJavaID.clone();
1699
1700            ArrayList JavaDoc baseTablesCreated = new ArrayList JavaDoc();
1701            ArrayList JavaDoc baseTableConstraintsCreated = new ArrayList JavaDoc();
1702            ArrayList JavaDoc viewsCreated = new ArrayList JavaDoc();
1703            boolean completed = false;
1704
1705            try
1706            {
1707                /* Add ClassTable's for the requested classes. */
1708                addClassTables(classes);
1709
1710                /*
1711                 * Repeatedly loop over the set of all table objects, initializing
1712                 * any that need initialization. Each time a table object is
1713                 * initialized, it may cause other associated table objects to be
1714                 * added (via callbacks to addClasses()), so the loop must be
1715                 * repeated until no more tables exist that need initialization.
1716                 */

1717                ArrayList JavaDoc newBaseTables = new ArrayList JavaDoc();
1718                ArrayList JavaDoc newViews = new ArrayList JavaDoc();
1719                boolean someNeededInitialization;
1720
1721                do
1722                {
1723                    someNeededInitialization = false;
1724
1725                    Iterator JavaDoc i = ((ArrayList JavaDoc)allTables.clone()).iterator();
1726
1727                    while (i.hasNext())
1728                    {
1729                        JDOTable t = (JDOTable)i.next();
1730
1731                        if (!t.isInitialized())
1732                        {
1733                            t.initialize();
1734
1735                            if (t instanceof View)
1736                                newViews.add(t);
1737                            else
1738                                newBaseTables.add(t);
1739
1740                            someNeededInitialization = true;
1741                        }
1742                    }
1743                } while (someNeededInitialization);
1744
1745                /*
1746                 * For each new BaseTable object, validate it against the actual
1747                 * table in the database. If the table doesn't exist it is created
1748                 * (subject to auto-create mode). Views are done later.
1749                 */

1750                Iterator JavaDoc i = newBaseTables.iterator();
1751
1752                while (i.hasNext())
1753                {
1754                    BaseTable t = (BaseTable)i.next();
1755
1756                    if (t.validate(tableValidationFlags, schemaConnection))
1757                        baseTablesCreated.add(t);
1758
1759                    /* Discard any cached column info used to validate the table. */
1760                    columnInfoByTableName.remove(t.getName());
1761                }
1762
1763                /*
1764                 * Iterate over the newly added table objects again, this time
1765                 * validating their required constraints against the actual
1766                 * constraints existing in the database. If the constraints don't
1767                 * exist, they are created (subject to auto-create mode).
1768                 *
1769                 * Constraint processing is done as a separate step from table
1770                 * validation because you can't create constraints that reference
1771                 * other tables until those tables exist.
1772                 */

1773                i = newBaseTables.iterator();
1774
1775                while (i.hasNext())
1776                {
1777                    BaseTable t = (BaseTable)i.next();
1778
1779                    if (t.validateConstraints(constraintValidationFlags, schemaConnection))
1780                        baseTableConstraintsCreated.add(t);
1781                }
1782
1783                /*
1784                 * For each new View object, validate it against the actual view in
1785                 * the database. If the view doesn't exist it is created (subject
1786                 * to auto-create mode).
1787                 */

1788                i = newViews.iterator();
1789
1790                while (i.hasNext())
1791                {
1792                    View v = (View)i.next();
1793
1794                    if (v.validate(tableValidationFlags, schemaConnection))
1795                        viewsCreated.add(v);
1796
1797                    /* Discard any cached column info used to validate the view. */
1798                    columnInfoByTableName.remove(v.getName());
1799                }
1800
1801                completed = true;
1802            }
1803            finally
1804            {
1805                /*
1806                 * If something went wrong, roll things back to the way they were
1807                 * before we started. This may not restore the database 100% of the
1808                 * time (if DDL statements are not transactional) but it will always
1809                 * put the StoreManager's internal structures back the way they
1810                 * were.
1811                 */

1812                if (!completed)
1813                {
1814                    allTables = allTablesPrev;
1815                    tablesByTableID = tablesByTableIDPrev;
1816                    tablesByName = tablesByNamePrev;
1817                    tablesByJavaID = tablesByJavaIDPrev;
1818
1819                    /*
1820                     * Tables, table constraints, and views get removed in the
1821                     * reverse order from which they were created.
1822                     */

1823                    try
1824                    {
1825                        ListIterator JavaDoc li = viewsCreated.listIterator(viewsCreated.size());
1826
1827                        while (li.hasPrevious())
1828                            ((View)li.previous()).drop(schemaConnection);
1829
1830                        li = baseTableConstraintsCreated.listIterator(baseTableConstraintsCreated.size());
1831
1832                        while (li.hasPrevious())
1833                            ((BaseTable)li.previous()).dropConstraints(schemaConnection);
1834
1835                        li = baseTablesCreated.listIterator(baseTablesCreated.size());
1836
1837                        while (li.hasPrevious())
1838                            ((BaseTable)li.previous()).drop(schemaConnection);
1839                    }
1840                    catch (Exception JavaDoc e)
1841                    {
1842                        LOG.warn("An error occurred while auto-creating schema elements. The following exception occurred while attempting to rollback the partially-completed schema changes: " + e.toString());
1843                    }
1844                }
1845            }
1846        }
1847
1848
1849        /**
1850         * Called by Mapping objects in the midst of StoreManager.addClasses()
1851         * to request the creation of a set table.
1852         *
1853         * @param cbt The base table for the class containing the set.
1854         * @param fmd The field metadata describing the set field.
1855         *
1856         * @exception NestedSQLException
1857         * If a SQL exception occurs it is wrapped within one of these.
1858         * The assumption is that the top-level ClassAdder.execute() will
1859         * pick it out and rethrow it as a SQLException so that the entire
1860         * ClassAdder transaction can be retried, if appropriate.
1861         */

1862
1863        public SetTable newSetTable(ClassBaseTable cbt, FieldMetaData fmd)
1864        {
1865            TableMetadata tmd;
1866
1867            try
1868            {
1869                tmd = schemaTable.getTableMetadata(fmd, schemaConnection);
1870            }
1871            catch (SQLException JavaDoc e)
1872            {
1873                throw new NestedSQLException(e);
1874            }
1875
1876            SetTable st = new SetTable(tmd, fmd, StoreManager.this);
1877
1878            addTable(st);
1879
1880            return st;
1881        }
1882
1883
1884        /**
1885         * Called by Mapping objects in the midst of StoreManager.addClasses()
1886         * to request the creation of a map table.
1887         *
1888         * @param cbt The base table for the class containing the map.
1889         * @param fmd The field metadata describing the map field.
1890         *
1891         * @exception NestedSQLException
1892         * If a SQL exception occurs it is wrapped within one of these.
1893         * The assumption is that the top-level ClassAdder.execute() will
1894         * pick it out and rethrow it as a SQLException so that the entire
1895         * ClassAdder transaction can be retried, if appropriate.
1896         */

1897
1898        public MapTable newMapTable(ClassBaseTable cbt, FieldMetaData fmd)
1899        {
1900            TableMetadata tmd;
1901
1902            try
1903            {
1904                tmd = schemaTable.getTableMetadata(fmd, schemaConnection);
1905            }
1906            catch (SQLException JavaDoc e)
1907            {
1908                throw new NestedSQLException(e);
1909            }
1910
1911            MapTable mt = new MapTable(tmd, fmd, StoreManager.this);
1912
1913            addTable(mt);
1914
1915            return mt;
1916        }
1917    }
1918
1919
1920    /**
1921     * A runtime exception designed to tunnel a SQL exception from a deeply
1922     * nested recursive procedure up to a higher level that can handle it.
1923     */

1924
1925    private static class NestedSQLException extends RuntimeException JavaDoc
1926    {
1927        private final SQLException JavaDoc e;
1928
1929        public NestedSQLException(SQLException JavaDoc e)
1930        {
1931            super("Inner invocation of recursive procedure threw a SQL exception: " + e.getMessage());
1932            this.e = e;
1933        }
1934
1935        public SQLException JavaDoc getSQLException()
1936        {
1937            return e;
1938        }
1939    }
1940}
1941
Popular Tags