0001: /*
0002: * JBoss, Home of Professional Open Source.
0003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
0004: * as indicated by the @author tags. See the copyright.txt file in the
0005: * distribution for a full listing of individual contributors.
0006: *
0007: * This is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU Lesser General Public License as
0009: * published by the Free Software Foundation; either version 2.1 of
0010: * the License, or (at your option) any later version.
0011: *
0012: * This software is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this software; if not, write to the Free
0019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
0021: */
0022: package org.jboss.ejb.plugins.cmp.jdbc;
0023:
0024: import java.sql.Connection;
0025: import java.sql.Statement;
0026: import java.util.Collection;
0027: import java.util.Iterator;
0028: import java.util.ArrayList;
0029: import java.util.HashSet;
0030: import java.util.List;
0031: import java.util.Set;
0032: import javax.sql.DataSource;
0033: import javax.transaction.Transaction;
0034: import javax.transaction.TransactionManager;
0035:
0036: import org.jboss.deployment.DeploymentException;
0037: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
0038: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractCMRFieldBridge;
0039: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge;
0040: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityMetaData;
0041: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
0042: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
0043: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
0044: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
0045: import org.jboss.ejb.plugins.cmp.bridge.EntityBridge;
0046: import org.jboss.logging.Logger;
0047:
0048: /**
0049: * JDBCStartCommand creates the table if specified in xml.
0050: *
0051: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
0052: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
0053: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
0054: * @author <a href="mailto:shevlandj@kpi.com.au">Joe Shevland</a>
0055: * @author <a href="mailto:justin@j-m-f.demon.co.uk">Justin Forder</a>
0056: * @author <a href="mailto:michel.anke@wolmail.nl">Michel de Groot</a>
0057: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
0058: * @author <a href="mailto:heiko.rupp@cellent.de">Heiko W.Rupp</a>
0059: * @author <a href="mailto:joachim@cabsoft.be">Joachim Van der Auwera</a>
0060: * @version $Revision: 63140 $
0061: */
0062: public final class JDBCStartCommand {
0063: private static final String IDX_POSTFIX = "_idx";
0064: private static final String COULDNT_SUSPEND = "Could not suspend current transaction before ";
0065: private static final String COULDNT_REATTACH = "Could not reattach original transaction after ";
0066: private final static Object CREATED_TABLES_KEY = new Object();
0067: private final JDBCEntityPersistenceStore manager;
0068: private final JDBCAbstractEntityBridge entity;
0069: private final JDBCEntityMetaData entityMetaData;
0070: private final Logger log;
0071: private int idxCount = 0;
0072:
0073: public JDBCStartCommand(JDBCEntityPersistenceStore manager) {
0074: this .manager = manager;
0075: entity = manager.getEntityBridge();
0076: entityMetaData = manager.getMetaData();
0077:
0078: // Create the Log
0079: log = Logger.getLogger(this .getClass().getName() + "."
0080: + manager.getMetaData().getName());
0081:
0082: // Start index counter at 1
0083: idxCount = 1;
0084:
0085: // Create the created tables set
0086: Set tables = (Set) manager
0087: .getApplicationData(CREATED_TABLES_KEY);
0088: if (tables == null) {
0089: manager.putApplicationData(CREATED_TABLES_KEY,
0090: new HashSet());
0091: }
0092: }
0093:
0094: public void execute() throws DeploymentException {
0095: Set existedTables = getExistedTables(manager);
0096:
0097: boolean tableExisted = SQLUtil.tableExists(entity
0098: .getQualifiedTableName(), entity.getDataSource());
0099: if (tableExisted) {
0100: existedTables.add(entity.getEntityName());
0101: }
0102:
0103: if (tableExisted) {
0104: if (entityMetaData.getAlterTable()) {
0105: SQLUtil.OldColumns oldColumns = SQLUtil.getOldColumns(
0106: entity.getQualifiedTableName(), entity
0107: .getDataSource());
0108: ArrayList oldNames = oldColumns.getColumnNames();
0109: ArrayList oldTypes = oldColumns.getTypeNames();
0110: ArrayList oldSizes = oldColumns.getColumnSizes();
0111: SQLUtil.OldIndexes oldIndexes = null;
0112: ArrayList newNames = new ArrayList();
0113: JDBCFieldBridge fields[] = entity.getTableFields();
0114: String tableName = entity.getQualifiedTableName();
0115: for (int i = 0; i < fields.length; i++) {
0116: JDBCFieldBridge field = fields[i];
0117: JDBCType jdbcType = field.getJDBCType();
0118: String[] columnNames = jdbcType.getColumnNames();
0119: String[] sqlTypes = jdbcType.getSQLTypes();
0120: boolean[] notNull = jdbcType.getNotNull();
0121:
0122: for (int j = 0; j < columnNames.length; j++) {
0123: String name = columnNames[j];
0124: String ucName = name.toUpperCase();
0125:
0126: newNames.add(ucName);
0127:
0128: int oldIndex = oldNames.indexOf(ucName);
0129: if (oldIndex == -1) {
0130: // add new column
0131: StringBuffer buf = new StringBuffer(
0132: sqlTypes[j]);
0133: if (notNull[j]) {
0134: buf.append(SQLUtil.NOT).append(
0135: SQLUtil.NULL);
0136: }
0137: alterTable(entity.getDataSource(),
0138: entityMetaData.getTypeMapping()
0139: .getAddColumnTemplate(),
0140: tableName, name, buf.toString());
0141: } else {
0142: // alter existing columns
0143: // only CHAR and VARCHAR fields are altered, and only when they are longer then before
0144: String type = (String) oldTypes
0145: .get(oldIndex);
0146: if (type.equals("CHAR")
0147: || type.equals("VARCHAR")) {
0148: try {
0149: // get new length
0150: String l = sqlTypes[j];
0151: l = l.substring(l.indexOf('(') + 1,
0152: l.length() - 1);
0153: Integer oldLength = (Integer) oldSizes
0154: .get(oldIndex);
0155: if (Integer.parseInt(l) > oldLength
0156: .intValue()) {
0157: alterTable(
0158: entity.getDataSource(),
0159: entityMetaData
0160: .getTypeMapping()
0161: .getAlterColumnTemplate(),
0162: tableName, name,
0163: sqlTypes[j]);
0164: }
0165: } catch (Exception e) {
0166: log.warn("EXCEPTION ALTER :"
0167: + e.toString());
0168: }
0169: }
0170: }
0171: }
0172:
0173: // see if we have to add an index for the field
0174: JDBCCMPFieldMetaData fieldMD = entity.getMetaData()
0175: .getCMPFieldByName(field.getFieldName());
0176: if (fieldMD != null && fieldMD.isIndexed()) {
0177: if (oldIndexes == null) {
0178: oldIndexes = SQLUtil.getOldIndexes(entity
0179: .getQualifiedTableName(), entity
0180: .getDataSource());
0181: idxCount = oldIndexes.getIndexNames()
0182: .size();
0183: }
0184: if (!hasIndex(oldIndexes, field)) {
0185: createCMPIndex(entity.getDataSource(),
0186: field, oldIndexes.getIndexNames());
0187: }
0188:
0189: }
0190: } // for int i;
0191:
0192: // delete old columns
0193: Iterator it = oldNames.iterator();
0194: while (it.hasNext()) {
0195: String name = (String) (it.next());
0196: if (!newNames.contains(name)) {
0197: alterTable(entity.getDataSource(),
0198: entityMetaData.getTypeMapping()
0199: .getDropColumnTemplate(),
0200: tableName, name, "");
0201: }
0202: }
0203:
0204: }
0205: }
0206:
0207: // Create table if necessary
0208: Set createdTables = getCreatedTables(manager);
0209:
0210: if (entityMetaData.getCreateTable()
0211: && !createdTables.contains(entity.getEntityName())) {
0212: DataSource dataSource = entity.getDataSource();
0213: createTable(dataSource, entity.getQualifiedTableName(),
0214: getEntityCreateTableSQL(dataSource));
0215:
0216: // create indices only if table did not yet exist.
0217: if (!tableExisted) {
0218: createCMPIndices(dataSource, SQLUtil.getOldIndexes(
0219: entity.getQualifiedTableName(),
0220: entity.getDataSource()).getIndexNames());
0221: } else {
0222: if (log.isDebugEnabled()) {
0223: log.debug("Indices for table "
0224: + entity.getQualifiedTableName()
0225: + "not created as table existed");
0226: }
0227: }
0228:
0229: // issue extra (user-defined) sql for table
0230: if (!tableExisted) {
0231: issuePostCreateSQL(dataSource, entity.getMetaData()
0232: .getDefaultTablePostCreateCmd(), entity
0233: .getQualifiedTableName());
0234: }
0235:
0236: createdTables.add(entity.getEntityName());
0237: } else {
0238: log.debug("Table not create as requested: "
0239: + entity.getQualifiedTableName());
0240: }
0241:
0242: // create relation tables
0243: JDBCAbstractCMRFieldBridge[] cmrFields = entity.getCMRFields();
0244: for (int i = 0; i < cmrFields.length; ++i) {
0245: JDBCAbstractCMRFieldBridge cmrField = cmrFields[i];
0246: JDBCRelationMetaData relationMetaData = cmrField
0247: .getMetaData().getRelationMetaData();
0248:
0249: // if the table for the related entity has been created
0250: final EntityBridge relatedEntity = cmrField
0251: .getRelatedEntity();
0252: if (relationMetaData.isTableMappingStyle()
0253: && createdTables.contains(relatedEntity
0254: .getEntityName())) {
0255: DataSource dataSource = relationMetaData
0256: .getDataSource();
0257:
0258: boolean relTableExisted = SQLUtil.tableExists(cmrField
0259: .getQualifiedTableName(), entity
0260: .getDataSource());
0261:
0262: if (relTableExisted) {
0263: if (relationMetaData.getAlterTable()) {
0264: ArrayList oldNames = SQLUtil.getOldColumns(
0265: cmrField.getQualifiedTableName(),
0266: dataSource).getColumnNames();
0267: ArrayList newNames = new ArrayList();
0268: JDBCFieldBridge[] leftKeys = cmrField
0269: .getTableKeyFields();
0270: JDBCFieldBridge[] rightKeys = cmrField
0271: .getRelatedCMRField()
0272: .getTableKeyFields();
0273: JDBCFieldBridge[] fields = new JDBCFieldBridge[leftKeys.length
0274: + rightKeys.length];
0275: System.arraycopy(leftKeys, 0, fields, 0,
0276: leftKeys.length);
0277: System.arraycopy(rightKeys, 0, fields,
0278: leftKeys.length, rightKeys.length);
0279: // have to append field names to leftKeys, rightKeys...
0280:
0281: boolean different = false;
0282: for (int j = 0; j < fields.length; j++) {
0283: JDBCFieldBridge field = fields[j];
0284:
0285: String name = field.getJDBCType()
0286: .getColumnNames()[0].toUpperCase();
0287: newNames.add(name);
0288:
0289: if (!oldNames.contains(name)) {
0290: different = true;
0291: break;
0292: }
0293: } // for int j;
0294:
0295: if (!different) {
0296: Iterator it = oldNames.iterator();
0297: while (it.hasNext()) {
0298: String name = (String) (it.next());
0299: if (!newNames.contains(name)) {
0300: different = true;
0301: break;
0302: }
0303: }
0304: }
0305:
0306: if (different) {
0307: // only log, don't drop table is this can cause data loss
0308: log
0309: .error("CMR table structure is incorrect for "
0310: + cmrField
0311: .getQualifiedTableName());
0312: //SQLUtil.dropTable(entity.getDataSource(), cmrField.getQualifiedTableName());
0313: }
0314:
0315: } // if alter-table
0316:
0317: } // if existed
0318:
0319: // create the relation table
0320: if (relationMetaData.isTableMappingStyle()
0321: && !relationMetaData.isTableCreated()) {
0322: if (relationMetaData.getCreateTable()) {
0323: createTable(dataSource, cmrField
0324: .getQualifiedTableName(),
0325: getRelationCreateTableSQL(cmrField,
0326: dataSource));
0327: } else {
0328: log
0329: .debug("Relation table not created as requested: "
0330: + cmrField
0331: .getQualifiedTableName());
0332: }
0333: // create Indices if needed
0334: createCMRIndex(dataSource, cmrField);
0335:
0336: if (relationMetaData.getCreateTable()) {
0337: issuePostCreateSQL(dataSource, relationMetaData
0338: .getDefaultTablePostCreateCmd(),
0339: cmrField.getQualifiedTableName());
0340: }
0341: }
0342: }
0343: }
0344: }
0345:
0346: public void addForeignKeyConstraints() throws DeploymentException {
0347: // Create table if necessary
0348: Set createdTables = getCreatedTables(manager);
0349:
0350: JDBCAbstractCMRFieldBridge[] cmrFields = entity.getCMRFields();
0351: for (int i = 0; i < cmrFields.length; ++i) {
0352: JDBCAbstractCMRFieldBridge cmrField = cmrFields[i];
0353: JDBCRelationMetaData relationMetaData = cmrField
0354: .getMetaData().getRelationMetaData();
0355:
0356: // if the table for the related entity has been created
0357: final EntityBridge relatedEntity = cmrField
0358: .getRelatedEntity();
0359:
0360: // Only generate indices on foreign key columns if
0361: // the table was freshly created. If not, we risk
0362: // creating an index twice and get an exception from the DB
0363: if (relationMetaData.isForeignKeyMappingStyle()
0364: && createdTables.contains(relatedEntity
0365: .getEntityName())) {
0366: createCMRIndex(
0367: ((JDBCAbstractEntityBridge) relatedEntity)
0368: .getDataSource(), cmrField);
0369: }
0370:
0371: // Create my fk constraint
0372: addForeignKeyConstraint(cmrField);
0373: }
0374: }
0375:
0376: public static Set getCreatedTables(
0377: JDBCEntityPersistenceStore manager) {
0378: final String key = "CREATED_TABLES";
0379: Set createdTables = (Set) manager.getApplicationData(key);
0380: if (createdTables == null) {
0381: createdTables = new HashSet();
0382: manager.putApplicationData(key, createdTables);
0383: }
0384: return createdTables;
0385: }
0386:
0387: public static Set getExistedTables(
0388: JDBCEntityPersistenceStore manager) {
0389: final String key = "EXISTED_TABLES";
0390: Set existedTables = (Set) manager.getApplicationData(key);
0391: if (existedTables == null) {
0392: existedTables = new HashSet();
0393: manager.putApplicationData(key, existedTables);
0394: }
0395: return existedTables;
0396: }
0397:
0398: /**
0399: * Check whether a required index already exists on a table
0400: *
0401: * @param oldIndexes list of existing indexes
0402: * @param field field we test the existence of an index for
0403: * @return True if the field has an index; otherwise false
0404: */
0405: private boolean hasIndex(SQLUtil.OldIndexes oldIndexes,
0406: JDBCFieldBridge field) {
0407: JDBCType jdbcType = field.getJDBCType();
0408: String[] columns = jdbcType.getColumnNames();
0409: ArrayList idxNames = oldIndexes.getIndexNames();
0410: ArrayList idxColumns = oldIndexes.getColumnNames();
0411:
0412: // check if the columns are in the same index
0413: String indexName = null;
0414: for (int i = 0; i < columns.length; ++i) {
0415: String column = columns[i];
0416: int index = columnIndex(idxColumns, column);
0417: if (index == -1) {
0418: return false;
0419: }
0420:
0421: if (indexName == null) {
0422: indexName = (String) idxNames.get(index);
0423: } else if (!indexName.equals(idxNames.get(index))) {
0424: return false;
0425: }
0426: }
0427:
0428: return true;
0429: /*
0430: previous implementation
0431: ArrayList idxAscDesc = oldIndexes.getColumnAscDesc();
0432:
0433: // search for for column in index
0434: if(idxAscDesc != null)
0435: {
0436: for(int i = 0; i < idxColumns.size(); i++)
0437: {
0438: // only match ascending columns
0439: if("A".equals(idxAscDesc.get(i)))
0440: {
0441: String name = columns[0];
0442: String testCol = (String) idxColumns.get(i);
0443: if(testCol.charAt(0) == '\"' &&
0444: testCol.charAt(testCol.charAt(testCol.length() - 1)) == '\"')
0445: {
0446: testCol = testCol.substring(1, testCol.length() - 1);
0447: }
0448:
0449: if(testCol.equalsIgnoreCase(name))
0450: {
0451: // first column matches, now check the others
0452: String idxName = (String) idxNames.get(i);
0453: int j = 1;
0454: for(; j < columns.length; j++)
0455: {
0456: name = columns[j];
0457: testCol = (String) idxColumns.get(i + j);
0458: if(testCol.charAt(0) == '\"' &&
0459: testCol.charAt(testCol.charAt(testCol.length() - 1)) == '\"')
0460: {
0461: testCol = testCol.substring(1, testCol.length() - 1);
0462: }
0463:
0464: String testName = (String) idxNames.get(i + j);
0465: if(!(testName.equals(idxName)
0466: &&
0467: testCol.equalsIgnoreCase(name)
0468: && idxAscDesc.get(i + j).equals("A")))
0469: {
0470: break;
0471: }
0472: }
0473: // if they all matched -> found
0474: if(j == columns.length) return true;
0475: }
0476: }
0477: }
0478: }
0479: return false;
0480: */
0481: }
0482:
0483: private int columnIndex(ArrayList idxColumns, String column) {
0484: for (int j = 0; j < idxColumns.size(); ++j) {
0485: String idxColumn = (String) idxColumns.get(j);
0486: idxColumn = idxColumn.trim();
0487: while (idxColumn.charAt(0) == '\"'
0488: && idxColumn.charAt(idxColumn.charAt(idxColumn
0489: .length() - 1)) == '\"') {
0490: idxColumn = idxColumn.substring(1,
0491: idxColumn.length() - 1);
0492: }
0493:
0494: if (idxColumn.equalsIgnoreCase(column)) {
0495: return j;
0496: }
0497: }
0498: return -1;
0499: }
0500:
0501: private void alterTable(DataSource dataSource,
0502: JDBCFunctionMappingMetaData mapping, String tableName,
0503: String fieldName, String fieldStructure)
0504: throws DeploymentException {
0505: StringBuffer sqlBuf = new StringBuffer();
0506: mapping.getFunctionSql(new String[] { tableName, fieldName,
0507: fieldStructure }, sqlBuf);
0508: String sql = sqlBuf.toString();
0509:
0510: log.warn(sql);
0511:
0512: // suspend the current transaction
0513: TransactionManager tm = manager.getContainer()
0514: .getTransactionManager();
0515: Transaction oldTransaction;
0516: try {
0517: oldTransaction = tm.suspend();
0518: } catch (Exception e) {
0519: throw new DeploymentException(COULDNT_SUSPEND
0520: + " alter table.", e);
0521: }
0522:
0523: try {
0524: Connection con = null;
0525: Statement statement = null;
0526: try {
0527: con = dataSource.getConnection();
0528: statement = con.createStatement();
0529: statement.executeUpdate(sql);
0530: } finally {
0531: // make sure to close the connection and statement before
0532: // comitting the transaction or XA will break
0533: JDBCUtil.safeClose(statement);
0534: JDBCUtil.safeClose(con);
0535: }
0536: } catch (Exception e) {
0537: log.error("Could not alter table " + tableName + ": "
0538: + e.getMessage());
0539: throw new DeploymentException("Error while alter table "
0540: + tableName + " " + sql, e);
0541: } finally {
0542: try {
0543: // resume the old transaction
0544: if (oldTransaction != null) {
0545: tm.resume(oldTransaction);
0546: }
0547: } catch (Exception e) {
0548: throw new DeploymentException(COULDNT_REATTACH
0549: + "alter table");
0550: }
0551: }
0552:
0553: // success
0554: if (log.isDebugEnabled())
0555: log.debug("Table altered successfully.");
0556: }
0557:
0558: private void createTable(DataSource dataSource, String tableName,
0559: String sql) throws DeploymentException {
0560: // does this table already exist
0561: if (SQLUtil.tableExists(tableName, dataSource)) {
0562: log.debug("Table '" + tableName + "' already exists");
0563: return;
0564: }
0565:
0566: // since we use the pools, we have to do this within a transaction
0567:
0568: // suspend the current transaction
0569: TransactionManager tm = manager.getContainer()
0570: .getTransactionManager();
0571: Transaction oldTransaction;
0572: try {
0573: oldTransaction = tm.suspend();
0574: } catch (Exception e) {
0575: throw new DeploymentException(COULDNT_SUSPEND
0576: + "creating table.", e);
0577: }
0578:
0579: try {
0580: Connection con = null;
0581: Statement statement = null;
0582: try {
0583: // execute sql
0584: if (log.isDebugEnabled()) {
0585: log.debug("Executing SQL: " + sql);
0586: }
0587:
0588: con = dataSource.getConnection();
0589: statement = con.createStatement();
0590: statement.executeUpdate(sql);
0591: } finally {
0592: // make sure to close the connection and statement before
0593: // comitting the transaction or XA will break
0594: JDBCUtil.safeClose(statement);
0595: JDBCUtil.safeClose(con);
0596: }
0597: } catch (Exception e) {
0598: log.debug("Could not create table " + tableName);
0599: throw new DeploymentException("Error while creating table "
0600: + tableName, e);
0601: } finally {
0602: try {
0603: // resume the old transaction
0604: if (oldTransaction != null) {
0605: tm.resume(oldTransaction);
0606: }
0607: } catch (Exception e) {
0608: throw new DeploymentException(COULDNT_REATTACH
0609: + "create table");
0610: }
0611: }
0612:
0613: // success
0614: Set createdTables = (Set) manager
0615: .getApplicationData(CREATED_TABLES_KEY);
0616: createdTables.add(tableName);
0617: }
0618:
0619: /**
0620: * Create an index on a field. Does the create
0621: *
0622: * @param dataSource
0623: * @param tableName In which table is the index?
0624: * @param indexName Which is the index?
0625: * @param sql The SQL statement to issue
0626: * @throws DeploymentException
0627: */
0628: private void createIndex(DataSource dataSource, String tableName,
0629: String indexName, String sql) throws DeploymentException {
0630: // we are only called directly after creating a table
0631: // since we use the pools, we have to do this within a transaction
0632: // suspend the current transaction
0633: TransactionManager tm = manager.getContainer()
0634: .getTransactionManager();
0635: Transaction oldTransaction;
0636: try {
0637: oldTransaction = tm.suspend();
0638: } catch (Exception e) {
0639: throw new DeploymentException(COULDNT_SUSPEND
0640: + "creating index.", e);
0641: }
0642:
0643: try {
0644: Connection con = null;
0645: Statement statement = null;
0646: try {
0647: // execute sql
0648: if (log.isDebugEnabled()) {
0649: log.debug("Executing SQL: " + sql);
0650: }
0651: con = dataSource.getConnection();
0652: statement = con.createStatement();
0653: statement.executeUpdate(sql);
0654: } finally {
0655: // make sure to close the connection and statement before
0656: // comitting the transaction or XA will break
0657: JDBCUtil.safeClose(statement);
0658: JDBCUtil.safeClose(con);
0659: }
0660: } catch (Exception e) {
0661: log.debug("Could not create index " + indexName
0662: + "on table" + tableName);
0663: throw new DeploymentException("Error while creating table",
0664: e);
0665: } finally {
0666: try {
0667: // resume the old transaction
0668: if (oldTransaction != null) {
0669: tm.resume(oldTransaction);
0670: }
0671: } catch (Exception e) {
0672: throw new DeploymentException(COULDNT_REATTACH
0673: + "create index");
0674: }
0675: }
0676: }
0677:
0678: /**
0679: * Send (user-defined) SQL commands to the server.
0680: * The commands can be found in the <sql-statement> elements
0681: * within the <post-table-create> tag in jbossjdbc-cmp.xml
0682: *
0683: * @param dataSource
0684: */
0685: private void issuePostCreateSQL(DataSource dataSource, List sql,
0686: String table) throws DeploymentException {
0687: if (sql == null) { // no work to do.
0688: log.trace("issuePostCreateSQL: sql is null");
0689: return;
0690: }
0691:
0692: log.debug("issuePostCreateSQL::sql: " + sql.toString()
0693: + " on table " + table);
0694:
0695: TransactionManager tm = manager.getContainer()
0696: .getTransactionManager();
0697: Transaction oldTransaction;
0698:
0699: try {
0700: oldTransaction = tm.suspend();
0701: } catch (Exception e) {
0702: throw new DeploymentException(COULDNT_SUSPEND
0703: + "sending sql command.", e);
0704: }
0705:
0706: String currentCmd = "";
0707:
0708: try {
0709: Connection con = null;
0710: Statement statement = null;
0711: try {
0712: con = dataSource.getConnection();
0713: statement = con.createStatement();
0714:
0715: // execute sql
0716: for (int i = 0; i < sql.size(); i++) {
0717: currentCmd = (String) sql.get(i);
0718: /*
0719: * Replace %%t in the sql command with the current table name
0720: */
0721: currentCmd = replaceTable(currentCmd, table);
0722: currentCmd = replaceIndexCounter(currentCmd);
0723: log.debug("Executing SQL: " + currentCmd);
0724: statement.executeUpdate(currentCmd);
0725: }
0726: } finally {
0727: // make sure to close the connection and statement before
0728: // comitting the transaction or XA will break
0729: JDBCUtil.safeClose(statement);
0730: JDBCUtil.safeClose(con);
0731: }
0732: } catch (Exception e) {
0733: log.warn("Issuing sql " + currentCmd + " failed: "
0734: + e.toString());
0735: throw new DeploymentException(
0736: "Error while issuing sql in post-table-create", e);
0737: } finally {
0738: try {
0739: // resume the old transaction
0740: if (oldTransaction != null) {
0741: tm.resume(oldTransaction);
0742: }
0743: } catch (Exception e) {
0744: throw new DeploymentException(COULDNT_REATTACH
0745: + "create index");
0746: }
0747: }
0748:
0749: // success
0750: log.debug("Issued SQL " + sql + " successfully.");
0751: }
0752:
0753: private String getEntityCreateTableSQL(DataSource dataSource)
0754: throws DeploymentException {
0755: StringBuffer sql = new StringBuffer();
0756: sql.append(SQLUtil.CREATE_TABLE).append(
0757: entity.getQualifiedTableName()).append(" (");
0758:
0759: // add fields
0760: boolean comma = false;
0761: JDBCFieldBridge[] fields = entity.getTableFields();
0762: for (int i = 0; i < fields.length; ++i) {
0763: JDBCFieldBridge field = fields[i];
0764: JDBCType type = field.getJDBCType();
0765: if (comma) {
0766: sql.append(SQLUtil.COMMA);
0767: } else {
0768: comma = true;
0769: }
0770: addField(type, sql);
0771: }
0772:
0773: // add a pk constraint
0774: if (entityMetaData.hasPrimaryKeyConstraint()) {
0775: JDBCFunctionMappingMetaData pkConstraint = manager
0776: .getMetaData().getTypeMapping()
0777: .getPkConstraintTemplate();
0778: if (pkConstraint == null) {
0779: throw new IllegalStateException(
0780: "Primary key constraint is "
0781: + "not allowed for this type of data source");
0782: }
0783:
0784: String defTableName = entity.getManager().getMetaData()
0785: .getDefaultTableName();
0786: String name = "pk_"
0787: + SQLUtil.unquote(defTableName, dataSource);
0788: name = SQLUtil.fixConstraintName(name, dataSource);
0789: String[] args = new String[] {
0790: name,
0791: SQLUtil.getColumnNamesClause(
0792: entity.getPrimaryKeyFields(),
0793: new StringBuffer(100)).toString() };
0794: sql.append(SQLUtil.COMMA);
0795: pkConstraint.getFunctionSql(args, sql);
0796: }
0797:
0798: return sql.append(')').toString();
0799: }
0800:
0801: /**
0802: * Create indices for the fields in the table that have a
0803: * <dbindex> tag in jbosscmp-jdbc.xml
0804: *
0805: * @param dataSource
0806: * @throws DeploymentException
0807: */
0808: private void createCMPIndices(DataSource dataSource,
0809: ArrayList indexNames) throws DeploymentException {
0810: // Only create indices on CMP fields
0811: JDBCFieldBridge[] cmpFields = entity.getTableFields();
0812: for (int i = 0; i < cmpFields.length; ++i) {
0813: JDBCFieldBridge field = cmpFields[i];
0814: JDBCCMPFieldMetaData fieldMD = entity.getMetaData()
0815: .getCMPFieldByName(field.getFieldName());
0816:
0817: if (fieldMD != null && fieldMD.isIndexed()) {
0818: createCMPIndex(dataSource, field, indexNames);
0819: }
0820: }
0821:
0822: final JDBCAbstractCMRFieldBridge[] cmrFields = entity
0823: .getCMRFields();
0824: if (cmrFields != null) {
0825: for (int i = 0; i < cmrFields.length; ++i) {
0826: JDBCAbstractCMRFieldBridge cmrField = cmrFields[i];
0827: if (cmrField.getRelatedCMRField().getMetaData()
0828: .isIndexed()) {
0829: final JDBCFieldBridge[] fkFields = cmrField
0830: .getForeignKeyFields();
0831: if (fkFields != null) {
0832: for (int fkInd = 0; fkInd < fkFields.length; ++fkInd) {
0833: createCMPIndex(dataSource, fkFields[fkInd],
0834: indexNames);
0835: }
0836: }
0837: }
0838: }
0839: }
0840: }
0841:
0842: /**
0843: * Create indix for one specific field
0844: *
0845: * @param dataSource
0846: * @param field to create index for
0847: * @throws DeploymentException
0848: */
0849: private void createCMPIndex(DataSource dataSource,
0850: JDBCFieldBridge field, ArrayList indexNames)
0851: throws DeploymentException {
0852: StringBuffer sql;
0853: log.debug("Creating index for field " + field.getFieldName());
0854: sql = new StringBuffer();
0855: sql.append(SQLUtil.CREATE_INDEX);
0856: String indexName;
0857: boolean indexExists;
0858: do {
0859: indexName = entity.getQualifiedTableName() + IDX_POSTFIX
0860: + idxCount;
0861: idxCount++;
0862: indexExists = false;
0863: if (indexNames != null) {
0864: for (int i = 0; i < indexNames.size() && !indexExists; i++) {
0865: indexExists = indexName
0866: .equalsIgnoreCase(((String) indexNames
0867: .get(i)));
0868: }
0869: }
0870: } while (indexExists);
0871:
0872: sql.append(indexName);
0873: sql.append(SQLUtil.ON);
0874: sql.append(entity.getQualifiedTableName() + " (");
0875: SQLUtil.getColumnNamesClause(field, sql);
0876: sql.append(")");
0877:
0878: createIndex(dataSource, entity.getQualifiedTableName(),
0879: indexName, sql.toString());
0880: }
0881:
0882: private void createCMRIndex(DataSource dataSource,
0883: JDBCAbstractCMRFieldBridge field)
0884: throws DeploymentException {
0885: JDBCRelationMetaData rmd;
0886: String tableName;
0887:
0888: rmd = field.getMetaData().getRelationMetaData();
0889:
0890: if (rmd.isTableMappingStyle()) {
0891: tableName = rmd.getDefaultTableName();
0892: createFKIndex(rmd.getLeftRelationshipRole(), dataSource,
0893: tableName);
0894: createFKIndex(rmd.getRightRelationshipRole(), dataSource,
0895: tableName);
0896: } else if (field.hasForeignKey()) {
0897: tableName = field.getEntity().getQualifiedTableName();
0898: createFKIndex(field.getRelatedCMRField().getMetaData(),
0899: dataSource, tableName);
0900: }
0901: }
0902:
0903: private void createFKIndex(JDBCRelationshipRoleMetaData metadata,
0904: DataSource dataSource, String tableName)
0905: throws DeploymentException {
0906: Collection kfl = metadata.getKeyFields();
0907: Iterator it = kfl.iterator();
0908: while (it.hasNext()) {
0909: JDBCCMPFieldMetaData fi = (JDBCCMPFieldMetaData) it.next();
0910: if (metadata.isIndexed()) {
0911: createIndex(dataSource, tableName, fi.getFieldName(),
0912: createIndexSQL(fi, tableName));
0913: idxCount++;
0914: }
0915: }
0916: }
0917:
0918: private String createIndexSQL(JDBCCMPFieldMetaData fi,
0919: String tableName) {
0920: StringBuffer sql = new StringBuffer();
0921: sql.append(SQLUtil.CREATE_INDEX);
0922: sql.append(tableName + IDX_POSTFIX + idxCount);
0923: sql.append(SQLUtil.ON);
0924: sql.append(tableName + " (");
0925: sql.append(fi.getColumnName());
0926: sql.append(')');
0927: return sql.toString();
0928: }
0929:
0930: private void addField(JDBCType type, StringBuffer sqlBuffer)
0931: throws DeploymentException {
0932: // apply auto-increment template
0933: if (type.getAutoIncrement()[0]) {
0934: String columnClause = SQLUtil
0935: .getCreateTableColumnsClause(type);
0936: JDBCFunctionMappingMetaData autoIncrement = manager
0937: .getMetaData().getTypeMapping()
0938: .getAutoIncrementTemplate();
0939: if (autoIncrement == null) {
0940: throw new IllegalStateException(
0941: "auto-increment template not found");
0942: }
0943: String[] args = new String[] { columnClause };
0944: autoIncrement.getFunctionSql(args, sqlBuffer);
0945: } else {
0946: sqlBuffer.append(SQLUtil.getCreateTableColumnsClause(type));
0947: }
0948: }
0949:
0950: private String getRelationCreateTableSQL(
0951: JDBCAbstractCMRFieldBridge cmrField, DataSource dataSource)
0952: throws DeploymentException {
0953: JDBCFieldBridge[] leftKeys = cmrField.getTableKeyFields();
0954: JDBCFieldBridge[] rightKeys = cmrField.getRelatedCMRField()
0955: .getTableKeyFields();
0956: JDBCFieldBridge[] fieldsArr = new JDBCFieldBridge[leftKeys.length
0957: + rightKeys.length];
0958: System.arraycopy(leftKeys, 0, fieldsArr, 0, leftKeys.length);
0959: System.arraycopy(rightKeys, 0, fieldsArr, leftKeys.length,
0960: rightKeys.length);
0961:
0962: StringBuffer sql = new StringBuffer();
0963: sql.append(SQLUtil.CREATE_TABLE).append(
0964: cmrField.getQualifiedTableName()).append(" (")
0965: // add field declaration
0966: .append(SQLUtil.getCreateTableColumnsClause(fieldsArr));
0967:
0968: // add a pk constraint
0969: final JDBCRelationMetaData relationMetaData = cmrField
0970: .getMetaData().getRelationMetaData();
0971: if (relationMetaData.hasPrimaryKeyConstraint()) {
0972: JDBCFunctionMappingMetaData pkConstraint = manager
0973: .getMetaData().getTypeMapping()
0974: .getPkConstraintTemplate();
0975: if (pkConstraint == null) {
0976: throw new IllegalStateException(
0977: "Primary key constraint is not allowed for this type of data store");
0978: }
0979:
0980: String name = "pk_"
0981: + relationMetaData.getDefaultTableName();
0982: name = SQLUtil.fixConstraintName(name, dataSource);
0983: String[] args = new String[] {
0984: name,
0985: SQLUtil.getColumnNamesClause(fieldsArr,
0986: new StringBuffer(100).toString(),
0987: new StringBuffer()).toString() };
0988: sql.append(SQLUtil.COMMA);
0989: pkConstraint.getFunctionSql(args, sql);
0990: }
0991: sql.append(')');
0992: return sql.toString();
0993: }
0994:
0995: private void addForeignKeyConstraint(
0996: JDBCAbstractCMRFieldBridge cmrField)
0997: throws DeploymentException {
0998: JDBCRelationshipRoleMetaData metaData = cmrField.getMetaData();
0999: if (metaData.hasForeignKeyConstraint()) {
1000: if (metaData.getRelationMetaData().isTableMappingStyle()) {
1001: addForeignKeyConstraint(metaData.getRelationMetaData()
1002: .getDataSource(), cmrField
1003: .getQualifiedTableName(), cmrField
1004: .getFieldName(), cmrField.getTableKeyFields(),
1005: cmrField.getEntity().getQualifiedTableName(),
1006: cmrField.getEntity().getPrimaryKeyFields());
1007:
1008: } else if (cmrField.hasForeignKey()) {
1009: JDBCAbstractEntityBridge relatedEntity = (JDBCAbstractEntityBridge) cmrField
1010: .getRelatedEntity();
1011: addForeignKeyConstraint(cmrField.getEntity()
1012: .getDataSource(), cmrField.getEntity()
1013: .getQualifiedTableName(), cmrField
1014: .getFieldName(),
1015: cmrField.getForeignKeyFields(), relatedEntity
1016: .getQualifiedTableName(), relatedEntity
1017: .getPrimaryKeyFields());
1018: }
1019: } else {
1020: log
1021: .debug("Foreign key constraint not added as requested: relationshipRolename="
1022: + metaData.getRelationshipRoleName());
1023: }
1024: }
1025:
1026: private void addForeignKeyConstraint(DataSource dataSource,
1027: String tableName, String cmrFieldName,
1028: JDBCFieldBridge[] fields, String referencesTableName,
1029: JDBCFieldBridge[] referencesFields)
1030: throws DeploymentException {
1031: // can only alter tables we created
1032: Set createdTables = (Set) manager
1033: .getApplicationData(CREATED_TABLES_KEY);
1034: if (!createdTables.contains(tableName)) {
1035: return;
1036: }
1037:
1038: JDBCFunctionMappingMetaData fkConstraint = manager
1039: .getMetaData().getTypeMapping()
1040: .getFkConstraintTemplate();
1041: if (fkConstraint == null) {
1042: throw new IllegalStateException(
1043: "Foreign key constraint is not allowed for this type of datastore");
1044: }
1045: String a = SQLUtil.getColumnNamesClause(fields,
1046: new StringBuffer(50)).toString();
1047: String b = SQLUtil.getColumnNamesClause(referencesFields,
1048: new StringBuffer(50)).toString();
1049:
1050: String[] args = new String[] {
1051: tableName,
1052: SQLUtil.fixConstraintName("fk_" + tableName + "_"
1053: + cmrFieldName, dataSource), a,
1054: referencesTableName, b };
1055:
1056: String sql = fkConstraint.getFunctionSql(args,
1057: new StringBuffer(100)).toString();
1058:
1059: // since we use the pools, we have to do this within a transaction
1060: // suspend the current transaction
1061: TransactionManager tm = manager.getContainer()
1062: .getTransactionManager();
1063: Transaction oldTransaction;
1064: try {
1065: oldTransaction = tm.suspend();
1066: } catch (Exception e) {
1067: throw new DeploymentException(COULDNT_SUSPEND
1068: + "alter table create foreign key.", e);
1069: }
1070:
1071: try {
1072: Connection con = null;
1073: Statement statement = null;
1074: try {
1075: if (log.isDebugEnabled()) {
1076: log.debug("Executing SQL: " + sql);
1077: }
1078: con = dataSource.getConnection();
1079: statement = con.createStatement();
1080: statement.executeUpdate(sql);
1081: } finally {
1082: // make sure to close the connection and statement before
1083: // comitting the transaction or XA will break
1084: JDBCUtil.safeClose(statement);
1085: JDBCUtil.safeClose(con);
1086: }
1087: } catch (Exception e) {
1088: log.warn("Could not add foreign key constraint: table="
1089: + tableName);
1090: throw new DeploymentException(
1091: "Error while adding foreign key constraint", e);
1092: } finally {
1093: try {
1094: // resume the old transaction
1095: if (oldTransaction != null) {
1096: tm.resume(oldTransaction);
1097: }
1098: } catch (Exception e) {
1099: throw new DeploymentException(COULDNT_REATTACH
1100: + "create table");
1101: }
1102: }
1103: }
1104:
1105: /**
1106: * Replace %%t in the sql command with the current table name
1107: *
1108: * @param in sql statement with possible %%t to substitute with table name
1109: * @param table the table name
1110: * @return String with sql statement
1111: */
1112: private static String replaceTable(String in, String table) {
1113: int pos;
1114:
1115: pos = in.indexOf("%%t");
1116: // No %%t -> return input
1117: if (pos == -1) {
1118: return in;
1119: }
1120:
1121: String first = in.substring(0, pos);
1122: String last = in.substring(pos + 3);
1123:
1124: return first + table + last;
1125: }
1126:
1127: /**
1128: * Replace %%n in the sql command with a running (index) number
1129: *
1130: * @param in
1131: * @return
1132: */
1133: private String replaceIndexCounter(String in) {
1134: int pos;
1135:
1136: pos = in.indexOf("%%n");
1137: // No %%n -> return input
1138: if (pos == -1) {
1139: return in;
1140: }
1141:
1142: String first = in.substring(0, pos);
1143: String last = in.substring(pos + 3);
1144: idxCount++;
1145: return first + idxCount + last;
1146: }
1147: }
|