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.jdbc2.schema;
0023:
0024: import org.jboss.deployment.DeploymentException;
0025: import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
0026: import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
0027: import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
0028: import org.jboss.ejb.plugins.cmp.jdbc.JDBCTypeFactory;
0029: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractCMRFieldBridge;
0030: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityMetaData;
0031: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
0032: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCTypeMappingMetaData;
0033: import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCEntityBridge2;
0034: import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMPFieldBridge2;
0035: import org.jboss.logging.Logger;
0036: import org.jboss.mx.util.MBeanServerLocator;
0037: import org.jboss.mx.util.MBeanProxyExt;
0038: import org.jboss.system.ServiceControllerMBean;
0039: import org.jboss.system.Registry;
0040: import org.jboss.metadata.ConfigurationMetaData;
0041: import org.jboss.metadata.MetaData;
0042: import org.jboss.metadata.EntityMetaData;
0043: import org.jboss.cache.invalidation.InvalidationManagerMBean;
0044: import org.jboss.cache.invalidation.InvalidationGroup;
0045: import org.w3c.dom.Element;
0046:
0047: import javax.naming.InitialContext;
0048: import javax.naming.NamingException;
0049: import javax.sql.DataSource;
0050: import javax.ejb.DuplicateKeyException;
0051: import javax.ejb.EJBException;
0052: import javax.ejb.NoSuchEntityException;
0053: import javax.ejb.NoSuchObjectLocalException;
0054: import javax.transaction.Transaction;
0055: import javax.management.MBeanServer;
0056: import javax.management.ObjectName;
0057: import java.sql.Connection;
0058: import java.sql.PreparedStatement;
0059: import java.sql.SQLException;
0060: import java.sql.ResultSet;
0061: import java.util.Map;
0062: import java.util.HashMap;
0063: import java.util.ArrayList;
0064: import java.util.List;
0065:
0066: /**
0067: * todo refactor optimistic locking
0068: *
0069: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
0070: * @version <tt>$Revision: 61754 $</tt>
0071: */
0072: public class EntityTable implements Table {
0073: private static final byte UNREFERENCED = 0;
0074: private static final byte CLEAN = 1;
0075: private static final byte DIRTY = 2;
0076: private static final byte CREATED = 4;
0077: private static final byte DELETED = 8;
0078: private static final byte DIRTY_RELATIONS = 16;
0079:
0080: private static final Object NOT_LOADED = new Object();
0081:
0082: private JDBCEntityBridge2 entity;
0083: private String tableName;
0084: private int fieldsTotal;
0085: private int relationsTotal;
0086: private DataSource dataSource;
0087: private Schema schema;
0088: private int tableId;
0089: private boolean dontFlushCreated;
0090:
0091: private String deleteSql;
0092: private String updateSql;
0093: private String insertSql;
0094: private String selectSql;
0095: private String duplicatePkSql;
0096:
0097: private final CommitStrategy insertStrategy;
0098: private final CommitStrategy deleteStrategy;
0099: private final CommitStrategy updateStrategy;
0100:
0101: private Logger log;
0102:
0103: private Cache cache;
0104: private ServiceControllerMBean serviceController;
0105: private ObjectName cacheName;
0106:
0107: private int[] references;
0108: private int[] referencedBy;
0109:
0110: private ForeignKeyConstraint[] fkConstraints;
0111:
0112: private CacheInvalidator cacheInvalidator;
0113:
0114: public EntityTable(JDBCEntityMetaData metadata,
0115: JDBCEntityBridge2 entity, Schema schema, int tableId)
0116: throws DeploymentException {
0117: try {
0118: InitialContext ic = new InitialContext();
0119: dataSource = (DataSource) ic.lookup(metadata
0120: .getDataSourceName());
0121: } catch (NamingException e) {
0122: throw new DeploymentException("Filed to lookup: "
0123: + metadata.getDataSourceName(), e);
0124: }
0125:
0126: this .entity = entity;
0127: tableName = SQLUtil.fixTableName(
0128: metadata.getDefaultTableName(), dataSource);
0129: log = Logger.getLogger(getClass().getName() + "." + tableName);
0130:
0131: this .schema = schema;
0132: this .tableId = tableId;
0133:
0134: final EntityMetaData entityMetaData = ((EntityMetaData) entity
0135: .getContainer().getBeanMetaData());
0136: final ConfigurationMetaData containerConf = entityMetaData
0137: .getContainerConfiguration();
0138: dontFlushCreated = containerConf.isInsertAfterEjbPostCreate();
0139:
0140: // create cache
0141: final Element cacheConf = containerConf.getContainerCacheConf();
0142: final Element cachePolicy = cacheConf == null ? null : MetaData
0143: .getOptionalChild(cacheConf, "cache-policy-conf");
0144:
0145: int minCapacity;
0146: int maxCapacity;
0147: if (cachePolicy != null) {
0148: String str = MetaData.getOptionalChildContent(cachePolicy,
0149: "min-capacity");
0150: minCapacity = (str == null ? 1000 : Integer.parseInt(str));
0151: str = MetaData.getOptionalChildContent(cachePolicy,
0152: "max-capacity");
0153: maxCapacity = (str == null ? 10000 : Integer.parseInt(str));
0154: } else {
0155: minCapacity = 1000;
0156: maxCapacity = 10000;
0157: }
0158:
0159: final Element otherConf = cacheConf == null ? null : MetaData
0160: .getOptionalChild(cacheConf, "cache-policy-conf-other");
0161:
0162: int partitionsTotal;
0163: final boolean invalidable;
0164: final Element batchCommitStrategy;
0165: if (otherConf != null) {
0166: String str = MetaData.getOptionalChildContent(otherConf,
0167: "partitions");
0168: partitionsTotal = (str == null ? 10 : Integer.parseInt(str));
0169: batchCommitStrategy = MetaData.getOptionalChild(otherConf,
0170: "batch-commit-strategy");
0171: invalidable = MetaData.getOptionalChild(otherConf,
0172: "invalidable") == null ? false : true;
0173: } else {
0174: partitionsTotal = 10;
0175: batchCommitStrategy = null;
0176: invalidable = false;
0177: }
0178:
0179: if (cachePolicy != null) {
0180: cache = new PartitionedTableCache(minCapacity, maxCapacity,
0181: partitionsTotal);
0182:
0183: String periodStr = MetaData.getOptionalChildContent(
0184: cachePolicy, "overager-period");
0185: String maxAgeStr = MetaData.getOptionalChildContent(
0186: cachePolicy, "max-bean-age");
0187: if (periodStr != null && maxAgeStr == null
0188: || maxAgeStr != null && periodStr == null) {
0189: throw new DeploymentException(
0190: "Failed to initialize age-out thread for entity "
0191: + entity.getEntityName()
0192: + ": overager-period or max-bean-age is missing!");
0193: } else if (periodStr != null && maxAgeStr != null) {
0194: long period = Long.parseLong(periodStr);
0195: long maxAge = Long.parseLong(maxAgeStr);
0196: ((PartitionedTableCache) cache).initOverager(period,
0197: maxAge, entity.getEntityName() + " overager");
0198:
0199: if (log.isTraceEnabled()) {
0200: log.trace("initialized age-out thread for "
0201: + entity.getEntityName()
0202: + ": overager-period=" + period
0203: + ", max-bean-age=" + maxAge);
0204: }
0205: }
0206:
0207: final MBeanServer server = MBeanServerLocator.locateJBoss();
0208: serviceController = (ServiceControllerMBean) MBeanProxyExt
0209: .create(ServiceControllerMBean.class,
0210: ServiceControllerMBean.OBJECT_NAME, server);
0211: try {
0212: cacheName = new ObjectName(
0213: "jboss.cmp:service=tablecache,ejbname="
0214: + metadata.getName() + ",table="
0215: + tableName);
0216: server.registerMBean(cache, cacheName);
0217: serviceController.create(cacheName);
0218: } catch (Exception e) {
0219: throw new DeploymentException(
0220: "Failed to register table cache for "
0221: + tableName, e);
0222: }
0223: } else {
0224: cache = Cache.NONE;
0225: }
0226:
0227: if (invalidable) {
0228: String groupName = entityMetaData
0229: .getDistributedCacheInvalidationConfig()
0230: .getInvalidationGroupName();
0231: String imName = entityMetaData
0232: .getDistributedCacheInvalidationConfig()
0233: .getInvalidationManagerName();
0234:
0235: InvalidationManagerMBean im = (InvalidationManagerMBean) Registry
0236: .lookup(imName);
0237: InvalidationGroup invalidationGroup = im
0238: .getInvalidationGroup(groupName);
0239:
0240: cacheInvalidator = new CacheInvalidator(cache, entity
0241: .getContainer().getTransactionManager(),
0242: invalidationGroup);
0243: }
0244:
0245: if (batchCommitStrategy == null) {
0246: insertStrategy = NON_BATCH_UPDATE;
0247: deleteStrategy = NON_BATCH_UPDATE;
0248: updateStrategy = NON_BATCH_UPDATE;
0249: } else {
0250: log.debug("batch-commit-strategy enabled");
0251: insertStrategy = BATCH_UPDATE;
0252: deleteStrategy = BATCH_UPDATE;
0253: updateStrategy = BATCH_UPDATE;
0254: }
0255: }
0256:
0257: public void start() throws DeploymentException {
0258: final JDBCAbstractCMRFieldBridge[] cmrFields = entity
0259: .getCMRFields();
0260: relationsTotal = (cmrFields != null ? cmrFields.length : 0);
0261:
0262: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
0263: .getPrimaryKeyFields();
0264: JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[]) entity
0265: .getTableFields();
0266:
0267: // DELETE SQL
0268: deleteSql = "delete from " + tableName + " where ";
0269: deleteSql += pkFields[0].getColumnName() + "=?";
0270: for (int i = 1; i < pkFields.length; ++i) {
0271: deleteSql += " and " + pkFields[i].getColumnName() + "=?";
0272: }
0273: log.debug("delete sql: " + deleteSql);
0274:
0275: // INSERT SQL
0276: insertSql = "insert into " + tableName + "(";
0277: insertSql += tableFields[0].getColumnName();
0278: for (int i = 1; i < tableFields.length; ++i) {
0279: insertSql += ", " + tableFields[i].getColumnName();
0280: }
0281: insertSql += ") values (?";
0282: for (int i = 1; i < tableFields.length; ++i) {
0283: insertSql += ", ?";
0284: }
0285: insertSql += ")";
0286: log.debug("insert sql: " + insertSql);
0287:
0288: // UPDATE SQL
0289: updateSql = "update " + tableName + " set ";
0290: int setFields = 0;
0291: for (int i = 0; i < tableFields.length; ++i) {
0292: JDBCCMPFieldBridge2 field = tableFields[i];
0293: if (!field.isPrimaryKeyMember()) {
0294: if (setFields++ > 0) {
0295: updateSql += ", ";
0296: }
0297: updateSql += field.getColumnName() + "=?";
0298: }
0299: }
0300: updateSql += " where ";
0301: updateSql += pkFields[0].getColumnName() + "=?";
0302: for (int i = 1; i < pkFields.length; ++i) {
0303: updateSql += " and " + pkFields[i].getColumnName() + "=?";
0304: }
0305:
0306: if (entity.getVersionField() != null) {
0307: updateSql += " and "
0308: + entity.getVersionField().getColumnName() + "=?";
0309: }
0310: log.debug("update sql: " + updateSql);
0311:
0312: // SELECT SQL
0313: String selectColumns = tableFields[0].getColumnName();
0314: for (int i = 1; i < tableFields.length; ++i) {
0315: JDBCCMPFieldBridge2 field = tableFields[i];
0316: selectColumns += ", " + field.getColumnName();
0317: }
0318:
0319: String whereColumns = pkFields[0].getColumnName() + "=?";
0320: for (int i = 1; i < pkFields.length; ++i) {
0321: whereColumns += " and " + pkFields[i].getColumnName()
0322: + "=?";
0323: }
0324:
0325: if (entity.getMetaData().hasRowLocking()) {
0326: JDBCEntityPersistenceStore manager = entity.getManager();
0327: JDBCTypeFactory typeFactory = manager.getJDBCTypeFactory();
0328: JDBCTypeMappingMetaData typeMapping = typeFactory
0329: .getTypeMapping();
0330: JDBCFunctionMappingMetaData rowLockingTemplate = typeMapping
0331: .getRowLockingTemplate();
0332: if (rowLockingTemplate == null) {
0333: throw new DeploymentException(
0334: "Row locking template is not defined for mapping: "
0335: + typeMapping.getName());
0336: }
0337:
0338: selectSql = rowLockingTemplate.getFunctionSql(
0339: new Object[] { selectColumns, tableName,
0340: whereColumns, null }, new StringBuffer())
0341: .toString();
0342: } else {
0343: selectSql = "select ";
0344: selectSql += selectColumns;
0345: selectSql += " from " + tableName + " where ";
0346: selectSql += whereColumns;
0347: }
0348: log.debug("select sql: " + selectSql);
0349:
0350: // DUPLICATE KEY
0351: if (dontFlushCreated) {
0352: duplicatePkSql = "select ";
0353: duplicatePkSql += pkFields[0].getColumnName();
0354: for (int i = 1; i < pkFields.length; ++i) {
0355: duplicatePkSql += ", " + pkFields[i].getColumnName();
0356: }
0357: duplicatePkSql += " from " + tableName + " where ";
0358: duplicatePkSql += pkFields[0].getColumnName() + "=?";
0359: for (int i = 1; i < pkFields.length; ++i) {
0360: duplicatePkSql += " and " + pkFields[i].getColumnName()
0361: + "=?";
0362: }
0363: log.debug("duplicate pk sql: " + duplicatePkSql);
0364: }
0365:
0366: if (cacheName != null) {
0367: try {
0368: serviceController.start(cacheName);
0369: } catch (Exception e) {
0370: throw new DeploymentException(
0371: "Failed to start table cache.", e);
0372: }
0373: }
0374: }
0375:
0376: public void stop() throws Exception {
0377: if (cacheInvalidator != null) {
0378: cacheInvalidator.unregister();
0379: }
0380:
0381: if (cacheName != null) {
0382: serviceController.stop(cacheName);
0383: serviceController.destroy(cacheName);
0384: serviceController.remove(cacheName);
0385: }
0386: serviceController = null;
0387: }
0388:
0389: public StringBuffer appendColumnNames(JDBCCMPFieldBridge2[] fields,
0390: String alias, StringBuffer buf) {
0391: for (int i = 0; i < fields.length; ++i) {
0392: if (i > 0) {
0393: buf.append(", ");
0394: }
0395:
0396: if (alias != null) {
0397: buf.append(alias).append(".");
0398: }
0399:
0400: buf.append(fields[i].getColumnName());
0401: }
0402:
0403: return buf;
0404: }
0405:
0406: public void addField() {
0407: ++fieldsTotal;
0408: }
0409:
0410: public int addVersionField() {
0411: return fieldsTotal++;
0412: }
0413:
0414: public ForeignKeyConstraint addFkConstraint(
0415: JDBCCMPFieldBridge2[] fkFields, EntityTable referenced) {
0416: addReference(referenced);
0417: referenced.addReferencedBy(this );
0418:
0419: if (fkConstraints == null) {
0420: fkConstraints = new ForeignKeyConstraint[1];
0421: } else {
0422: ForeignKeyConstraint[] tmp = fkConstraints;
0423: fkConstraints = new ForeignKeyConstraint[tmp.length + 1];
0424: System.arraycopy(tmp, 0, fkConstraints, 0, tmp.length);
0425: }
0426: final int fkindex = fkConstraints.length - 1;
0427: final ForeignKeyConstraint fkc = new ForeignKeyConstraint(
0428: fkindex, fkFields);
0429: fkConstraints[fkindex] = fkc;
0430: return fkc;
0431: }
0432:
0433: public DataSource getDataSource() {
0434: return dataSource;
0435: }
0436:
0437: public Object loadRow(ResultSet rs, boolean searchableOnly) {
0438: View view = getView();
0439: Object pk = view.loadPk(rs);
0440: if (pk != null) {
0441: view.loadRow(rs, pk, searchableOnly);
0442: } else if (log.isTraceEnabled()) {
0443: log.trace("loaded pk is null.");
0444: }
0445: return pk;
0446: }
0447:
0448: public Row getRow(Object id) {
0449: return getView().getRow(id);
0450: }
0451:
0452: public boolean hasRow(Object id) {
0453: return getView().hasRow(id);
0454: }
0455:
0456: public Row loadRow(Object id) throws SQLException {
0457: View view = getView();
0458:
0459: Row row = view.getRowByPk(id, false);
0460: if (row != null) {
0461: if (log.isTraceEnabled()) {
0462: log.trace("row is already loaded: pk=" + id);
0463: }
0464: return row;
0465: }
0466:
0467: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
0468: .getPrimaryKeyFields();
0469:
0470: Connection con = null;
0471: PreparedStatement ps = null;
0472: ResultSet rs = null;
0473: try {
0474: if (log.isDebugEnabled()) {
0475: log.debug("executing sql: " + selectSql);
0476: }
0477:
0478: con = dataSource.getConnection();
0479: ps = con.prepareStatement(selectSql);
0480:
0481: int paramInd = 1;
0482: for (int i = 0; i < pkFields.length; ++i) {
0483: JDBCCMPFieldBridge2 pkField = pkFields[i];
0484: Object pkValue = pkField.getPrimaryKeyValue(id);
0485: paramInd = pkField.setArgumentParameters(ps, paramInd,
0486: pkValue);
0487: }
0488:
0489: rs = ps.executeQuery();
0490:
0491: if (!rs.next()) {
0492: throw new NoSuchEntityException("Row not found: " + id);
0493: }
0494:
0495: return view.loadRow(rs, id, false);
0496: } catch (SQLException e) {
0497: log.error("Failed to load row: table=" + tableName
0498: + ", pk=" + id);
0499: throw e;
0500: } finally {
0501: JDBCUtil.safeClose(rs);
0502: JDBCUtil.safeClose(ps);
0503: JDBCUtil.safeClose(con);
0504: }
0505: }
0506:
0507: // Table implementation
0508:
0509: public int getTableId() {
0510: return tableId;
0511: }
0512:
0513: public String getTableName() {
0514: return tableName;
0515: }
0516:
0517: public Table.View createView(Transaction tx) {
0518: return new View(tx);
0519: }
0520:
0521: // Private
0522:
0523: private void addReference(EntityTable table) {
0524: boolean wasRegistered = false;
0525: if (references != null) {
0526: for (int i = 0; i < references.length; ++i) {
0527: if (references[i] == table.getTableId()) {
0528: wasRegistered = true;
0529: break;
0530: }
0531: }
0532:
0533: if (!wasRegistered) {
0534: int[] tmp = references;
0535: references = new int[references.length + 1];
0536: System.arraycopy(tmp, 0, references, 0, tmp.length);
0537: references[tmp.length] = table.getTableId();
0538: }
0539: } else {
0540: references = new int[1];
0541: references[0] = table.getTableId();
0542: }
0543:
0544: if (!wasRegistered) {
0545: if (log.isTraceEnabled()) {
0546: log.trace("references " + table.getTableName());
0547: }
0548: }
0549: }
0550:
0551: private void addReferencedBy(EntityTable table) {
0552: boolean wasRegistered = false;
0553: if (referencedBy != null) {
0554: for (int i = 0; i < referencedBy.length; ++i) {
0555: if (referencedBy[i] == table.getTableId()) {
0556: wasRegistered = true;
0557: break;
0558: }
0559: }
0560:
0561: if (!wasRegistered) {
0562: int[] tmp = referencedBy;
0563: referencedBy = new int[referencedBy.length + 1];
0564: System.arraycopy(tmp, 0, referencedBy, 0, tmp.length);
0565: referencedBy[tmp.length] = table.getTableId();
0566: }
0567: } else {
0568: referencedBy = new int[1];
0569: referencedBy[0] = table.getTableId();
0570: }
0571:
0572: if (!wasRegistered) {
0573: if (log.isTraceEnabled()) {
0574: log.trace("referenced by " + table.getTableName());
0575: }
0576: }
0577: }
0578:
0579: private void delete(View view) throws SQLException {
0580: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
0581: .getPrimaryKeyFields();
0582:
0583: Connection con = null;
0584: PreparedStatement ps = null;
0585: try {
0586: if (log.isDebugEnabled()) {
0587: log.debug("executing : " + deleteSql);
0588: }
0589:
0590: con = dataSource.getConnection();
0591: ps = con.prepareStatement(deleteSql);
0592:
0593: int batchCount = 0;
0594: while (view.deleted != null) {
0595: Row row = view.deleted;
0596:
0597: int paramInd = 1;
0598: for (int pkInd = 0; pkInd < pkFields.length; ++pkInd) {
0599: JDBCCMPFieldBridge2 pkField = pkFields[pkInd];
0600: Object fieldValue = row.fields[pkField
0601: .getRowIndex()];
0602: paramInd = pkField.setArgumentParameters(ps,
0603: paramInd, fieldValue);
0604: }
0605:
0606: deleteStrategy.executeUpdate(ps);
0607:
0608: ++batchCount;
0609: row.flushStatus();
0610: }
0611:
0612: deleteStrategy.executeBatch(ps);
0613:
0614: if (view.deleted != null) {
0615: throw new IllegalStateException(
0616: "There are still rows to delete!");
0617: }
0618:
0619: if (log.isTraceEnabled()) {
0620: log.trace("deleted rows: " + batchCount);
0621: }
0622: } catch (SQLException e) {
0623: log.error("Failed to delete view: " + e.getMessage(), e);
0624: throw e;
0625: } finally {
0626: JDBCUtil.safeClose(ps);
0627: JDBCUtil.safeClose(con);
0628: }
0629: }
0630:
0631: private void update(View view) throws SQLException {
0632: JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[]) entity
0633: .getTableFields();
0634: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
0635: .getPrimaryKeyFields();
0636:
0637: Connection con = null;
0638: PreparedStatement ps = null;
0639: try {
0640: if (log.isDebugEnabled()) {
0641: log.debug("executing : " + updateSql);
0642: }
0643:
0644: con = dataSource.getConnection();
0645: ps = con.prepareStatement(updateSql);
0646:
0647: int batchCount = 0;
0648: while (view.dirty != null) {
0649: Row row = view.dirty;
0650:
0651: int paramInd = 1;
0652: for (int fInd = 0; fInd < tableFields.length; ++fInd) {
0653: JDBCCMPFieldBridge2 field = tableFields[fInd];
0654: if (!field.isPrimaryKeyMember()) {
0655: Object fieldValue = row.fields[field
0656: .getRowIndex()];
0657: paramInd = field.setArgumentParameters(ps,
0658: paramInd, fieldValue);
0659: }
0660: }
0661:
0662: for (int fInd = 0; fInd < pkFields.length; ++fInd) {
0663: JDBCCMPFieldBridge2 pkField = pkFields[fInd];
0664: Object fieldValue = row.fields[pkField
0665: .getRowIndex()];
0666: paramInd = pkField.setArgumentParameters(ps,
0667: paramInd, fieldValue);
0668: }
0669:
0670: JDBCCMPFieldBridge2 versionField = entity
0671: .getVersionField();
0672: if (versionField != null) {
0673: int versionIndex = versionField.getVersionIndex();
0674: Object curVersion = row.fields[versionIndex];
0675: paramInd = versionField.setArgumentParameters(ps,
0676: paramInd, curVersion);
0677:
0678: Object newVersion = row.fields[versionField
0679: .getRowIndex()];
0680: row.fields[versionIndex] = newVersion;
0681: }
0682:
0683: updateStrategy.executeUpdate(ps);
0684:
0685: ++batchCount;
0686: row.flushStatus();
0687: }
0688:
0689: updateStrategy.executeBatch(ps);
0690:
0691: if (log.isTraceEnabled()) {
0692: log.trace("updated rows: " + batchCount);
0693: }
0694: } catch (SQLException e) {
0695: log.error("Failed to update: table=" + tableName, e);
0696: throw e;
0697: } finally {
0698: JDBCUtil.safeClose(ps);
0699: JDBCUtil.safeClose(con);
0700: }
0701: }
0702:
0703: private void insert(View view) throws SQLException {
0704: JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[]) entity
0705: .getTableFields();
0706: Connection con = null;
0707: PreparedStatement ps = null;
0708: try {
0709: if (log.isDebugEnabled()) {
0710: log.debug("executing : " + insertSql);
0711: }
0712:
0713: con = dataSource.getConnection();
0714: ps = con.prepareStatement(insertSql);
0715:
0716: int batchCount = 0;
0717: while (view.created != null) {
0718: Row row = view.created;
0719:
0720: int paramInd = 1;
0721: for (int fInd = 0; fInd < tableFields.length; ++fInd) {
0722: JDBCCMPFieldBridge2 field = tableFields[fInd];
0723: Object fieldValue = row.fields[field.getRowIndex()];
0724: paramInd = field.setArgumentParameters(ps,
0725: paramInd, fieldValue);
0726: }
0727:
0728: insertStrategy.executeUpdate(ps);
0729:
0730: ++batchCount;
0731: row.flushStatus();
0732: }
0733:
0734: insertStrategy.executeBatch(ps);
0735:
0736: if (log.isTraceEnabled()) {
0737: log.trace("inserted rows: " + batchCount);
0738: }
0739: } catch (SQLException e) {
0740: log
0741: .error("Failed to insert new rows: "
0742: + e.getMessage(), e);
0743: throw e;
0744: } finally {
0745: JDBCUtil.safeClose(ps);
0746: JDBCUtil.safeClose(con);
0747: }
0748: }
0749:
0750: private EntityTable.View getView() {
0751: return (EntityTable.View) schema.getView(this );
0752: }
0753:
0754: public class View implements Table.View {
0755: private final Transaction tx;
0756:
0757: private Map rowByPk = new HashMap();
0758: private Row created;
0759: private Row deleted;
0760: private Row dirty;
0761: private Row dirtyRelations;
0762: private Row clean;
0763:
0764: private Row cacheUpdates;
0765:
0766: private List rowsWithNullFks;
0767:
0768: private boolean inFlush;
0769:
0770: public View(Transaction tx) {
0771: this .tx = tx;
0772: }
0773:
0774: public Row getRow(Object pk) {
0775: Row row;
0776: if (pk == null) {
0777: row = new Row(this );
0778: } else {
0779: row = getRowByPk(pk, false);
0780: if (row == null) {
0781: row = createCleanRow(pk);
0782: }
0783: }
0784: return row;
0785: }
0786:
0787: public Row getRowByPk(Object pk, boolean required) {
0788: /*
0789: Row cursor = clean;
0790: while(cursor != null)
0791: {
0792: if(pk.equals(cursor.pk))
0793: {
0794: return cursor;
0795: }
0796: cursor = cursor.next;
0797: }
0798:
0799: cursor = dirty;
0800: while(cursor != null)
0801: {
0802: if(pk.equals(cursor.pk))
0803: {
0804: return cursor;
0805: }
0806: cursor = cursor.next;
0807: }
0808:
0809: cursor = created;
0810: while(cursor != null)
0811: {
0812: if(pk.equals(cursor.pk))
0813: {
0814: return cursor;
0815: }
0816: cursor = cursor.next;
0817: }
0818: */
0819:
0820: Row row = (Row) rowByPk.get(pk);
0821:
0822: if (row == null) {
0823: Object[] fields;
0824: Object[] relations = null;
0825: try {
0826: cache.lock(pk);
0827:
0828: fields = cache.getFields(pk);
0829: if (fields != null && relationsTotal > 0) {
0830: relations = cache.getRelations(pk);
0831: if (relations == null) {
0832: relations = new Object[relationsTotal];
0833: }
0834: }
0835: } finally {
0836: cache.unlock(pk);
0837: }
0838:
0839: if (fields != null) {
0840: row = createCleanRow(pk, fields, relations);
0841: }
0842: }
0843:
0844: if (row == null && required) {
0845: throw new IllegalStateException("row not found: pk="
0846: + pk);
0847: }
0848:
0849: return row;
0850: }
0851:
0852: public void addClean(Row row) {
0853: /*
0854: if(getRowByPk(row.pk, false) != null)
0855: {
0856: throw new IllegalStateException("View already contains the row: key=" + row.pk);
0857: }
0858: */
0859:
0860: if (clean != null) {
0861: row.next = clean;
0862: clean.prev = row;
0863: }
0864:
0865: clean = row;
0866: row.state = CLEAN;
0867:
0868: rowByPk.put(row.pk, row);
0869: }
0870:
0871: public void addCreated(Row row) throws DuplicateKeyException {
0872: //if(getRowByPk(row.pk, false) != null)
0873: //{
0874: // throw new DuplicateKeyException("Table " + tableName + ", key=" + row.pk);
0875: //}
0876:
0877: if (created != null) {
0878: row.next = created;
0879: created.prev = row;
0880: }
0881:
0882: created = row;
0883: row.state = CREATED;
0884:
0885: rowByPk.put(row.pk, row);
0886:
0887: JDBCCMPFieldBridge2 versionField = entity.getVersionField();
0888: if (versionField != null) {
0889: row.fields[versionField.getVersionIndex()] = row.fields[versionField
0890: .getRowIndex()];
0891: }
0892: }
0893:
0894: public Row loadRow(ResultSet rs, Object pk,
0895: boolean searchableOnly) {
0896: Row row = getRowByPk(pk, false);
0897: if (row != null) {
0898: if (log.isTraceEnabled()) {
0899: log.trace("row is already loaded: pk=" + pk);
0900: }
0901: return row;
0902: } else if (log.isTraceEnabled()) {
0903: log.trace("reading result set: pk=" + pk);
0904: }
0905:
0906: row = createCleanRow(pk);
0907: JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[]) entity
0908: .getTableFields();
0909: // this rsOffset is kind of a hack
0910: // but since tableIndex and rowIndex of a field are the same
0911: // this should work ok
0912: int rsOffset = 1;
0913: for (int i = 0; i < tableFields.length; ++i) {
0914: JDBCCMPFieldBridge2 field = tableFields[i];
0915: if (searchableOnly
0916: && !field.getJDBCType().isSearchable()) {
0917: row.fields[field.getRowIndex()] = NOT_LOADED;
0918: --rsOffset;
0919: continue;
0920: }
0921:
0922: Object columnValue = field.loadArgumentResults(rs,
0923: field.getRowIndex() + rsOffset);
0924: row.fields[field.getRowIndex()] = columnValue;
0925:
0926: if (field.getVersionIndex() != -1) {
0927: row.fields[field.getVersionIndex()] = columnValue;
0928: }
0929: }
0930:
0931: Object[] relations = (relationsTotal > 0 ? new Object[relationsTotal]
0932: : null);
0933:
0934: try {
0935: cache.lock(row.pk);
0936: cache.put(tx, row.pk, row.fields, relations);
0937: } finally {
0938: cache.unlock(row.pk);
0939: }
0940:
0941: return row;
0942: }
0943:
0944: public Object loadPk(ResultSet rs) {
0945: Object pk = null;
0946: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
0947: .getPrimaryKeyFields();
0948: //int rsInd = 1;
0949: for (int i = 0; i < pkFields.length; ++i) {
0950: JDBCCMPFieldBridge2 field = pkFields[i];
0951: //Object columnValue = field.loadArgumentResults(rs, rsInd++);
0952: Object columnValue = field.loadArgumentResults(rs,
0953: field.getRowIndex() + 1);
0954: pk = field.setPrimaryKeyValue(pk, columnValue);
0955: }
0956: return pk;
0957: }
0958:
0959: public boolean hasRow(Object id) {
0960: boolean has = rowByPk.containsKey(id);
0961: if (!has) {
0962: try {
0963: cache.lock(id);
0964: has = cache.contains(tx, id);
0965: } finally {
0966: cache.unlock(id);
0967: }
0968: }
0969: return has;
0970: }
0971:
0972: public void addRowWithNullFk(Row row) {
0973: if (rowsWithNullFks == null) {
0974: rowsWithNullFks = new ArrayList();
0975: }
0976: rowsWithNullFks.add(row);
0977: }
0978:
0979: private Row createCleanRow(Object pk) {
0980: Row row = new Row(this );
0981: row.pk = pk;
0982: addClean(row);
0983: return row;
0984: }
0985:
0986: private Row createCleanRow(Object pk, Object[] fields,
0987: Object[] relations) {
0988: Row row = new Row(this , fields, relations);
0989: row.pk = pk;
0990: addClean(row);
0991: return row;
0992: }
0993:
0994: // Table.View implementation
0995:
0996: public void flushDeleted(Schema.Views views)
0997: throws SQLException {
0998: if (rowsWithNullFks != null) {
0999: nullifyForeignKeys();
1000: rowsWithNullFks = null;
1001: }
1002:
1003: if (deleted == null) {
1004: if (log.isTraceEnabled()) {
1005: log.trace("no rows to delete");
1006: }
1007: return;
1008: }
1009:
1010: if (referencedBy != null) {
1011: if (inFlush) {
1012: if (log.isTraceEnabled()) {
1013: log.trace("inFlush, ignoring flushDeleted");
1014: }
1015: return;
1016: }
1017:
1018: inFlush = true;
1019:
1020: try {
1021: for (int i = 0; i < referencedBy.length; ++i) {
1022: final Table.View view = views.entityViews[referencedBy[i]];
1023: if (view != null) {
1024: view.flushDeleted(views);
1025: }
1026: }
1027: } finally {
1028: inFlush = false;
1029: }
1030: }
1031:
1032: delete(this );
1033: }
1034:
1035: public void flushCreated(Schema.Views views)
1036: throws SQLException {
1037: if (created == null || dontFlushCreated) {
1038: if (log.isTraceEnabled()) {
1039: log.trace("no rows to insert");
1040: }
1041: return;
1042: }
1043:
1044: if (references != null) {
1045: if (inFlush) {
1046: if (log.isTraceEnabled()) {
1047: log.trace("inFlush, ignorning flushCreated");
1048: }
1049: return;
1050: } else if (log.isTraceEnabled()) {
1051: log.trace("flushing created references");
1052: }
1053:
1054: inFlush = true;
1055: try {
1056: for (int i = 0; i < references.length; ++i) {
1057: final Table.View view = views.entityViews[references[i]];
1058: if (view != null) {
1059: view.flushCreated(views);
1060: }
1061: }
1062: } finally {
1063: inFlush = false;
1064: }
1065: }
1066:
1067: insert(this );
1068: }
1069:
1070: public void flushUpdated() throws SQLException {
1071: if (dirtyRelations != null) {
1072: while (dirtyRelations != null) {
1073: Row row = dirtyRelations;
1074: row.flushStatus();
1075: }
1076: }
1077:
1078: if (dirty == null) {
1079: if (log.isTraceEnabled()) {
1080: log.trace("no rows to update");
1081: }
1082: return;
1083: }
1084:
1085: update(this );
1086: }
1087:
1088: public void beforeCompletion() {
1089: /* There is no sense in the current impl of lock-for-update.
1090: if(cacheUpdates != null)
1091: {
1092: Row cursor = cacheUpdates;
1093:
1094: while(cursor != null)
1095: {
1096: cache.lock(cursor.pk);
1097: try
1098: {
1099: cache.lockForUpdate(tx, cursor.pk);
1100: }
1101: catch(Exception e)
1102: {
1103: throw new EJBException("Table " + entity.getQualifiedTableName() + ": " + e.getMessage());
1104: }
1105: finally
1106: {
1107: cache.unlock(cursor.pk);
1108: }
1109:
1110: cursor.lockedForUpdate = true;
1111: cursor = cursor.nextCacheUpdate;
1112: }
1113: }
1114: */
1115: }
1116:
1117: public void committed() {
1118: if (cacheUpdates != null) {
1119: Row cursor = cacheUpdates;
1120:
1121: while (cursor != null) {
1122: //if(cursor.lockedForUpdate)
1123: //{
1124: cache.lock(cursor.pk);
1125: try {
1126: switch (cursor.state) {
1127: case CLEAN:
1128: cache.put(tx, cursor.pk, cursor.fields,
1129: cursor.relations);
1130: break;
1131: case DELETED:
1132: try {
1133: cache.remove(tx, cursor.pk);
1134: } catch (Cache.RemoveException e) {
1135: log.warn(e.getMessage());
1136: }
1137: break;
1138: default:
1139: throw new IllegalStateException(
1140: "Unexpected row state: table="
1141: + entity
1142: .getQualifiedTableName()
1143: + ", pk=" + cursor.pk
1144: + ", state=" + cursor.state);
1145: }
1146: } finally {
1147: cache.unlock(cursor.pk);
1148: }
1149: //cursor.lockedForUpdate = false;
1150: //}
1151: cursor = cursor.nextCacheUpdate;
1152: }
1153: }
1154: }
1155:
1156: public void rolledback() {
1157: /* There is no sense in the current impl of lock-for-update.
1158: if(cacheUpdates != null)
1159: {
1160: Row cursor = cacheUpdates;
1161:
1162: while(cursor != null)
1163: {
1164: if(cursor.lockedForUpdate)
1165: {
1166: cache.lock(cursor.pk);
1167: try
1168: {
1169: cache.releaseLock(tx, cursor.pk);
1170: }
1171: catch(Exception e)
1172: {
1173: log.warn("Table " + entity.getQualifiedTableName() + ": " + e.getMessage());
1174: }
1175: finally
1176: {
1177: cache.unlock(cursor.pk);
1178: }
1179: cursor.lockedForUpdate = false;
1180: }
1181: cursor = cursor.nextCacheUpdate;
1182: }
1183: }
1184: */
1185: }
1186:
1187: private void nullifyForeignKeys() throws SQLException {
1188: if (log.isTraceEnabled()) {
1189: log.trace("nullifying foreign keys");
1190: }
1191:
1192: Connection con = null;
1193: PreparedStatement[] ps = new PreparedStatement[fkConstraints.length];
1194:
1195: try {
1196: final JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
1197: .getPrimaryKeyFields();
1198: con = dataSource.getConnection();
1199:
1200: for (int i = 0; i < rowsWithNullFks.size(); ++i) {
1201: final Row row = (Row) rowsWithNullFks.get(i);
1202: if (row.state != DELETED) {
1203: final ForeignKeyConstraint[] cons = row.fkUpdates;
1204: for (int c = 0; c < fkConstraints.length; ++c) {
1205: if (cons[c] != null) {
1206: PreparedStatement s = ps[c];
1207: if (s == null) {
1208: if (log.isDebugEnabled()) {
1209: log.debug("nullifying fk: "
1210: + cons[c].nullFkSql);
1211: }
1212: s = con
1213: .prepareStatement(cons[c].nullFkSql);
1214: ps[c] = s;
1215: }
1216:
1217: int paramInd = 1;
1218: for (int fInd = 0; fInd < pkFields.length; ++fInd) {
1219: JDBCCMPFieldBridge2 pkField = pkFields[fInd];
1220: Object fieldValue = row.fields[pkField
1221: .getRowIndex()];
1222: paramInd = pkField
1223: .setArgumentParameters(s,
1224: paramInd,
1225: fieldValue);
1226: }
1227:
1228: final int affected = s.executeUpdate();
1229: if (affected != 1) {
1230: throw new EJBException(
1231: "Affected "
1232: + affected
1233: + " rows while expected just one");
1234: }
1235: }
1236: }
1237: }
1238: }
1239: } finally {
1240: for (int i = 0; i < ps.length; ++i) {
1241: JDBCUtil.safeClose(ps[i]);
1242: }
1243: JDBCUtil.safeClose(con);
1244: }
1245: }
1246: }
1247:
1248: public class Row {
1249: private EntityTable.View view;
1250: private Object pk;
1251: private final Object[] fields;
1252: private final Object[] relations;
1253:
1254: private byte state;
1255:
1256: private Row prev;
1257: private Row next;
1258:
1259: private boolean cacheUpdateScheduled;
1260: private Row nextCacheUpdate;
1261: //private boolean lockedForUpdate;
1262:
1263: private ForeignKeyConstraint[] fkUpdates;
1264:
1265: public Row(EntityTable.View view) {
1266: this .view = view;
1267: fields = new Object[fieldsTotal];
1268: relations = (relationsTotal == 0 ? null
1269: : new Object[relationsTotal]);
1270: state = UNREFERENCED;
1271: }
1272:
1273: public Row(EntityTable.View view, Object[] fields,
1274: Object[] relations) {
1275: this .view = view;
1276: this .fields = fields;
1277: this .relations = relations;
1278: state = UNREFERENCED;
1279: }
1280:
1281: public Object getPk() {
1282: return pk;
1283: }
1284:
1285: public void loadCachedRelations(int index,
1286: Cache.CacheLoader loader) {
1287: if (relations != null) {
1288: final Object cached = relations[index];
1289: relations[index] = loader.loadFromCache(cached);
1290: }
1291: }
1292:
1293: public void cacheRelations(int index, Cache.CacheLoader loader) {
1294: relations[index] = loader.getCachedValue();
1295: scheduleCacheUpdate();
1296: }
1297:
1298: public void insert(Object pk) throws DuplicateKeyException {
1299: this .pk = pk;
1300: view.addCreated(this );
1301: }
1302:
1303: public Object getFieldValue(int i) {
1304: if (state == DELETED) {
1305: throw new NoSuchObjectLocalException(
1306: "The instance was removed: " + pk);
1307: }
1308:
1309: Object value = fields[i];
1310: if (value == NOT_LOADED) {
1311: value = loadField(i);
1312: }
1313:
1314: return value;
1315: }
1316:
1317: public void setFieldValue(int i, Object value) {
1318: fields[i] = value;
1319: }
1320:
1321: public boolean isDirty() {
1322: return state != CLEAN && state != DIRTY_RELATIONS;
1323: }
1324:
1325: public void setDirty() {
1326: if (state == CLEAN || state == DIRTY_RELATIONS) {
1327: updateState(DIRTY);
1328: }
1329: }
1330:
1331: public void setDirtyRelations() {
1332: if (state == CLEAN) {
1333: updateState(DIRTY_RELATIONS);
1334: }
1335: }
1336:
1337: public void delete() {
1338: if (state == CLEAN || state == DIRTY
1339: || state == DIRTY_RELATIONS) {
1340: updateState(DELETED);
1341: } else if (state == CREATED) {
1342: dereference();
1343: state = DELETED;
1344: view.rowByPk.remove(pk);
1345: } else if (state == DELETED) {
1346: throw new IllegalStateException(
1347: "The row is already deleted: pk=" + pk);
1348: }
1349: }
1350:
1351: public void nullForeignKey(ForeignKeyConstraint constraint) {
1352: if (fkUpdates == null) {
1353: fkUpdates = new ForeignKeyConstraint[fkConstraints.length];
1354: view.addRowWithNullFk(this );
1355: }
1356:
1357: fkUpdates[constraint.index] = constraint;
1358: }
1359:
1360: public void nonNullForeignKey(ForeignKeyConstraint constraint) {
1361: if (fkUpdates != null) {
1362: fkUpdates[constraint.index] = null;
1363: }
1364: }
1365:
1366: private void flushStatus() {
1367: if (state == CREATED || state == DIRTY) {
1368: updateState(CLEAN);
1369: } else if (state == DELETED) {
1370: dereference();
1371: } else if (state == DIRTY_RELATIONS) {
1372: updateState(CLEAN);
1373: }
1374:
1375: scheduleCacheUpdate();
1376: }
1377:
1378: private void scheduleCacheUpdate() {
1379: if (!cacheUpdateScheduled) {
1380: if (view.cacheUpdates == null) {
1381: view.cacheUpdates = this ;
1382: } else {
1383: nextCacheUpdate = view.cacheUpdates;
1384: view.cacheUpdates = this ;
1385: }
1386: cacheUpdateScheduled = true;
1387: }
1388: }
1389:
1390: private void updateState(byte state) {
1391: dereference();
1392:
1393: if (state == CLEAN) {
1394: if (view.clean != null) {
1395: next = view.clean;
1396: view.clean.prev = this ;
1397: }
1398: view.clean = this ;
1399: } else if (state == DIRTY) {
1400: if (view.dirty != null) {
1401: next = view.dirty;
1402: view.dirty.prev = this ;
1403: }
1404: view.dirty = this ;
1405: } else if (state == CREATED) {
1406: if (view.created != null) {
1407: next = view.created;
1408: view.created.prev = this ;
1409: }
1410: view.created = this ;
1411: } else if (state == DELETED) {
1412: if (view.deleted != null) {
1413: next = view.deleted;
1414: view.deleted.prev = this ;
1415: }
1416: view.deleted = this ;
1417: } else if (state == DIRTY_RELATIONS) {
1418: if (view.dirtyRelations != null) {
1419: next = view.dirtyRelations;
1420: view.dirtyRelations.prev = this ;
1421: }
1422: view.dirtyRelations = this ;
1423: } else {
1424: throw new IllegalStateException(
1425: "Can't update to state: " + state);
1426: }
1427:
1428: this .state = state;
1429: }
1430:
1431: private void dereference() {
1432: if (state == CLEAN && view.clean == this ) {
1433: view.clean = next;
1434: } else if (state == DIRTY && view.dirty == this ) {
1435: view.dirty = next;
1436: } else if (state == CREATED && view.created == this ) {
1437: view.created = next;
1438: } else if (state == DELETED && view.deleted == this ) {
1439: view.deleted = next;
1440: } else if (state == DIRTY_RELATIONS
1441: && view.dirtyRelations == this ) {
1442: view.dirtyRelations = next;
1443: }
1444:
1445: if (next != null) {
1446: next.prev = prev;
1447: }
1448:
1449: if (prev != null) {
1450: prev.next = next;
1451: }
1452:
1453: prev = null;
1454: next = null;
1455: }
1456:
1457: public void flush() throws SQLException, DuplicateKeyException {
1458: // todo needs refactoring
1459:
1460: if (state != CREATED) {
1461: if (log.isTraceEnabled()) {
1462: log.trace("The row is already inserted: pk=" + pk);
1463: }
1464: return;
1465: }
1466:
1467: Connection con = null;
1468: PreparedStatement duplicatePkPs = null;
1469: PreparedStatement insertPs = null;
1470: ResultSet rs = null;
1471: try {
1472: int paramInd;
1473: con = dataSource.getConnection();
1474:
1475: // check for duplicate key
1476: /*
1477: if(log.isDebugEnabled())
1478: {
1479: log.debug("executing : " + duplicatePkSql);
1480: }
1481:
1482: duplicatePkPs = con.prepareStatement(duplicatePkSql);
1483:
1484: paramInd = 1;
1485: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity.getPrimaryKeyFields();
1486: for(int i = 0; i < pkFields.length; ++i)
1487: {
1488: JDBCCMPFieldBridge2 pkField = pkFields[i];
1489: Object fieldValue = fields[pkField.getRowIndex()];
1490: paramInd = pkField.setArgumentParameters(duplicatePkPs, paramInd, fieldValue);
1491: }
1492:
1493: rs = duplicatePkPs.executeQuery();
1494: if(rs.next())
1495: {
1496: throw new DuplicateKeyException("Table " + tableName + ", pk=" + pk);
1497: }
1498: */
1499:
1500: // insert
1501: if (log.isDebugEnabled()) {
1502: log.debug("executing : " + insertSql);
1503: }
1504:
1505: insertPs = con.prepareStatement(insertSql);
1506:
1507: paramInd = 1;
1508: JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[]) entity
1509: .getTableFields();
1510: for (int fInd = 0; fInd < tableFields.length; ++fInd) {
1511: JDBCCMPFieldBridge2 field = tableFields[fInd];
1512: Object fieldValue = fields[field.getRowIndex()];
1513: paramInd = field.setArgumentParameters(insertPs,
1514: paramInd, fieldValue);
1515: }
1516:
1517: insertPs.executeUpdate();
1518:
1519: flushStatus();
1520: } catch (SQLException e) {
1521: log.error("Failed to insert new rows: "
1522: + e.getMessage(), e);
1523: throw e;
1524: } finally {
1525: JDBCUtil.safeClose(rs);
1526: JDBCUtil.safeClose(duplicatePkPs);
1527: JDBCUtil.safeClose(insertPs);
1528: JDBCUtil.safeClose(con);
1529: }
1530: }
1531:
1532: private Object loadField(int i) {
1533: JDBCCMPFieldBridge2 field = (JDBCCMPFieldBridge2) entity
1534: .getFields().get(i);
1535:
1536: StringBuffer query = new StringBuffer();
1537: query.append("select ").append(field.getColumnName())
1538: .append(" from ").append(tableName).append(
1539: " where ");
1540:
1541: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
1542: .getPrimaryKeyFields();
1543: for (int pkI = 0; pkI < pkFields.length; ++pkI) {
1544: if (pkI > 0) {
1545: query.append(" and ");
1546: }
1547: query.append(pkFields[pkI].getColumnName())
1548: .append("=?");
1549: }
1550:
1551: if (log.isDebugEnabled()) {
1552: log.debug("executing: " + query.toString());
1553: }
1554:
1555: Object value = null;
1556: Connection con = null;
1557: PreparedStatement ps = null;
1558: ResultSet rs = null;
1559:
1560: try {
1561: con = dataSource.getConnection();
1562: ps = con.prepareStatement(query.toString());
1563:
1564: for (int pkI = 0; pkI < pkFields.length; ++pkI) {
1565: JDBCCMPFieldBridge2 pkField = pkFields[pkI];
1566: Object fieldValue = fields[pkField.getRowIndex()];
1567: pkField.setArgumentParameters(ps, pkI + 1,
1568: fieldValue);
1569: }
1570:
1571: rs = ps.executeQuery();
1572:
1573: if (!rs.next()) {
1574: throw new NoSuchEntityException("Row not found: "
1575: + pk);
1576: }
1577:
1578: value = field.loadArgumentResults(rs, 1);
1579: } catch (SQLException e) {
1580: throw new EJBException("Failed to load field "
1581: + entity.getEntityName() + "."
1582: + field.getFieldName() + ": " + e.getMessage(),
1583: e);
1584: } finally {
1585: JDBCUtil.safeClose(rs);
1586: JDBCUtil.safeClose(ps);
1587: JDBCUtil.safeClose(con);
1588: }
1589:
1590: fields[field.getRowIndex()] = value;
1591: return value;
1592: }
1593: }
1594:
1595: public static interface CommitStrategy {
1596: void executeUpdate(PreparedStatement ps) throws SQLException;
1597:
1598: void executeBatch(PreparedStatement ps) throws SQLException;
1599: }
1600:
1601: private static final CommitStrategy BATCH_UPDATE = new CommitStrategy() {
1602: public void executeUpdate(PreparedStatement ps)
1603: throws SQLException {
1604: ps.addBatch();
1605: }
1606:
1607: public void executeBatch(PreparedStatement ps)
1608: throws SQLException {
1609: int[] updates = ps.executeBatch();
1610: for (int i = 0; i < updates.length; ++i) {
1611: int status = updates[i];
1612: if (status != 1 && status != -2 /* java.sql.Statement.SUCCESS_NO_INFO since jdk1.4*/) {
1613: String msg = (status == -3 /* java.sql.Statement.EXECUTE_FAILED since jdk1.4 */? "One of the commands in the batch failed to execute"
1614: : "Each command in the batch should update exactly 1 row but "
1615: + "one of the commands updated "
1616: + updates[i] + " rows.");
1617: throw new EJBException(msg);
1618: }
1619: }
1620: }
1621: };
1622:
1623: private static final CommitStrategy NON_BATCH_UPDATE = new CommitStrategy() {
1624: public void executeUpdate(PreparedStatement ps)
1625: throws SQLException {
1626: int rows = ps.executeUpdate();
1627: if (rows != 1) {
1628: throw new EJBException(
1629: "Expected one updated row but got: " + rows);
1630: }
1631: }
1632:
1633: public void executeBatch(PreparedStatement ps) {
1634: }
1635: };
1636:
1637: public class ForeignKeyConstraint {
1638: public final int index;
1639: private final String nullFkSql;
1640:
1641: public ForeignKeyConstraint(int index,
1642: JDBCCMPFieldBridge2[] fkFields) {
1643: this .index = index;
1644:
1645: StringBuffer buf = new StringBuffer();
1646: buf.append("update ").append(tableName).append(" set ")
1647: .append(fkFields[0].getColumnName())
1648: .append("=null");
1649: for (int i = 1; i < fkFields.length; ++i) {
1650: buf.append(", ").append(fkFields[i].getColumnName())
1651: .append("=null");
1652: }
1653:
1654: buf.append(" where ");
1655: JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[]) entity
1656: .getPrimaryKeyFields();
1657: buf.append(pkFields[0].getColumnName()).append("=?");
1658: for (int i = 1; i < pkFields.length; ++i) {
1659: buf.append(" and ").append(pkFields[i].getColumnName())
1660: .append("=?");
1661: }
1662:
1663: nullFkSql = buf.toString();
1664: if (log.isDebugEnabled()) {
1665: log.debug("update foreign key sql: " + nullFkSql);
1666: }
1667: }
1668: }
1669: }
|