/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the "License"). You may not use this file except * in compliance with the License. * * You can obtain a copy of the license at * glassfish/bootstrap/legal/CDDLv1.0.txt or * https://glassfish.dev.java.net/public/CDDLv1.0.html. * See the License for the specific language governing * permissions and limitations under the License. * * When distributing Covered Code, include this CDDL * HEADER in each file and include the License file at * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable, * add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your * own identifying information: Portions Copyright [yyyy] * [name of copyright owner] */ // Copyright (c) 1998, 2005, Oracle. All rights reserved. package oracle.toplink.essentials.tools.schemaframework; import java.io.Writer; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Vector; import oracle.toplink.essentials.exceptions.DatabaseException; import oracle.toplink.essentials.exceptions.TopLinkException; import oracle.toplink.essentials.exceptions.ValidationException; import oracle.toplink.essentials.internal.databaseaccess.DatabaseAccessor; import oracle.toplink.essentials.internal.sequencing.Sequencing; import oracle.toplink.essentials.logging.SessionLog; import oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl; import oracle.toplink.essentials.descriptors.ClassDescriptor; import oracle.toplink.essentials.internal.sessions.AbstractSession; import oracle.toplink.essentials.sequencing.DefaultSequence; import oracle.toplink.essentials.sequencing.NativeSequence; import oracle.toplink.essentials.sequencing.Sequence; import oracle.toplink.essentials.sequencing.TableSequence; /** *

* Purpose: Define all user level protocol for development time database manipulation. *

* Responsibilities: *

