Source Code Cross Referenced for StoreManager.java in  » Database-ORM » TJDO » com » triactive » jdo » store » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database ORM » TJDO » com.triactive.jdo.store 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Copyright 2004 (C) TJDO.
0003:         * All rights reserved.
0004:         *
0005:         * This software is distributed under the terms of the TJDO License version 1.0.
0006:         * See the terms of the TJDO License in the documentation provided with this software.
0007:         *
0008:         * $Id: StoreManager.java,v 1.18 2004/01/18 03:01:06 jackknifebarber Exp $
0009:         */
0010:
0011:        package com.triactive.jdo.store;
0012:
0013:        import com.triactive.jdo.ClassNotPersistenceCapableException;
0014:        import com.triactive.jdo.PersistenceManager;
0015:        import com.triactive.jdo.PersistenceManagerFactoryImpl;
0016:        import com.triactive.jdo.SchemaManager;
0017:        import com.triactive.jdo.StateManager;
0018:        import com.triactive.jdo.model.ClassMetaData;
0019:        import com.triactive.jdo.model.FieldMetaData;
0020:        import com.triactive.jdo.model.MetaData;
0021:        import com.triactive.jdo.util.MacroString;
0022:        import com.triactive.jdo.util.SoftValueMap;
0023:        import java.sql.Connection;
0024:        import java.sql.DatabaseMetaData;
0025:        import java.sql.ResultSet;
0026:        import java.sql.SQLException;
0027:        import java.sql.SQLWarning;
0028:        import java.sql.Statement;
0029:        import java.util.ArrayList;
0030:        import java.util.Collections;
0031:        import java.util.HashMap;
0032:        import java.util.HashSet;
0033:        import java.util.Iterator;
0034:        import java.util.List;
0035:        import java.util.ListIterator;
0036:        import java.util.Map;
0037:        import javax.jdo.Extent;
0038:        import javax.jdo.JDODataStoreException;
0039:        import javax.jdo.JDOException;
0040:        import javax.jdo.JDOFatalException;
0041:        import javax.jdo.JDOUserException;
0042:        import javax.sql.DataSource;
0043:        import org.apache.log4j.Category;
0044:
0045:        /**
0046:         * Manages the contents of a data store (aka database schema) on behalf of a
0047:         * particular PersistenceManagerFactory and all its persistent instances.
0048:         * <p>
0049:         * The store manager's responsibilities include:
0050:         * <ul>
0051:         * <li>Creating and/or validating database tables according to the persistent
0052:         * classes being accessed by the application.</li>
0053:         * <li>Serving as the primary intermediary between StateManagers and the
0054:         * database (implements insert(), fetch(), update(), delete()).</li>
0055:         * <li>Managing TJDO's schema table (JDO_TABLE).
0056:         * <li>Serving as the base Extent and Query factory.</li>
0057:         * <li>Providing cached access to JDBC database metadata.</li>
0058:         * <li>Resolving SQL identifier macros to actual SQL identifiers.</li>
0059:         * </ul>
0060:         * <p>
0061:         * A store manager's knowledge of its schema's contents is not necessarily
0062:         * complete.
0063:         * It is aware of only those tables whose classes have somehow been accessed,
0064:         * directly or indirectly, by the application during the life of the store
0065:         * manager object.
0066:         * 
0067:         * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
0068:         * @version $Revision: 1.18 $
0069:         */
0070:
0071:        public class StoreManager implements  SchemaManager {
0072:            private static final Category LOG = Category
0073:                    .getInstance(StoreManager.class);
0074:
0075:            /**
0076:             * The amount of time before we expire any cached column info.  Hardcoded to
0077:             * five minutes.
0078:             */
0079:            private static final int COLUMN_INFO_EXPIRATION_MS = 5 * 60 * 1000;
0080:
0081:            private final DataSource ds;
0082:            private final String userName;
0083:            private final String password;
0084:            private final DatabaseAdapter dba;
0085:            private final int tableValidationFlags;
0086:            private final int constraintValidationFlags;
0087:            private final String schemaName;
0088:
0089:            /*
0090:             * Access to fields below is synchronized on the StoreManager object.
0091:             * Note:  if DB work must also be done, the DB connection must be
0092:             * acquired *before* locking the StoreManager in order to avoid deadlocks.
0093:             */
0094:            private ArrayList allTables = new ArrayList();
0095:            private ArrayList tablesByTableID = new ArrayList();
0096:            private HashMap tablesByName = new HashMap();
0097:            private HashMap tablesByJavaID = new HashMap();
0098:            private SchemaTable schemaTable = null;
0099:            private Map columnInfoByTableName = new HashMap();
0100:            private long columnInfoReadTimestamp = -1;
0101:
0102:            /**
0103:             * The cache of database requests.  Access is synchronized on the map
0104:             * object itself.
0105:             */
0106:            private Map requestsByID = Collections
0107:                    .synchronizedMap(new SoftValueMap());
0108:
0109:            /**
0110:             * The active class adder transaction, if any.
0111:             * Some StoreManager methods are called recursively in the course of adding
0112:             * new classes.
0113:             * This field allows such methods to coordinate with the active ClassAdder
0114:             * transaction.
0115:             * Recursive methods include:
0116:             * <ul>
0117:             * <li>addClasses()</li>
0118:             * <li>newSetTable()</li>
0119:             * <li>newMapTable()</li>
0120:             * </ul>
0121:             * Access is synchronized on the StoreManager itself.
0122:             * Invariant: classAdder == null if StoreManager is unlocked.
0123:             */
0124:            private ClassAdder classAdder = null;
0125:
0126:            /**
0127:             * Constructs a new StoreManager.
0128:             * On successful return the new StoreManager will have successfully
0129:             * connected to the database with the given credentials and determined the
0130:             * schema name, but will not have inspected the schema contents any further.
0131:             * The contents (tables, views, etc.) will be subsequently created and/or
0132:             * validated on-demand as the application accesses persistent classes.
0133:             * <p>
0134:             * To avoid creating unnecessary redundant StoreManagers, new StoreManagers
0135:             * should always be obtained from the {@link StoreManagerFactory}, rather
0136:             * than constructed directly.
0137:             *
0138:             * @param pmf
0139:             *      The corresponding PersistenceManagerFactory.  This factory's
0140:             *      non-transactional data source will be used to get database
0141:             *      connections as needed to perform management functions.
0142:             * @param userName
0143:             *      The database user name.
0144:             * @param password
0145:             *      The database user's password.
0146:             *
0147:             * @exception JDODataStoreException
0148:             *      If the database could not be accessed or the name of the schema
0149:             *      could not be determined.
0150:             *
0151:             * @see StoreManagerFactory
0152:             */
0153:
0154:            StoreManager(PersistenceManagerFactoryImpl pmf, String userName,
0155:                    String password) {
0156:                this .ds = pmf.getNontransactionalDataSource();
0157:                this .userName = userName;
0158:                this .password = password;
0159:
0160:                int validateTables = pmf.getValidateTables() ? Table.VALIDATE
0161:                        : 0;
0162:                int validateConstraints = pmf.getValidateConstraints() ? Table.VALIDATE
0163:                        : 0;
0164:                int autoCreate = pmf.getAutoCreateTables() ? Table.AUTO_CREATE
0165:                        : 0;
0166:
0167:                tableValidationFlags = validateTables | autoCreate;
0168:                constraintValidationFlags = validateConstraints | autoCreate;
0169:
0170:                try {
0171:                    Connection conn;
0172:
0173:                    if (userName == null)
0174:                        conn = ds.getConnection();
0175:                    else
0176:                        conn = ds.getConnection(userName, password);
0177:
0178:                    try {
0179:                        dba = DatabaseAdapter.getInstance(conn);
0180:                        String name;
0181:
0182:                        try {
0183:                            name = dba.getSchemaName(conn);
0184:                        } catch (UnsupportedOperationException e) {
0185:                            /*
0186:                             * The DatabaseAdapter doesn't know how to determine the
0187:                             * current schema name.  As a fallback, create a temporary
0188:                             * probe table which can look itself up in the JDBC metadata
0189:                             * and thereby find out what schema we're in.
0190:                             */
0191:                            ProbeTable pt = new ProbeTable(this );
0192:                            pt.initialize();
0193:                            pt.create(conn);
0194:
0195:                            try {
0196:                                name = pt.findSchemaName(conn);
0197:                            } finally {
0198:                                pt.drop(conn);
0199:                            }
0200:                        }
0201:
0202:                        schemaName = name;
0203:                    } finally {
0204:                        conn.close();
0205:                    }
0206:                } catch (SQLException e) {
0207:                    throw new JDODataStoreException(
0208:                            "Failed initializing database", e);
0209:                }
0210:            }
0211:
0212:            /**
0213:             * Clears all knowledge of tables, cached requests, metadata, etc and resets
0214:             * the store manager to its initial state.
0215:             */
0216:
0217:            public synchronized void reset() {
0218:                allTables.clear();
0219:                tablesByTableID.clear();
0220:                tablesByName.clear();
0221:                tablesByJavaID.clear();
0222:                schemaTable = null;
0223:
0224:                columnInfoByTableName.clear();
0225:                columnInfoReadTimestamp = -1;
0226:
0227:                requestsByID.clear();
0228:            }
0229:
0230:            /**
0231:             * Asserts that the schema has been initialized, meaning the schema table
0232:             * (JDO_TABLE) exists and is valid, and the schemaTable field is non-null.
0233:             * <p>
0234:             * This method must be called with the StoreManager's monitor unlocked,
0235:             * since it may invoke a management transaction.
0236:             */
0237:
0238:            private void checkSchemaInitialized() {
0239:                //assert !Thread.holdsLock(this);   // only possible with 1.4
0240:
0241:                synchronized (this ) {
0242:                    if (schemaTable != null)
0243:                        return;
0244:                }
0245:
0246:                MgmtTransaction mtx = new MgmtTransaction(
0247:                        Connection.TRANSACTION_SERIALIZABLE) {
0248:                    public String toString() {
0249:                        return "Initialize schema table for " + schemaName;
0250:                    }
0251:
0252:                    protected void execute(Connection conn) throws SQLException {
0253:                        initializeSchemaTable(true, conn);
0254:                    }
0255:                };
0256:
0257:                mtx.execute();
0258:            }
0259:
0260:            /**
0261:             * Initializes the schemaTable field.
0262:             *
0263:             * @param validate
0264:             *      <code>true</code> to validate the table even if it doesn't exist.
0265:             *      If auto-create mode is on this will cause it to be created.
0266:             * @param conn
0267:             *      The connection to use.
0268:             *
0269:             * @return
0270:             *      <code>true</code> if the schemaTable field has been set or was
0271:             *      already set,
0272:             *      <code>false</code> if validation is false and the table doesn't
0273:             *      exist.
0274:             */
0275:            private synchronized boolean initializeSchemaTable(
0276:                    boolean validate, Connection conn) throws SQLException {
0277:                if (schemaTable != null)
0278:                    return true;
0279:
0280:                try {
0281:                    SchemaTable st = new SchemaTable(this );
0282:                    st.initialize();
0283:
0284:                    if (validate || st.exists(conn)) {
0285:                        st.validate(tableValidationFlags, conn);
0286:
0287:                        LOG.info("Schema initialized: " + schemaName);
0288:
0289:                        schemaTable = st;
0290:                        return true;
0291:                    } else
0292:                        return false;
0293:                } finally {
0294:                    if (schemaTable == null)
0295:                        reset();
0296:                }
0297:            }
0298:
0299:            /**
0300:             * Returns the database adapter used by this store manager.
0301:             *
0302:             * @return  The database adapter used by this store manager.
0303:             */
0304:
0305:            public DatabaseAdapter getDatabaseAdapter() {
0306:                return dba;
0307:            }
0308:
0309:            public String getSchemaName() {
0310:                return schemaName;
0311:            }
0312:
0313:            /**
0314:             * Logs SQL warnings to the common log.  Should be called after any
0315:             * operation on a JDBC <tt>Statement</tt> or <tt>ResultSet</tt>
0316:             * object as:
0317:             *
0318:             * <blockquote><pre>
0319:             * storeMgr.logSQLWarnings(obj.getWarnings());
0320:             * </pre></blockquote>
0321:             *
0322:             * If any warnings were generated, the entire list of them are logged
0323:             * to <tt>System.err</tt>.
0324:             *
0325:             * @param   warning     the value returned from getWarnings().
0326:             */
0327:
0328:            public void logSQLWarnings(SQLWarning warning) {
0329:                while (warning != null) {
0330:                    LOG.warn("SQL warning: " + warning);
0331:
0332:                    warning = warning.getNextWarning();
0333:                }
0334:            }
0335:
0336:            public void logSQLWarnings(Connection conn) {
0337:                try {
0338:                    logSQLWarnings(conn.getWarnings());
0339:                } catch (SQLException e) {
0340:                    throw dba.newDataStoreException(
0341:                            "Error obtaining warnings from connection " + conn,
0342:                            e);
0343:                }
0344:            }
0345:
0346:            public void logSQLWarnings(Statement stmt) {
0347:                try {
0348:                    logSQLWarnings(stmt.getWarnings());
0349:                } catch (SQLException e) {
0350:                    throw dba.newDataStoreException(
0351:                            "Error obtaining warnings from statement " + stmt,
0352:                            e);
0353:                }
0354:            }
0355:
0356:            public void logSQLWarnings(ResultSet rs) {
0357:                try {
0358:                    logSQLWarnings(rs.getWarnings());
0359:                } catch (SQLException e) {
0360:                    throw dba
0361:                            .newDataStoreException(
0362:                                    "Error obtaining warnings from result set "
0363:                                            + rs, e);
0364:                }
0365:            }
0366:
0367:            /**
0368:             * Adds the given persistence-capable classes to the store manager's set
0369:             * of active classes ready for persistence.
0370:             * The act of adding a class to the store manager causes any necessary
0371:             * database objects (tables, views, constraints, indexes, etc.) to be
0372:             * created.
0373:             * <p>
0374:             * Other StoreManager methods may cause classes to be added as a side
0375:             * effect.
0376:             *
0377:             * @param classes   The class(es) to be added.
0378:             */
0379:
0380:            public void addClasses(Class[] classes) {
0381:                checkSchemaInitialized();
0382:
0383:                synchronized (this ) {
0384:                    if (classAdder != null) {
0385:                        /*
0386:                         * addClasses() has been recursively re-entered:  just add table
0387:                         * objects for the requested classes and return.
0388:                         */
0389:                        classAdder.addClasses(classes);
0390:                        return;
0391:                    }
0392:                }
0393:
0394:                new ClassAdder(classes).execute();
0395:            }
0396:
0397:            /**
0398:             * Called by Mapping objects in the midst of StoreManager.addClasses()
0399:             * to request the creation of a set table.
0400:             *
0401:             * @param cbt   The base table for the class containing the set.
0402:             * @param fmd   The field metadata describing the set field.
0403:             */
0404:
0405:            synchronized SetTable newSetTable(ClassBaseTable cbt,
0406:                    FieldMetaData fmd) {
0407:                if (classAdder == null)
0408:                    throw new IllegalStateException(
0409:                            "SetTables can only be created as a side effect of adding a new class");
0410:
0411:                return classAdder.newSetTable(cbt, fmd);
0412:            }
0413:
0414:            /**
0415:             * Called by Mapping objects in the midst of StoreManager.addClasses()
0416:             * to request the creation of a map table.
0417:             *
0418:             * @param cbt   The base table for the class containing the map.
0419:             * @param fmd   The field metadata describing the map field.
0420:             */
0421:
0422:            synchronized MapTable newMapTable(ClassBaseTable cbt,
0423:                    FieldMetaData fmd) {
0424:                if (classAdder == null)
0425:                    throw new IllegalStateException(
0426:                            "MapTables can only be created as a side effect of adding a new class");
0427:
0428:                return classAdder.newMapTable(cbt, fmd);
0429:            }
0430:
0431:            public void dropTablesFor(final Class[] classes) {
0432:                MgmtTransaction mtx = new MgmtTransaction(
0433:                        Connection.TRANSACTION_READ_COMMITTED) {
0434:                    public String toString() {
0435:                        return "Drop tables for selected classes from schema "
0436:                                + schemaName;
0437:                    }
0438:
0439:                    protected void execute(Connection conn) throws SQLException {
0440:                        synchronized (StoreManager.this ) {
0441:                            try {
0442:                                if (initializeSchemaTable(false, conn))
0443:                                    schemaTable.dropTablesFor(classes, conn);
0444:                            } finally {
0445:                                reset();
0446:                            }
0447:                        }
0448:                    }
0449:                };
0450:
0451:                mtx.execute();
0452:            }
0453:
0454:            public void dropAllTables() {
0455:                MgmtTransaction mtx = new MgmtTransaction(
0456:                        Connection.TRANSACTION_READ_COMMITTED) {
0457:                    public String toString() {
0458:                        return "Drop all tables from schema " + schemaName;
0459:                    }
0460:
0461:                    protected void execute(Connection conn) throws SQLException {
0462:                        synchronized (StoreManager.this ) {
0463:                            try {
0464:                                if (initializeSchemaTable(false, conn)) {
0465:                                    schemaTable.dropAllTables(conn);
0466:                                    schemaTable.drop(conn);
0467:                                }
0468:                            } finally {
0469:                                reset();
0470:                            }
0471:                        }
0472:                    }
0473:                };
0474:
0475:                mtx.execute();
0476:            }
0477:
0478:            /**
0479:             * Returns the JDO table having the given SQL name, if any.
0480:             * Returns <code>null</code> if no such table is (yet) known to the store
0481:             * manager.
0482:             *
0483:             * @param name  The name of the table.
0484:             *
0485:             * @return  The corresponding JDO table, or <code>null</code>.
0486:             */
0487:
0488:            public synchronized JDOTable getTable(SQLIdentifier name) {
0489:                return (JDOTable) tablesByName.get(name);
0490:            }
0491:
0492:            /**
0493:             * Returns the JDO table having the given table ID.
0494:             * Returns <code>null</code> if no such table is (yet) known to the store
0495:             * manager.
0496:             *
0497:             * @param tableID
0498:             *      The table ID of the table to be returned.
0499:             *
0500:             * @return  The corresponding JDO table, or <code>null</code>.
0501:             */
0502:
0503:            public synchronized JDOTable getTable(int tableID) {
0504:                if (tableID < 0 || tableID >= tablesByTableID.size())
0505:                    return null;
0506:                else
0507:                    return (JDOTable) tablesByTableID.get(tableID);
0508:            }
0509:
0510:            /**
0511:             * Returns the JDO table having the given metadata, if any.
0512:             * Returns <code>null</code> if no such table is (yet) known to the store
0513:             * manager.
0514:             *
0515:             * @param md    The metadata for the table.
0516:             *
0517:             * @return  The corresponding JDO table, or <code>null</code>.
0518:             */
0519:
0520:            synchronized JDOTable getTable(MetaData md) {
0521:                return (JDOTable) tablesByJavaID.get(md.getJavaName());
0522:            }
0523:
0524:            /**
0525:             * Returns the primary table serving as backing for the given class.
0526:             * If the class is not yet known to the store manager, {@link #addClasses}
0527:             * is called to add it.
0528:             *
0529:             * @param c     The class whose table is be returned.
0530:             *
0531:             * @return  The corresponding class table.
0532:             *
0533:             * @exception NoExtentException
0534:             *      If the given class is not persistence-capable.
0535:             */
0536:
0537:            public ClassTable getTable(Class c) {
0538:                ClassTable ct;
0539:
0540:                synchronized (this ) {
0541:                    ct = (ClassTable) tablesByJavaID.get(c.getName());
0542:                }
0543:
0544:                if (ct == null) {
0545:                    addClasses(new Class[] { c });
0546:
0547:                    /* Retry. */
0548:                    synchronized (this ) {
0549:                        ct = (ClassTable) tablesByJavaID.get(c.getName());
0550:                    }
0551:
0552:                    if (ct == null)
0553:                        throw new NoExtentException(c);
0554:                }
0555:
0556:                return ct;
0557:            }
0558:
0559:            /**
0560:             * Returns the primary table serving as backing for the given class.
0561:             * If the class is not yet known to the store manager, {@link #addClasses}
0562:             * is called to add it.
0563:             * <p>
0564:             * This method is functionally equivalent to {@link #getTable(Class)}
0565:             * except that it will only return base tables (not views).
0566:             *
0567:             * @param c     The class whose table is be returned.
0568:             *
0569:             * @return  The corresponding class table.
0570:             *
0571:             * @exception NoExtentException
0572:             *      If the given class is not persistence-capable.
0573:             * @exception ViewNotSupportedException
0574:             *      If the given class is backed by a view.
0575:             */
0576:
0577:            public ClassBaseTable getClassBaseTable(Class c) {
0578:                ClassTable t = getTable(c);
0579:
0580:                if (!(t instanceof  ClassBaseTable))
0581:                    throw new ViewNotSupportedException(c); // must be a ClassView
0582:
0583:                return (ClassBaseTable) t;
0584:            }
0585:
0586:            /**
0587:             * Returns the Java name of the JDO table having the given table ID.
0588:             * Returns <code>null</code> if no such table exists in the schema.
0589:             * <p>
0590:             * If the table is not (yet) known to the store manager, this method does
0591:             * <em>not</em> initialize it.
0592:             *
0593:             * @param tableID
0594:             *      The table ID of the table to be returned.
0595:             *
0596:             * @return  The corresponding Java name, or <code>null</code>.
0597:             */
0598:
0599:            public String getJavaName(final int tableID) {
0600:                JDOTable table = getTable(tableID);
0601:
0602:                if (table != null)
0603:                    return table.getJavaName();
0604:                else {
0605:                    checkSchemaInitialized();
0606:
0607:                    final String s[] = new String[1];
0608:
0609:                    MgmtTransaction mtx = new MgmtTransaction(
0610:                            Connection.TRANSACTION_READ_COMMITTED) {
0611:                        public String toString() {
0612:                            return "Query schema table for schema "
0613:                                    + schemaName;
0614:                        }
0615:
0616:                        protected void execute(Connection conn)
0617:                                throws SQLException {
0618:                            synchronized (StoreManager.this ) {
0619:                                if (schemaTable != null)
0620:                                    s[0] = schemaTable.getJavaName(tableID,
0621:                                            conn);
0622:                            }
0623:                        }
0624:                    };
0625:
0626:                    mtx.execute();
0627:
0628:                    String javaName = s[0];
0629:
0630:                    if (javaName == null)
0631:                        throw new JDOUserException("Unknown table ID = "
0632:                                + tableID);
0633:
0634:                    return javaName;
0635:                }
0636:            }
0637:
0638:            public Extent getExtent(PersistenceManager pm, Class c,
0639:                    boolean subclasses) {
0640:                ClassTable t = getTable(c);
0641:
0642:                return t.newExtent(pm, subclasses);
0643:            }
0644:
0645:            public Query getQuery(PersistenceManager pm, Object query) {
0646:                return getQuery("javax.jdo.query.JDOQL", pm, query);
0647:            }
0648:
0649:            public Query getQuery(String language, PersistenceManager pm,
0650:                    Object query) {
0651:                Query q;
0652:
0653:                if (language.equals("javax.jdo.query.JDOQL")) {
0654:                    if (query != null && !(query instanceof  JDOQLQuery))
0655:                        throw new JDOUserException("Invalid query argument, "
0656:                                + query + ", should be an object of type "
0657:                                + JDOQLQuery.class.getName());
0658:
0659:                    q = new JDOQLQuery(pm, this , (JDOQLQuery) query);
0660:                } else if (language.equals("javax.jdo.query.TJDOSQL")) {
0661:                    if (query == null || !(query instanceof  String))
0662:                        throw new JDOUserException(
0663:                                "Invalid query argument, "
0664:                                        + query
0665:                                        + ", should be a String containing a SQL SELECT statement, optionally using embedded macros");
0666:
0667:                    q = new TJDOSQLQuery(pm, this , (String) query);
0668:                } else
0669:                    throw new JDOUserException("Unknown query language: "
0670:                            + language);
0671:
0672:                return q;
0673:            }
0674:
0675:            /**
0676:             * Returns a new, unique ID for an object of the given class.
0677:             *
0678:             * @param c     The class of the object.
0679:             *
0680:             * @return  A new object ID.
0681:             */
0682:
0683:            public Object newObjectID(Class c) {
0684:                ClassMetaData cmd = ClassMetaData.forClass(c);
0685:
0686:                if (cmd == null)
0687:                    throw new ClassNotPersistenceCapableException(c);
0688:
0689:                if (cmd.requiresExtent())
0690:                    return getTable(c).newOID();
0691:                else
0692:                    return new SCOID(c);
0693:            }
0694:
0695:            /**
0696:             * Returns the next OID high-order value for IDs of the given class.
0697:             *
0698:             * @param classID   The class ID number of the class.
0699:             *
0700:             * @return  The next high-order OID value.
0701:             *
0702:             * @exception JDODataStoreException
0703:             *      If an error occurs in accessing or updating the schema table.
0704:             */
0705:
0706:            int getNextOIDHiValue(final int classID) {
0707:                final int[] nextHiValue = new int[] { -1 };
0708:                MgmtTransaction mtx = new MgmtTransaction(
0709:                        Connection.TRANSACTION_SERIALIZABLE) {
0710:                    public String toString() {
0711:                        return "Obtain next ID value for class ID " + classID;
0712:                    }
0713:
0714:                    protected void execute(Connection conn) throws SQLException {
0715:                        nextHiValue[0] = schemaTable.getNextOIDHiValue(classID,
0716:                                conn);
0717:                    }
0718:                };
0719:
0720:                mtx.execute();
0721:
0722:                return nextHiValue[0];
0723:            }
0724:
0725:            /**
0726:             * Inserts a persistent object into the database.
0727:             *
0728:             * @param sm    The state manager of the object to be inserted.
0729:             */
0730:
0731:            public void insert(StateManager sm) {
0732:                getClassBaseTable(sm.getObject().getClass()).insert(sm);
0733:            }
0734:
0735:            /**
0736:             * Confirms that a persistent object exists in the database.
0737:             *
0738:             * @param sm            The state manager of the object to be fetched.
0739:             */
0740:
0741:            public void lookup(StateManager sm) {
0742:                getClassBaseTable(sm.getObject().getClass()).lookup(sm);
0743:            }
0744:
0745:            /**
0746:             * Fetches a persistent object from the database.
0747:             *
0748:             * @param sm            The state manager of the object to be fetched.
0749:             * @param fieldNumbers  The numbers of the fields to be fetched.
0750:             */
0751:
0752:            public void fetch(StateManager sm, int fieldNumbers[]) {
0753:                getClassBaseTable(sm.getObject().getClass()).fetch(sm,
0754:                        fieldNumbers);
0755:            }
0756:
0757:            /**
0758:             * Updates a persistent object in the database.
0759:             *
0760:             * @param sm            The state manager of the object to be updated.
0761:             * @param fieldNumbers  The numbers of the fields to be updated.
0762:             */
0763:
0764:            public void update(StateManager sm, int fieldNumbers[]) {
0765:                getClassBaseTable(sm.getObject().getClass()).update(sm,
0766:                        fieldNumbers);
0767:            }
0768:
0769:            /**
0770:             * Deletes a persistent object from the database.
0771:             *
0772:             * @param sm    The state manager of the object to be deleted.
0773:             */
0774:
0775:            public void delete(StateManager sm) {
0776:                getClassBaseTable(sm.getObject().getClass()).delete(sm);
0777:            }
0778:
0779:            /**
0780:             * Returns a request object that will insert a row in the given table.
0781:             * The store manager will cache the request object for re-use by subsequent
0782:             * requests to the same table.
0783:             *
0784:             * @param cbt   The table into which to insert.
0785:             *
0786:             * @return  An insertion request object.
0787:             */
0788:
0789:            InsertRequest getInsertRequest(ClassBaseTable cbt) {
0790:                RequestIdentifier reqID = new RequestIdentifier(cbt, null,
0791:                        RequestIdentifier.Type.INSERT);
0792:                InsertRequest req;
0793:
0794:                req = (InsertRequest) requestsByID.get(reqID);
0795:
0796:                if (req == null) {
0797:                    req = new InsertRequest(cbt);
0798:                    requestsByID.put(reqID, req);
0799:                }
0800:
0801:                return req;
0802:            }
0803:
0804:            /**
0805:             * Returns a request object that will lookup a row in the given table.
0806:             * The store manager will cache the request object for re-use by subsequent
0807:             * requests to the same table.
0808:             *
0809:             * @param cbt   The table in which to lookup.
0810:             *
0811:             * @return  A lookup request object.
0812:             */
0813:
0814:            LookupRequest getLookupRequest(ClassBaseTable cbt) {
0815:                RequestIdentifier reqID = new RequestIdentifier(cbt, null,
0816:                        RequestIdentifier.Type.LOOKUP);
0817:                LookupRequest req;
0818:
0819:                req = (LookupRequest) requestsByID.get(reqID);
0820:
0821:                if (req == null) {
0822:                    req = new LookupRequest(cbt);
0823:                    requestsByID.put(reqID, req);
0824:                }
0825:
0826:                return req;
0827:            }
0828:
0829:            /**
0830:             * Returns a request object that will fetch a row from the given table.
0831:             * The store manager will cache the request object for re-use by subsequent
0832:             * requests to the same table.
0833:             *
0834:             * @param cbt           The table from which to fetch.
0835:             * @param fieldNumbers  The field numbers corresponding to the columns to be
0836:             *                      fetched.  Field numbers whose columns exist in
0837:             *                      supertables will be ignored.
0838:             *
0839:             * @return  A fetch request object.
0840:             */
0841:
0842:            FetchRequest getFetchRequest(ClassBaseTable cbt, int[] fieldNumbers) {
0843:                RequestIdentifier reqID = new RequestIdentifier(cbt,
0844:                        fieldNumbers, RequestIdentifier.Type.FETCH);
0845:                FetchRequest req;
0846:
0847:                req = (FetchRequest) requestsByID.get(reqID);
0848:
0849:                if (req == null) {
0850:                    req = new FetchRequest(cbt, fieldNumbers);
0851:                    requestsByID.put(reqID, req);
0852:                }
0853:
0854:                return req;
0855:            }
0856:
0857:            /**
0858:             * Returns a request object that will update a row in the given table.
0859:             * The store manager will cache the request object for re-use by subsequent
0860:             * requests to the same table.
0861:             *
0862:             * @param cbt           The table in which to update.
0863:             * @param fieldNumbers  The field numbers corresponding to the columns to be
0864:             *                      updated.  Field numbers whose columns exist in
0865:             *                      supertables will be ignored.
0866:             *
0867:             * @return  An update request object.
0868:             */
0869:
0870:            UpdateRequest getUpdateRequest(ClassBaseTable cbt,
0871:                    int[] fieldNumbers) {
0872:                RequestIdentifier reqID = new RequestIdentifier(cbt,
0873:                        fieldNumbers, RequestIdentifier.Type.UPDATE);
0874:                UpdateRequest req;
0875:
0876:                req = (UpdateRequest) requestsByID.get(reqID);
0877:
0878:                if (req == null) {
0879:                    req = new UpdateRequest(cbt, fieldNumbers);
0880:                    requestsByID.put(reqID, req);
0881:                }
0882:
0883:                return req;
0884:            }
0885:
0886:            /**
0887:             * Returns a request object that will delete a row from the given table.
0888:             * The store manager will cache the request object for re-use by subsequent
0889:             * requests to the same table.
0890:             *
0891:             * @param cbt   The table from which to delete.
0892:             *
0893:             * @return  A deletion request object.
0894:             */
0895:
0896:            DeleteRequest getDeleteRequest(ClassBaseTable cbt) {
0897:                RequestIdentifier reqID = new RequestIdentifier(cbt, null,
0898:                        RequestIdentifier.Type.DELETE);
0899:                DeleteRequest req;
0900:
0901:                req = (DeleteRequest) requestsByID.get(reqID);
0902:
0903:                if (req == null) {
0904:                    req = new DeleteRequest(cbt);
0905:                    requestsByID.put(reqID, req);
0906:                }
0907:
0908:                return req;
0909:            }
0910:
0911:            /**
0912:             * Returns the type of a database table.
0913:             *
0914:             * @param tableName     The name of the table (or view).
0915:             * @param conn          A JDBC connection to the database.
0916:             *
0917:             * @return  one of the TABLE_TYPE_* values from {@link Table}.
0918:             *
0919:             * @see Table
0920:             */
0921:
0922:            public int getTableType(SQLIdentifier tableName, Connection conn)
0923:                    throws SQLException {
0924:                String tableType = null;
0925:                String tableNameSQL = tableName.getSQLIdentifier();
0926:                DatabaseMetaData dmd = conn.getMetaData();
0927:
0928:                ResultSet rs = dmd.getTables(null, schemaName, tableNameSQL,
0929:                        null);
0930:
0931:                try {
0932:                    while (rs.next()) {
0933:                        if (tableNameSQL.equalsIgnoreCase(rs.getString(3))) {
0934:                            tableType = rs.getString(4).toUpperCase();
0935:                            break;
0936:                        }
0937:                    }
0938:                } finally {
0939:                    rs.close();
0940:                }
0941:
0942:                if (tableType == null)
0943:                    return Table.TABLE_TYPE_MISSING;
0944:                else if (tableType.equals("TABLE"))
0945:                    return Table.TABLE_TYPE_BASE_TABLE;
0946:                else if (tableType.equals("VIEW"))
0947:                    return Table.TABLE_TYPE_VIEW;
0948:                else
0949:                    return Table.TABLE_TYPE_UNKNOWN;
0950:            }
0951:
0952:            /**
0953:             * Returns the column info for a database table.  This should be used
0954:             * instead of making direct calls to DatabaseMetaData.getColumns().
0955:             *
0956:             * <p>Where possible, this method loads and caches column info for more than
0957:             * just the table being requested, improving performance by reducing the
0958:             * overall number of calls made to DatabaseMetaData.getColumns() (each of
0959:             * which usually results in one or more database queries).
0960:             *
0961:             * @param tableName     The name of the table (or view).
0962:             * @param conn          A JDBC connection to the database.
0963:             *
0964:             * @return  A list of ColumnInfo objects describing the columns of the
0965:             *          table.  The list is in the same order as was supplied by
0966:             *          getColumns().  If no column info is found for the given table,
0967:             *          an empty list is returned.
0968:             *
0969:             * @see ColumnInfo
0970:             */
0971:
0972:            List getColumnInfo(SQLIdentifier tableName, Connection conn)
0973:                    throws SQLException {
0974:                List cols = null;
0975:
0976:                if (schemaTable == null) {
0977:                    /*
0978:                     * There's no SchemaTable yet.  We can't yet employ the smart
0979:                     * caching stuff below, so we just make a direct JDBC metadata
0980:                     * query to get the info for this one table.  This should only be
0981:                     * the case when we're validating the SchemaTable itself.
0982:                     */
0983:                    cols = new ArrayList();
0984:                    DatabaseMetaData dmd = conn.getMetaData();
0985:                    ResultSet rs = dmd.getColumns(null, schemaName, tableName
0986:                            .getSQLIdentifier(), null);
0987:
0988:                    try {
0989:                        while (rs.next())
0990:                            cols.add(dba.newColumnInfo(rs));
0991:                    } finally {
0992:                        rs.close();
0993:                    }
0994:                } else {
0995:                    synchronized (this ) {
0996:                        long now = System.currentTimeMillis();
0997:
0998:                        /*
0999:                         * If we have cached column info that hasn't expired yet, see
1000:                         * if it contains info for the given table.
1001:                         */
1002:                        if (now >= columnInfoReadTimestamp
1003:                                && now < columnInfoReadTimestamp
1004:                                        + COLUMN_INFO_EXPIRATION_MS)
1005:                            cols = (List) columnInfoByTableName.get(tableName);
1006:
1007:                        /*
1008:                         * If we have no info for that table, or stale info overall,
1009:                         * refresh the column info cache.
1010:                         */
1011:                        if (cols == null) {
1012:                            /*
1013:                             * Determine the set of known JDO tables according to what's
1014:                             * recorded in the schema table.
1015:                             */
1016:                            HashSet knownTableNames = new HashSet();
1017:                            Iterator i = schemaTable.getAllTableMetadata(false,
1018:                                    conn).iterator();
1019:
1020:                            while (i.hasNext())
1021:                                knownTableNames
1022:                                        .add(((TableMetadata) i.next()).tableName);
1023:
1024:                            /*
1025:                             * Query for column info on all tables in the schema,
1026:                             * discarding info for any tables that aren't JDO tables,
1027:                             * or that are JDO tables but are already validated.
1028:                             */
1029:                            HashMap cim = new HashMap();
1030:                            DatabaseMetaData dmd = conn.getMetaData();
1031:                            ResultSet rs = dmd.getColumns(null, schemaName,
1032:                                    null, null);
1033:
1034:                            try {
1035:                                while (rs.next()) {
1036:                                    SQLIdentifier tblName = new SQLIdentifier(
1037:                                            dba, rs.getString(3));
1038:
1039:                                    if (knownTableNames.contains(tblName)) {
1040:                                        Table tbl = (Table) tablesByName
1041:                                                .get(tblName);
1042:
1043:                                        if (tbl == null || !tbl.isValidated()) {
1044:                                            List l = (List) cim.get(tblName);
1045:
1046:                                            if (l == null) {
1047:                                                l = new ArrayList();
1048:                                                cim.put(tblName, l);
1049:                                            }
1050:
1051:                                            l.add(dba.newColumnInfo(rs));
1052:                                        }
1053:                                    }
1054:                                }
1055:                            } finally {
1056:                                rs.close();
1057:                            }
1058:
1059:                            if (LOG.isDebugEnabled())
1060:                                LOG.debug("Column info loaded for "
1061:                                        + schemaName + ", " + cim.size()
1062:                                        + " tables, time = "
1063:                                        + (System.currentTimeMillis() - now)
1064:                                        + " ms");
1065:
1066:                            /* Replace the old cache (if any) with the new one. */
1067:                            columnInfoByTableName = cim;
1068:                            columnInfoReadTimestamp = now;
1069:
1070:                            /*
1071:                             * Finally, lookup info for the desired table in the new
1072:                             * cache.
1073:                             */
1074:                            cols = (List) columnInfoByTableName.get(tableName);
1075:
1076:                            if (cols == null) {
1077:                                cols = Collections.EMPTY_LIST;
1078:                                LOG.warn("No column info found for "
1079:                                        + tableName);
1080:                            }
1081:                        }
1082:                    }
1083:                }
1084:
1085:                return cols;
1086:            }
1087:
1088:            /**
1089:             * Returns the foreign key info for a database table.
1090:             * This should be used instead of making direct calls to
1091:             * DatabaseMetaData.getImportedKeys() or DatabaseMetaData.getExportedKeys().
1092:             *
1093:             * @param tableName     The name of the table (or view).
1094:             * @param conn          A JDBC connection to the database.
1095:             *
1096:             * @return
1097:             *      A list of ForeignKeyInfo objects describing the columns of the
1098:             *      table's foreign keys.
1099:             *      The list is in the same order as was supplied by get??portedKeys().
1100:             *      If no column info is found for the given table, an empty list is
1101:             *      returned.
1102:             *
1103:             * @see ForeignKeyInfo
1104:             */
1105:
1106:            List getForeignKeyInfo(SQLIdentifier tableName, Connection conn)
1107:                    throws SQLException {
1108:                List fkCols = new ArrayList();
1109:                DatabaseMetaData dmd = conn.getMetaData();
1110:                ResultSet rs = dmd.getImportedKeys(null, schemaName, tableName
1111:                        .getSQLIdentifier());
1112:
1113:                try {
1114:                    while (rs.next()) {
1115:                        ForeignKeyInfo fki = dba.newForeignKeyInfo(rs);
1116:
1117:                        /*
1118:                         * The contains() test is necessary only because some drivers
1119:                         * have been known to be so confused (PostgreSQL, I'm looking at
1120:                         * you) as to return duplicate rows from getImportedKeys().
1121:                         */
1122:                        if (!fkCols.contains(fki))
1123:                            fkCols.add(fki);
1124:                    }
1125:                } finally {
1126:                    rs.close();
1127:                }
1128:
1129:                return fkCols;
1130:            }
1131:
1132:            /**
1133:             * Tests if a database table exists.
1134:             *
1135:             * @param tableName     The name of the table (or view).
1136:             * @param conn          A JDBC connection to the database.
1137:             *
1138:             * @return  <tt>true</tt> if the table exists in the database,
1139:             *          <tt>false</tt> otherwise.
1140:             */
1141:
1142:            public boolean tableExists(SQLIdentifier tableName, Connection conn)
1143:                    throws SQLException {
1144:                return getTableType(tableName, conn) != Table.TABLE_TYPE_MISSING;
1145:            }
1146:
1147:            /**
1148:             * Resolves an identifier macro.  The public fields <var>clazz</var>,
1149:             * <var>fieldName</var>, and <var>subfieldName</var> of the given macro are
1150:             * taken as inputs, and the public <var>value</var> field is set to the SQL
1151:             * identifier of the corresponding database table or column.
1152:             *
1153:             * @param im    The macro to resolve.
1154:             */
1155:
1156:            public void resolveIdentifierMacro(MacroString.IdentifierMacro im) {
1157:                ClassTable ct = getTable(im.clazz);
1158:
1159:                if (im.fieldName == null) {
1160:                    im.value = ct.getName().toString();
1161:                    return;
1162:                }
1163:
1164:                ColumnMapping cm;
1165:
1166:                if (im.fieldName.equals("this")) {
1167:                    if (!(ct instanceof  ClassBaseTable))
1168:                        throw new JDOUserException("Table for class "
1169:                                + im.clazz.getName() + " has no ID column");
1170:
1171:                    if (im.subfieldName != null)
1172:                        throw new JDOUserException(
1173:                                "Field "
1174:                                        + im.clazz.getName()
1175:                                        + ".this has no table of its own in which to look for "
1176:                                        + im.subfieldName);
1177:
1178:                    cm = ((ClassBaseTable) ct).getIDMapping();
1179:                } else {
1180:                    ClassMetaData cmd = ClassMetaData.forClass(im.clazz);
1181:                    FieldMetaData fmd = cmd.getFieldRelative(cmd
1182:                            .getRelativeFieldNumber(im.fieldName));
1183:
1184:                    if (im.subfieldName == null) {
1185:                        Mapping m = ct.getFieldMapping(im.fieldName);
1186:
1187:                        if (m instanceof  ColumnMapping)
1188:                            cm = (ColumnMapping) m;
1189:                        else {
1190:                            JDOTable t = getTable(fmd);
1191:
1192:                            if (t == null)
1193:                                throw new JDOUserException(
1194:                                        "Invalid pseudo-field name "
1195:                                                + im.subfieldName
1196:                                                + " in macro "
1197:                                                + im
1198:                                                + ", has no backing table or column");
1199:
1200:                            im.value = t.getName().toString();
1201:                            return;
1202:                        }
1203:                    } else {
1204:                        JDOTable t = getTable(fmd);
1205:
1206:                        if (t instanceof  SetTable) {
1207:                            SetTable st = (SetTable) t;
1208:
1209:                            if (im.subfieldName.equals("owner"))
1210:                                cm = st.getOwnerMapping();
1211:                            else if (im.subfieldName.equals("element"))
1212:                                cm = st.getElementMapping();
1213:                            else
1214:                                throw new JDOUserException(
1215:                                        "Invalid pseudo-field name "
1216:                                                + im.subfieldName
1217:                                                + " in macro "
1218:                                                + im
1219:                                                + ", must be \"owner\" or \"element\"");
1220:                        } else if (t instanceof  MapTable) {
1221:                            MapTable mt = (MapTable) t;
1222:
1223:                            if (im.subfieldName.equals("owner"))
1224:                                cm = mt.getOwnerMapping();
1225:                            else if (im.subfieldName.equals("key"))
1226:                                cm = mt.getKeyMapping();
1227:                            else if (im.subfieldName.equals("value"))
1228:                                cm = mt.getValueMapping();
1229:                            else
1230:                                throw new JDOUserException(
1231:                                        "Invalid pseudo-field name "
1232:                                                + im.subfieldName
1233:                                                + " in macro "
1234:                                                + im
1235:                                                + ", must be \"owner\", \"key\", or \"value\"");
1236:                        } else
1237:                            throw new JDOUserException(
1238:                                    "Field "
1239:                                            + im.clazz.getName()
1240:                                            + '.'
1241:                                            + im.fieldName
1242:                                            + " has no table of its own in which to look for "
1243:                                            + im.subfieldName);
1244:                    }
1245:                }
1246:
1247:                im.value = cm.getColumn().getName().toString();
1248:            }
1249:
1250:            /*----------------------------- Inner classes ----------------------------*/
1251:
1252:            /**
1253:             * An abstract base class for StoreManager transactions that perform some
1254:             * management function on the database.
1255:             * <p>
1256:             * Management transactions may be retried in the face of SQL exceptions to
1257:             * work around failures caused by transient conditions, such as DB deadlocks.
1258:             */
1259:
1260:            private abstract class MgmtTransaction {
1261:                public static final String MAX_RETRIES_PROPERTY = "com.triactive.jdo.store.maxRetries";
1262:
1263:                protected final int isolationLevel;
1264:                protected final int maxRetries;
1265:
1266:                /**
1267:                 * Constructs a new management transaction having the given isolation
1268:                 * level.
1269:                 *
1270:                 * @param isolationLevel
1271:                 *      One of the isolation level constants from java.sql.Connection.
1272:                 */
1273:
1274:                public MgmtTransaction(int isolationLevel) {
1275:                    this .isolationLevel = isolationLevel;
1276:
1277:                    String s = System.getProperty(MAX_RETRIES_PROPERTY, "3");
1278:                    int mr;
1279:
1280:                    try {
1281:                        mr = Integer.parseInt(s);
1282:                    } catch (NumberFormatException e) {
1283:                        LOG.warn("Failed parsing " + MAX_RETRIES_PROPERTY
1284:                                + " property, value was " + s);
1285:                        mr = 3;
1286:                    }
1287:
1288:                    maxRetries = mr;
1289:                }
1290:
1291:                /**
1292:                 * Returns a description of the management transaction.
1293:                 * Subclasses should override this method so that transaction failures
1294:                 * are given an appropriate exception message.
1295:                 *
1296:                 * @return  A description of the management transaction. 
1297:                 */
1298:
1299:                public abstract String toString();
1300:
1301:                /**
1302:                 * Implements the body of the transaction.
1303:                 *
1304:                 * @param conn
1305:                 *      A connection to the database.  If the selected isolation level
1306:                 *      is Connection.TRANSACTION_NONE the connection has auto-commit
1307:                 *      set to true, otherwise auto-commit is false.
1308:                 *
1309:                 * @exception SQLException
1310:                 *      If the transaction fails due to a database error that should
1311:                 *      allow the entire transaction to be retried.
1312:                 */
1313:
1314:                protected abstract void execute(Connection conn)
1315:                        throws SQLException;
1316:
1317:                /**
1318:                 * Executes the transaction.
1319:                 * <p>
1320:                 * A database connection is acquired and the {@link #execute(Connection)}
1321:                 * method is invoked.
1322:                 * If the selected isolation level is not Connection.TRANSACTION_NONE,
1323:                 * then commit() or rollback() is called on the connection according to
1324:                 * whether the invocation succeeded or not.
1325:                 * If the invocation failed the sequence is repeated, up to a maximum of
1326:                 * <var>maxRetries</var> times, configurable by the system property
1327:                 * com.triactive.jdo.store.maxRetries.
1328:                 *
1329:                 * @exception JDODataStoreException
1330:                 *      If a SQL exception occurred even after <var>maxRetries</var>
1331:                 *      attempts.
1332:                 */
1333:
1334:                public final void execute() {
1335:                    int attempts = 0;
1336:
1337:                    for (;;) {
1338:                        try {
1339:                            Connection conn = dba.getConnection(ds, userName,
1340:                                    password, isolationLevel);
1341:
1342:                            try {
1343:                                boolean succeeded = false;
1344:
1345:                                try {
1346:                                    execute(conn);
1347:                                    succeeded = true;
1348:                                } finally {
1349:                                    if (isolationLevel != Connection.TRANSACTION_NONE) {
1350:                                        if (succeeded)
1351:                                            conn.commit();
1352:                                        else
1353:                                            conn.rollback();
1354:                                    }
1355:                                }
1356:                            } finally {
1357:                                dba.closeConnection(conn);
1358:                            }
1359:
1360:                            break;
1361:                        } catch (SQLException e) {
1362:                            SQLState state = dba.getSQLState(e);
1363:
1364:                            if ((state != null && !state.isWorthRetrying())
1365:                                    || ++attempts >= maxRetries)
1366:                                throw new JDODataStoreException(
1367:                                        "SQL exception: " + this , e);
1368:                        }
1369:                    }
1370:                }
1371:            }
1372:
1373:            /**
1374:             * A management transaction that adds a set of classes to the StoreManager,
1375:             * making them usable for persistence.
1376:             * <p>
1377:             * This class embodies the work necessary to activate a persistent class and
1378:             * ready it for storage management.
1379:             * It is the primary mutator of a StoreManager.
1380:             * <p>
1381:             * Adding classes is an involved process that includes the creation and/or
1382:             * validation in the database of tables, views, and table constraints, and
1383:             * their corresponding Java objects maintained by the StoreManager.
1384:             * Since it's a management transaction, the entire process is subject to
1385:             * retry on SQL exceptions.
1386:             * It is responsible for ensuring that the procedure either adds <i>all</i>
1387:             * of the requested classes successfully, or adds none of them and preserves
1388:             * the previous state of the StoreManager exactly as it was.
1389:             */
1390:
1391:            private class ClassAdder extends MgmtTransaction {
1392:                private final Class[] classes;
1393:                private Connection schemaConnection = null;
1394:
1395:                /**
1396:                 * Constructs a new class adder transaction that will add the given
1397:                 * classes to the StoreManager.
1398:                 *
1399:                 * @param classes   The class(es) to be added.
1400:                 */
1401:
1402:                public ClassAdder(Class[] classes) {
1403:                    super (Connection.TRANSACTION_SERIALIZABLE);
1404:
1405:                    this .classes = classes;
1406:                }
1407:
1408:                public String toString() {
1409:                    return "Add classes to schema " + schemaName;
1410:                }
1411:
1412:                protected void execute(Connection conn) throws SQLException {
1413:                    synchronized (StoreManager.this ) {
1414:                        classAdder = this ;
1415:                        schemaConnection = conn;
1416:
1417:                        try {
1418:                            try {
1419:                                addClassTablesAndValidate(classes);
1420:                            } catch (NestedSQLException e) {
1421:                                throw e.getSQLException();
1422:                            }
1423:                        } finally {
1424:                            schemaConnection = null;
1425:                            classAdder = null;
1426:                        }
1427:                    }
1428:                }
1429:
1430:                /**
1431:                 * Called by StoreManager.addClasses() when it has been recursively
1432:                 * re-entered.
1433:                 * This just adds table objects for the requested classes and returns.
1434:                 *
1435:                 * @param classes   The class(es) to be added.
1436:                 *
1437:                 * @exception NestedSQLException
1438:                 *      If a SQL exception occurs it is wrapped within one of these.
1439:                 *      The assumption is that the top-level ClassAdder.execute() will
1440:                 *      pick it out and rethrow it as a SQLException so that the entire
1441:                 *      ClassAdder transaction can be retried, if appropriate.
1442:                 */
1443:
1444:                public void addClasses(Class[] classes) {
1445:                    if (schemaConnection == null)
1446:                        throw new IllegalStateException(
1447:                                "Add classes transaction is not active");
1448:
1449:                    try {
1450:                        addClassTables(classes);
1451:                    } catch (SQLException e) {
1452:                        throw new NestedSQLException(e);
1453:                    }
1454:                }
1455:
1456:                /**
1457:                 * Adds a new table object (ie ClassBaseTable or ClassView) for every class
1458:                 * in the given list that 1) requires an extent and 2) does not yet have an
1459:                 * extent (ie table) initialized in the store manager.
1460:                 *
1461:                 * <p>This doesn't initialize or validate the tables, it just adds the table
1462:                 * objects to the StoreManager's internal data structures.
1463:                 *
1464:                 * @param classes   The class(es) whose tables are to be added.
1465:                 */
1466:
1467:                private void addClassTables(Class[] classes)
1468:                        throws SQLException {
1469:                    Iterator i = getReferencedClasses(classes).iterator();
1470:
1471:                    while (i.hasNext()) {
1472:                        ClassMetaData cmd = (ClassMetaData) i.next();
1473:
1474:                        if (getTable(cmd) == null && cmd.requiresExtent()) {
1475:                            TableMetadata tmd = schemaTable.getTableMetadata(
1476:                                    cmd, schemaConnection);
1477:                            ClassTable t;
1478:
1479:                            if (cmd.getViewDefinition(dba.getVendorID()) != null)
1480:                                t = new ClassView(tmd, cmd, StoreManager.this );
1481:                            else
1482:                                t = new ClassBaseTable(tmd, cmd,
1483:                                        StoreManager.this );
1484:
1485:                            addTable(t);
1486:                        }
1487:                    }
1488:
1489:                }
1490:
1491:                /**
1492:                 * Returns a List of {@link ClassMetaData} objects representing the set of
1493:                 * classes consisting of the requested classes plus any and all other
1494:                 * classes they may reference, directly or indirectly.
1495:                 * The returned list is ordered by dependency.
1496:                 *
1497:                 * @param classes   An array of persistence-capable classes.
1498:                 *
1499:                 * @return A List of <tt>ClassMetaData</tt> objects.
1500:                 *
1501:                 * @exception ClassNotPersistenceCapableException
1502:                 *      If any of the given classes is not persistence-capable.
1503:                 */
1504:
1505:                private List getReferencedClasses(Class[] classes) {
1506:                    List cmds = new ArrayList();
1507:
1508:                    for (int i = 0; i < classes.length; ++i) {
1509:                        ClassMetaData cmd = ClassMetaData.forClass(classes[i]);
1510:
1511:                        if (cmd == null)
1512:                            throw new ClassNotPersistenceCapableException(
1513:                                    classes[i]);
1514:
1515:                        cmds
1516:                                .addAll(cmd.getReferencedClasses(dba
1517:                                        .getVendorID()));
1518:                    }
1519:
1520:                    return cmds;
1521:                }
1522:
1523:                /**
1524:                 * Adds a new table object to the StoreManager's internal data structures.
1525:                 *
1526:                 * @param t The table to be added.
1527:                 */
1528:
1529:                private void addTable(JDOTable t) {
1530:                    allTables.add(t);
1531:
1532:                    int tableID = t.getTableID();
1533:
1534:                    while (tablesByTableID.size() <= tableID)
1535:                        tablesByTableID.add(null);
1536:
1537:                    tablesByTableID.set(tableID, t);
1538:                    tablesByName.put(t.getName(), t);
1539:                    tablesByJavaID.put(t.getJavaName(), t);
1540:                }
1541:
1542:                /**
1543:                 * Adds a new table object (ie ClassBaseTable or ClassView) for every class
1544:                 * in the given list that 1) requires an extent and 2) does not yet have an
1545:                 * extent (ie table) initialized in the store manager.
1546:                 *
1547:                 * <p>After all of the table objects, including any other tables they might
1548:                 * reference, have been added, each table is initialized and validated in
1549:                 * the database.
1550:                 *
1551:                 * <p>If any error occurs along the way, any table(s) that were created are
1552:                 * dropped and the state of the StoreManager is rolled back to the point at
1553:                 * which this method was called.
1554:                 *
1555:                 * @param classes   The class(es) whose tables are to be added.
1556:                 */
1557:
1558:                private void addClassTablesAndValidate(Class[] classes)
1559:                        throws SQLException {
1560:                    ArrayList allTablesPrev = (ArrayList) allTables.clone();
1561:                    ArrayList tablesByTableIDPrev = (ArrayList) tablesByTableID
1562:                            .clone();
1563:                    HashMap tablesByNamePrev = (HashMap) tablesByName.clone();
1564:                    HashMap tablesByJavaIDPrev = (HashMap) tablesByJavaID
1565:                            .clone();
1566:
1567:                    ArrayList baseTablesCreated = new ArrayList();
1568:                    ArrayList baseTableConstraintsCreated = new ArrayList();
1569:                    ArrayList viewsCreated = new ArrayList();
1570:                    boolean completed = false;
1571:
1572:                    try {
1573:                        /* Add ClassTable's for the requested classes. */
1574:                        addClassTables(classes);
1575:
1576:                        /*
1577:                         * Repeatedly loop over the set of all table objects, initializing
1578:                         * any that need initialization.  Each time a table object is
1579:                         * initialized, it may cause other associated table objects to be
1580:                         * added (via callbacks to addClasses()), so the loop must be
1581:                         * repeated until no more tables exist that need initialization.
1582:                         */
1583:                        ArrayList newBaseTables = new ArrayList();
1584:                        ArrayList newViews = new ArrayList();
1585:                        boolean someNeededInitialization;
1586:
1587:                        do {
1588:                            someNeededInitialization = false;
1589:
1590:                            Iterator i = ((ArrayList) allTables.clone())
1591:                                    .iterator();
1592:
1593:                            while (i.hasNext()) {
1594:                                JDOTable t = (JDOTable) i.next();
1595:
1596:                                if (!t.isInitialized()) {
1597:                                    t.initialize();
1598:
1599:                                    if (t instanceof  View)
1600:                                        newViews.add(t);
1601:                                    else
1602:                                        newBaseTables.add(t);
1603:
1604:                                    someNeededInitialization = true;
1605:                                }
1606:                            }
1607:                        } while (someNeededInitialization);
1608:
1609:                        /*
1610:                         * For each new BaseTable object, validate it against the actual
1611:                         * table in the database.  If the table doesn't exist it is created
1612:                         * (subject to auto-create mode).  Views are done later.
1613:                         */
1614:                        Iterator i = newBaseTables.iterator();
1615:
1616:                        while (i.hasNext()) {
1617:                            BaseTable t = (BaseTable) i.next();
1618:
1619:                            if (t.validate(tableValidationFlags,
1620:                                    schemaConnection))
1621:                                baseTablesCreated.add(t);
1622:
1623:                            /* Discard any cached column info used to validate the table. */
1624:                            columnInfoByTableName.remove(t.getName());
1625:                        }
1626:
1627:                        /*
1628:                         * Iterate over the newly added table objects again, this time
1629:                         * validating their required constraints against the actual
1630:                         * constraints existing in the database.  If the constraints don't
1631:                         * exist, they are created (subject to auto-create mode).
1632:                         * 
1633:                         * Constraint processing is done as a separate step from table
1634:                         * validation because you can't create constraints that reference
1635:                         * other tables until those tables exist.
1636:                         */
1637:                        i = newBaseTables.iterator();
1638:
1639:                        while (i.hasNext()) {
1640:                            BaseTable t = (BaseTable) i.next();
1641:
1642:                            if (t
1643:                                    .validateConstraints(
1644:                                            constraintValidationFlags,
1645:                                            schemaConnection))
1646:                                baseTableConstraintsCreated.add(t);
1647:                        }
1648:
1649:                        /*
1650:                         * For each new View object, validate it against the actual view in
1651:                         * the database.  If the view doesn't exist it is created (subject
1652:                         * to auto-create mode).
1653:                         */
1654:                        i = newViews.iterator();
1655:
1656:                        while (i.hasNext()) {
1657:                            View v = (View) i.next();
1658:
1659:                            if (v.validate(tableValidationFlags,
1660:                                    schemaConnection))
1661:                                viewsCreated.add(v);
1662:
1663:                            /* Discard any cached column info used to validate the view. */
1664:                            columnInfoByTableName.remove(v.getName());
1665:                        }
1666:
1667:                        completed = true;
1668:                    } finally {
1669:                        /*
1670:                         * If something went wrong, roll things back to the way they were
1671:                         * before we started.  This may not restore the database 100% of the
1672:                         * time (if DDL statements are not transactional) but it will always
1673:                         * put the StoreManager's internal structures back the way they
1674:                         * were.
1675:                         */
1676:                        if (!completed) {
1677:                            allTables = allTablesPrev;
1678:                            tablesByTableID = tablesByTableIDPrev;
1679:                            tablesByName = tablesByNamePrev;
1680:                            tablesByJavaID = tablesByJavaIDPrev;
1681:
1682:                            /*
1683:                             * Tables, table constraints, and views get removed in the
1684:                             * reverse order from which they were created.
1685:                             */
1686:                            try {
1687:                                ListIterator li = viewsCreated
1688:                                        .listIterator(viewsCreated.size());
1689:
1690:                                while (li.hasPrevious())
1691:                                    ((View) li.previous())
1692:                                            .drop(schemaConnection);
1693:
1694:                                li = baseTableConstraintsCreated
1695:                                        .listIterator(baseTableConstraintsCreated
1696:                                                .size());
1697:
1698:                                while (li.hasPrevious())
1699:                                    ((BaseTable) li.previous())
1700:                                            .dropConstraints(schemaConnection);
1701:
1702:                                li = baseTablesCreated
1703:                                        .listIterator(baseTablesCreated.size());
1704:
1705:                                while (li.hasPrevious())
1706:                                    ((BaseTable) li.previous())
1707:                                            .drop(schemaConnection);
1708:                            } catch (Exception e) {
1709:                                LOG
1710:                                        .warn("An error occurred while auto-creating schema elements.  The following exception occurred while attempting to rollback the partially-completed schema changes: "
1711:                                                + e.toString());
1712:                            }
1713:                        }
1714:                    }
1715:                }
1716:
1717:                /**
1718:                 * Called by Mapping objects in the midst of StoreManager.addClasses()
1719:                 * to request the creation of a set table.
1720:                 *
1721:                 * @param cbt   The base table for the class containing the set.
1722:                 * @param fmd   The field metadata describing the set field.
1723:                 *
1724:                 * @exception NestedSQLException
1725:                 *      If a SQL exception occurs it is wrapped within one of these.
1726:                 *      The assumption is that the top-level ClassAdder.execute() will
1727:                 *      pick it out and rethrow it as a SQLException so that the entire
1728:                 *      ClassAdder transaction can be retried, if appropriate.
1729:                 */
1730:
1731:                public SetTable newSetTable(ClassBaseTable cbt,
1732:                        FieldMetaData fmd) {
1733:                    TableMetadata tmd;
1734:
1735:                    try {
1736:                        tmd = schemaTable.getTableMetadata(fmd,
1737:                                schemaConnection);
1738:                    } catch (SQLException e) {
1739:                        throw new NestedSQLException(e);
1740:                    }
1741:
1742:                    SetTable st = new SetTable(tmd, fmd, StoreManager.this );
1743:
1744:                    addTable(st);
1745:
1746:                    return st;
1747:                }
1748:
1749:                /**
1750:                 * Called by Mapping objects in the midst of StoreManager.addClasses()
1751:                 * to request the creation of a map table.
1752:                 *
1753:                 * @param cbt   The base table for the class containing the map.
1754:                 * @param fmd   The field metadata describing the map field.
1755:                 *
1756:                 * @exception NestedSQLException
1757:                 *      If a SQL exception occurs it is wrapped within one of these.
1758:                 *      The assumption is that the top-level ClassAdder.execute() will
1759:                 *      pick it out and rethrow it as a SQLException so that the entire
1760:                 *      ClassAdder transaction can be retried, if appropriate.
1761:                 */
1762:
1763:                public MapTable newMapTable(ClassBaseTable cbt,
1764:                        FieldMetaData fmd) {
1765:                    TableMetadata tmd;
1766:
1767:                    try {
1768:                        tmd = schemaTable.getTableMetadata(fmd,
1769:                                schemaConnection);
1770:                    } catch (SQLException e) {
1771:                        throw new NestedSQLException(e);
1772:                    }
1773:
1774:                    MapTable mt = new MapTable(tmd, fmd, StoreManager.this );
1775:
1776:                    addTable(mt);
1777:
1778:                    return mt;
1779:                }
1780:            }
1781:
1782:            /**
1783:             * A runtime exception designed to tunnel a SQL exception from a deeply
1784:             * nested recursive procedure up to a higher level that can handle it.
1785:             */
1786:
1787:            private static class NestedSQLException extends RuntimeException {
1788:                private final SQLException e;
1789:
1790:                public NestedSQLException(SQLException e) {
1791:                    super (
1792:                            "Inner invocation of recursive procedure threw a SQL exception: "
1793:                                    + e.getMessage());
1794:                    this .e = e;
1795:                }
1796:
1797:                public SQLException getSQLException() {
1798:                    return e;
1799:                }
1800:            }
1801:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.