*/ public class SchemaManager { protected DatabaseSessionImpl session; protected Writer createSchemaWriter; protected Writer dropSchemaWriter; protected boolean createSQLFiles = true; public SchemaManager(DatabaseSessionImpl session) { this.session = session; } public SchemaManager(oracle.toplink.essentials.sessions.DatabaseSession session) { this.session = ((DatabaseSessionImpl)session); } protected Writer getDropSchemaWriter() { if(null == dropSchemaWriter) return createSchemaWriter; else return dropSchemaWriter; } /** * PUBLIC: If the schema manager is writing to a writer, append this string * to that writer. */ public void appendToDDLWriter(String stringToWrite) { // If this method is called, we know that it is the old case and // it would not matter which schemaWriter we use as both the // create and drop schemaWriters are essentially the same. // So just pick one. appendToDDLWriter(createSchemaWriter, stringToWrite); } public void appendToDDLWriter(Writer schemaWriter, String stringToWrite) { if (schemaWriter == null) { return; //do nothing. Ignore append request } try { schemaWriter.write(stringToWrite); schemaWriter.flush(); } catch (java.io.IOException ioException) { throw ValidationException.fileError(ioException); } } /** * INTERNAL: * builds the field names based on the type read in from the builder */ public void buildFieldTypes(TableDefinition tableDef) { tableDef.buildFieldTypes(getSession()); } /** * PUBLIC: * Close the schema writer. */ public void closeDDLWriter() { closeDDLWriter(createSchemaWriter); if(null != dropSchemaWriter) closeDDLWriter(dropSchemaWriter); } public void closeDDLWriter(Writer schemaWriter) { if (schemaWriter == null) { return; } try { schemaWriter.flush(); schemaWriter.close(); schemaWriter = null; } catch (java.io.IOException ioException) { throw ValidationException.fileError(ioException); } } /** * Use the table definition to add the constraints to the database, this is normally done * in two steps to avoid dependencies. */ public void createConstraints(TableDefinition tableDefinition) throws TopLinkException { if (shouldWriteToDatabase()) { tableDefinition.createConstraintsOnDatabase(getSession()); } else { tableDefinition.createConstraints(getSession(), createSchemaWriter); } } /** * Use the definition object to create the schema entity on the database. * This is used for creating tables, views, procedures ... etc ... */ public void createObject(DatabaseObjectDefinition databaseObjectDefinition) throws TopLinkException { if (shouldWriteToDatabase()) { databaseObjectDefinition.createOnDatabase(getSession()); } else { databaseObjectDefinition.createObject(getSession(), createSchemaWriter); if(createSQLFiles) this.appendToDDLWriter(createSchemaWriter, getSession().getPlatform().getStoredProcedureTerminationToken()); this.appendToDDLWriter(createSchemaWriter, "\n"); } } /** * Create all the receiver's sequences on the database for all of the loaded descriptors. */ public void createSequences() throws TopLinkException { createOrReplaceSequences(true); } /** * Drop and recreate all the receiver's sequences on the database for all of the loaded descriptors. */ public void replaceSequences() throws TopLinkException { createOrReplaceSequences(false); } /** * Common implementor for createSequence and replaceSequence */ protected void createOrReplaceSequences(boolean create) throws TopLinkException { Sequencing sequencing = getSession().getSequencing(); if ((sequencing == null) || (sequencing.whenShouldAcquireValueForAll() == Sequencing.AFTER_INSERT)) { // Not required on Sybase native etc. return; } // Prepare table and sequence definitions // table name mapped to TableDefinition HashMap tableDefinitions = new HashMap(); // sequence name to SequenceDefinition HashSet sequenceDefinitions = new HashSet(); // remember the processed - to handle each sequence just once. HashSet processedSequenceNames = new HashSet(); Iterator descriptors = getSession().getDescriptors().values().iterator(); while (descriptors.hasNext()) { ClassDescriptor descriptor = (ClassDescriptor)descriptors.next(); if (descriptor.usesSequenceNumbers()) { String seqName = descriptor.getSequenceNumberName(); if (seqName == null) { seqName = getSession().getDatasourcePlatform().getDefaultSequence().getName(); } if (processedSequenceNames.contains(seqName)) { continue; } processedSequenceNames.add(seqName); Sequence sequence = getSession().getDatasourcePlatform().getSequence(seqName); if (sequence.shouldAcquireValueAfterInsert()) { continue; } SequenceDefinition sequenceDefinition = buildSequenceDefinition(sequence); if (sequenceDefinition == null) { continue; } sequenceDefinitions.add(sequenceDefinition); TableDefinition tableDefinition = sequenceDefinition.buildTableDefinition(); if (tableDefinition != null) { String tableName = tableDefinition.getName(); TableDefinition otherTableDefinition = (TableDefinition) tableDefinitions.get(tableName); if (otherTableDefinition != null) { // check for a conflict; if there is one - throw a ValidationException } else { tableDefinitions.put(tableName, tableDefinition); } } } } // create tables Iterator itTableDefinitions = tableDefinitions.values().iterator(); while (itTableDefinitions.hasNext()) { TableDefinition tableDefinition = (TableDefinition) itTableDefinitions.next(); // CR 3870467, do not log stack boolean shouldLogExceptionStackTrace = session.getSessionLog().shouldLogExceptionStackTrace(); if (shouldLogExceptionStackTrace) { session.getSessionLog().setShouldLogExceptionStackTrace(false); } if (create) { try { createObject(tableDefinition); } catch (DatabaseException exception) { // Ignore already created } finally { if (shouldLogExceptionStackTrace) { session.getSessionLog().setShouldLogExceptionStackTrace(true); } } } else { try { dropObject(tableDefinition); } catch (DatabaseException exception) { // Ignore table not found for first creation } finally { if (shouldLogExceptionStackTrace) { session.getSessionLog().setShouldLogExceptionStackTrace(true); } } createObject(tableDefinition); } } // create sequence objects Iterator itSequenceDefinitions = sequenceDefinitions.iterator(); while (itSequenceDefinitions.hasNext()) { SequenceDefinition sequenceDefinition = (SequenceDefinition) itSequenceDefinitions.next(); if (!create) { try { dropObject(sequenceDefinition); } catch (DatabaseException exception) { // Ignore sequence not found for first creation } } createObject(sequenceDefinition); } } protected SequenceDefinition buildSequenceDefinition(Sequence sequence) { if (sequence instanceof DefaultSequence) { String name = sequence.getName(); int size = sequence.getPreallocationSize(); sequence = getSession().getDatasourcePlatform().getDefaultSequence(); if (sequence instanceof TableSequence) { TableSequence tableSequence = (TableSequence) sequence; return new TableSequenceDefinition(name, tableSequence); } else if (sequence instanceof NativeSequence) { if (getSession().getDatasourcePlatform().isOracle()) { return new OracleSequenceDefinition(name, size); } else if (getSession().getDatasourcePlatform().isTimesTen()) { return new TimesTenSequenceDefinition(name, size); } else { return null; } } else { return null; } } else if (sequence instanceof TableSequence) { TableSequence tableSequence = (TableSequence) sequence; return new TableSequenceDefinition(tableSequence); } else if (sequence instanceof NativeSequence) { if (getSession().getDatasourcePlatform().isOracle()) { NativeSequence nativeSequence = (NativeSequence) sequence; return new OracleSequenceDefinition(nativeSequence); } else { return null; } } else { return null; } } /** * Use the table definition to drop the constraints from the table, this is normally done * in two steps to avoid dependencies. */ public void dropConstraints(TableDefinition tableDefinition) throws TopLinkException { if (shouldWriteToDatabase()) { tableDefinition.dropConstraintsOnDatabase(getSession()); } else { tableDefinition.dropConstraints(getSession(), getDropSchemaWriter()); } } /** * Use the definition object to drop the schema entity from the database. * This is used for droping tables, views, procedures ... etc ... */ public void dropObject(DatabaseObjectDefinition databaseObjectDefinition) throws TopLinkException { if (shouldWriteToDatabase()) { databaseObjectDefinition.dropFromDatabase(getSession()); } else { Writer dropSchemaWriter = getDropSchemaWriter(); databaseObjectDefinition.dropObject(getSession(), dropSchemaWriter); if(createSQLFiles) this.appendToDDLWriter(dropSchemaWriter, getSession().getPlatform().getStoredProcedureTerminationToken()); this.appendToDDLWriter(dropSchemaWriter, "\n"); } } /** * Drop (delete) the table named tableName from the database. */ public void dropTable(String tableName) throws TopLinkException { TableDefinition tableDefinition; tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); dropObject(tableDefinition); } /** * INTERNAL: * Close the schema writer when the schema manger is garbage collected */ public void finalize() { try { this.closeDDLWriter(); } catch (ValidationException exception) { // do nothing } } /** * Return the appropriate accessor. * Assume we are dealing with a JDBC accessor. */ protected DatabaseAccessor getAccessor() { return (DatabaseAccessor) getSession().getAccessor(); } /** * Get a description of table columns available in a catalog. * *

Each column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE short => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name *
  7. COLUMN_SIZE int => column size. For char or date * types this is the maximum number of characters, for numeric or * decimal types this is precision. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed? * *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => "NO" means column definitely * does not allow NULL values; "YES" means the column might * allow NULL values. An empty string means nobody knows. *
* * @param tableName a table name pattern * @return a Vector of DatabaseRows. */ public Vector getAllColumnNames(String tableName) throws DatabaseException { return getAccessor().getColumnInfo(null, null, tableName, null, getSession()); } /** * Get a description of table columns available in a catalog. * *

Each column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE short => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name *
  7. COLUMN_SIZE int => column size. For char or date * types this is the maximum number of characters, for numeric or * decimal types this is precision. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed? * *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => "NO" means column definitely * does not allow NULL values; "YES" means the column might * allow NULL values. An empty string means nobody knows. *
* * @param creatorName a schema name pattern; "" retrieves those * without a schema * @param tableName a table name pattern * @return a Vector of DatabaseRows. */ public Vector getAllColumnNames(String creatorName, String tableName) throws DatabaseException { return getAccessor().getColumnInfo(null, creatorName, tableName, null, getSession()); } /** * Get a description of tables available in a catalog. * *

Each table description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. TABLE_TYPE String => table type. Typical types are "TABLE", * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". *
  5. REMARKS String => explanatory comment on the table *
* *

Note: Some databases may not return information for * all tables. * * @return a Vector of DatabaseRows. */ public Vector getAllTableNames() throws DatabaseException { return getAccessor().getTableInfo(null, null, null, null, getSession()); } /** * Get a description of table columns available in a catalog. * *

Each column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE short => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name *
  7. COLUMN_SIZE int => column size. For char or date * types this is the maximum number of characters, for numeric or * decimal types this is precision. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed? * *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => "NO" means column definitely * does not allow NULL values; "YES" means the column might * allow NULL values. An empty string means nobody knows. *
* * @param creatorName a schema name pattern; "" retrieves those * without a schema * @return a Vector of DatabaseRows. */ public Vector getAllTableNames(String creatorName) throws DatabaseException { return getAccessor().getTableInfo(null, creatorName, null, null, getSession()); } /** * Get a description of table columns available in a catalog. * *

Only column descriptions matching the catalog, schema, table * and column name criteria are returned. They are ordered by * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION. * *

Each column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE short => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name *
  7. COLUMN_SIZE int => column size. For char or date * types this is the maximum number of characters, for numeric or * decimal types this is precision. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed? * *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => "NO" means column definitely * does not allow NULL values; "YES" means the column might * allow NULL values. An empty string means nobody knows. *
* * @param catalog a catalog name; "" retrieves those without a * catalog; null means drop catalog name from the selection criteria * @param schemaPattern a schema name pattern; "" retrieves those * without a schema * @param tableNamePattern a table name pattern * @param columnNamePattern a column name pattern * @return a Vector of DatabaseRows. */ public Vector getColumnInfo(String catalog, String schema, String tableName, String columnName) throws DatabaseException { return getAccessor().getColumnInfo(catalog, schema, tableName, columnName, getSession()); } public AbstractSession getSession() { return session; } /** * Get a description of tables available in a catalog. * *

Only table descriptions matching the catalog, schema, table * name and type criteria are returned. They are ordered by * TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. * *

Each table description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. TABLE_TYPE String => table type. Typical types are "TABLE", * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". *
  5. REMARKS String => explanatory comment on the table *
* *

Note: Some databases may not return information for * all tables. * * @param catalog a catalog name; "" retrieves those without a * catalog; null means drop catalog name from the selection criteria * @param schemaPattern a schema name pattern; "" retrieves those * without a schema * @param tableNamePattern a table name pattern * @param types a list of table types to include; null returns all types * @return a Vector of DatabaseRows. */ public Vector getTableInfo(String catalog, String schema, String tableName, String[] types) throws DatabaseException { return getAccessor().getTableInfo(catalog, schema, tableName, types, getSession()); } /** * PUBLIC: * Output all DDL statements directly to the database. */ public void outputDDLToDatabase() { this.createSchemaWriter = null; this.dropSchemaWriter = null; } /** * PUBLIC: * Output all DDL statements to a file writer specified by the name in the parameter. */ public void outputDDLToFile(String fileName) { try { this.createSchemaWriter = new java.io.FileWriter(fileName); } catch (java.io.IOException ioException) { throw ValidationException.fileError(ioException); } } public void outputCreateDDLToFile(String fileName) { try { this.createSchemaWriter = new java.io.FileWriter(fileName); } catch (java.io.IOException ioException) { throw ValidationException.fileError(ioException); } } public void outputDropDDLToFile(String fileName) { try { this.dropSchemaWriter = new java.io.FileWriter(fileName); } catch (java.io.IOException ioException) { throw ValidationException.fileError(ioException); } } /** * PUBLIC: * Output all DDL statements to a writer specified in the parameter. */ public void outputDDLToWriter(Writer schemaWriter) { this.createSchemaWriter = schemaWriter; } public void outputCreateDDLToWriter(Writer createWriter) { this.createSchemaWriter = createWriter; } public void outputDropDDLToWriter(Writer dropWriter) { this.dropSchemaWriter = dropWriter; } /** * Use the definition object to drop and recreate the schema entity on the database. * This is used for dropping tables, views, procedures ... etc ... * This handles and ignore any database error while droping incase the object did not previously exist. */ public void replaceObject(DatabaseObjectDefinition databaseDefinition) throws TopLinkException { // CR 3870467, do not log stack boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace(); if (shouldLogExceptionStackTrace) { getSession().getSessionLog().setShouldLogExceptionStackTrace(false); } try { dropObject(databaseDefinition); } catch (DatabaseException exception) { // Ignore error } finally { if (shouldLogExceptionStackTrace) { getSession().getSessionLog().setShouldLogExceptionStackTrace(true); } } createObject(databaseDefinition); } /** * Create the default table schema for the TopLink project this session associated with. */ public void createDefaultTables() { //Create each table w/o throwing exception and/or exit if some of them are already existed in the db. //If a table is already existed, skip the creation. Iterator tblDefIter = new DefaultTableGenerator(session.getProject()).generateDefaultTableCreator().getTableDefinitions().iterator(); while (tblDefIter.hasNext()) { TableDefinition tblDef = (TableDefinition) tblDefIter.next(); boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace(); getSession().getSessionLog().setShouldLogExceptionStackTrace(false); try { createObject(tblDef); session.getSessionLog().log(SessionLog.FINEST, "default_tables_created", tblDef.getFullName()); } catch (DatabaseException exception) { // Ignore the exception, table already created session.getSessionLog().log(SessionLog.FINEST, "default_tables_already_existed", tblDef.getFullName()); } finally { getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace); } } //create sequence table createSequences(); } /** * Drop and recreate the default table schema for the TopLink project this session associated with. */ public void replaceDefaultTables() throws TopLinkException { boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace(); getSession().getSessionLog().setShouldLogExceptionStackTrace(false); try { DefaultTableGenerator gen = new DefaultTableGenerator(session.getProject()); gen.generateDefaultTableCreator().replaceTables(session, this); } catch (DatabaseException exception) { // Ignore error } finally { getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace); } } public void setSession(DatabaseSessionImpl session) { this.session = session; } /** * PUBLIC: * Return true if this SchemaManager should write to the database directly */ public boolean shouldWriteToDatabase() { return (this.createSchemaWriter == null && this.dropSchemaWriter == null); } /** * Use the definition to alter sequence. */ public void alterSequence(SequenceDefinition sequenceDefinition) throws TopLinkException { if (!sequenceDefinition.isAlterSupported()) { return; } if (shouldWriteToDatabase()) { sequenceDefinition.alterOnDatabase(getSession()); } else { sequenceDefinition.alter(getSession(), createSchemaWriter); } } public void shouldCreateSQLFiles(boolean genFlag) { this.createSQLFiles = genFlag; } }