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.bridge;
0023:
0024: import java.lang.ref.WeakReference;
0025: import java.lang.reflect.Method;
0026: import javax.sql.DataSource;
0027: import java.sql.PreparedStatement;
0028: import java.sql.ResultSet;
0029: import java.util.ArrayList;
0030: import java.util.Collection;
0031: import java.util.Collections;
0032: import java.util.HashSet;
0033: import java.util.Iterator;
0034: import java.util.List;
0035: import java.util.Map;
0036: import java.util.Set;
0037: import java.util.HashMap;
0038: import java.util.Arrays;
0039: import java.security.AccessController;
0040: import java.security.PrivilegedAction;
0041: import java.security.Principal;
0042: import java.rmi.RemoteException;
0043: import javax.ejb.EJBException;
0044: import javax.ejb.EJBLocalObject;
0045: import javax.ejb.EJBLocalHome;
0046: import javax.ejb.RemoveException;
0047: import javax.ejb.NoSuchObjectLocalException;
0048: import javax.transaction.Status;
0049: import javax.transaction.Synchronization;
0050: import javax.transaction.SystemException;
0051: import javax.transaction.Transaction;
0052: import javax.transaction.TransactionManager;
0053: import javax.transaction.RollbackException;
0054:
0055: import org.jboss.deployment.DeploymentException;
0056: import org.jboss.ejb.EntityCache;
0057: import org.jboss.ejb.EntityContainer;
0058: import org.jboss.ejb.EntityEnterpriseContext;
0059: import org.jboss.ejb.LocalProxyFactory;
0060: import org.jboss.ejb.plugins.cmp.bridge.EntityBridge;
0061: import org.jboss.ejb.plugins.cmp.bridge.FieldBridge;
0062: import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
0063: import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
0064: import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
0065: import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
0066: import org.jboss.ejb.plugins.cmp.jdbc.CascadeDeleteStrategy;
0067: import org.jboss.ejb.plugins.cmp.jdbc.RelationData;
0068: import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
0069: import org.jboss.ejb.plugins.cmp.jdbc.JDBCParameterSetter;
0070: import org.jboss.ejb.plugins.cmp.jdbc.JDBCResultSetReader;
0071: import org.jboss.tm.TransactionLocal;
0072: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
0073: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
0074: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
0075: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
0076: import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
0077: import org.jboss.ejb.plugins.lock.Entrancy;
0078: import org.jboss.invocation.InvocationType;
0079: import org.jboss.logging.Logger;
0080: import org.jboss.security.SecurityAssociation;
0081:
0082: /**
0083: * JDBCCMRFieldBridge a bean relationship. This class only supports
0084: * relationships between entities managed by a JDBCStoreManager in the same
0085: * application.
0086: * <p/>
0087: * Life-cycle:
0088: * Tied to the EntityBridge.
0089: * <p/>
0090: * Multiplicity:
0091: * One for each role that entity has.
0092: *
0093: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
0094: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
0095: * @version $Revision: 61754 $
0096: */
0097: public final class JDBCCMRFieldBridge extends
0098: JDBCAbstractCMRFieldBridge {
0099: /**
0100: * The entity bridge to which this cmr field belongs.
0101: */
0102: private final JDBCEntityBridge entity;
0103: /**
0104: * The manager of this entity.
0105: */
0106: private final JDBCStoreManager manager;
0107: /**
0108: * Metadata of the relationship role that this field represents.
0109: */
0110: private final JDBCRelationshipRoleMetaData metadata;
0111: /**
0112: * The data source used to acess the relation table if relevant.
0113: */
0114: private DataSource dataSource;
0115: /**
0116: * The relation table name if relevent.
0117: */
0118: private String qualifiedTableName;
0119: private String tableName;
0120: /**
0121: * The key fields that this entity maintains in the relation table.
0122: */
0123: private JDBCCMP2xFieldBridge[] tableKeyFields;
0124: /**
0125: * JDBCType for the foreign key fields. Basically, this is an ordered
0126: * merge of the JDBCType of the foreign key field.
0127: */
0128: private JDBCType jdbcType;
0129: /**
0130: * The related entity's container.
0131: */
0132: private WeakReference relatedContainerRef;
0133: /**
0134: * The related entity's jdbc store manager
0135: */
0136: private JDBCStoreManager relatedManager;
0137: /**
0138: * The related entity.
0139: */
0140: private JDBCEntityBridge relatedEntity;
0141: /**
0142: * The related entity's cmr field for this relationship.
0143: */
0144: private JDBCCMRFieldBridge relatedCMRField;
0145: /**
0146: * da log.
0147: */
0148: private final Logger log;
0149:
0150: /**
0151: * Foreign key fields of this entity (i.e., related entities pk fields)
0152: */
0153: private JDBCCMP2xFieldBridge[] foreignKeyFields;
0154: /**
0155: * Indicates whether all FK fields are mapped to PK fields
0156: */
0157: private boolean allFKFieldsMappedToPKFields;
0158: /**
0159: * This map contains related PK fields that are mapped through FK fields to this entity's PK fields
0160: */
0161: private final Map relatedPKFieldsByMyPKFields = new HashMap();
0162: /**
0163: * This map contains related PK fields keyed by FK fields
0164: */
0165: private final Map relatedPKFieldsByMyFKFields = new HashMap();
0166: /**
0167: * Indicates whether there are foreign key fields mapped to CMP fields
0168: */
0169: private boolean hasFKFieldsMappedToCMPFields;
0170:
0171: // Map for lists of related PK values keyed by this side's PK values.
0172: // The values are put/removed by related entities when its fields representing
0173: // foreign key are changed. When entity with this CMR is created, this map is checked
0174: // for waiting for it entities. Relationship with waiting entities is established,
0175: // removing waiting entities' primary keys from the map.
0176: // NOTE: this map is used only for foreign key fields mapped to CMP fields.
0177: private final TransactionLocal relatedPKValuesWaitingForMyPK = new TransactionLocal() {
0178: protected Object initialValue() {
0179: return new HashMap();
0180: }
0181: };
0182:
0183: /**
0184: * FindByPrimaryKey method used to find related instances in case when FK fields mapped to PK fields
0185: */
0186: private Method relatedFindByPrimaryKey;
0187:
0188: /**
0189: * index of the field in the JDBCContext
0190: */
0191: private final int jdbcContextIndex;
0192:
0193: /**
0194: * cascade-delete strategy
0195: */
0196: private CascadeDeleteStrategy cascadeDeleteStrategy;
0197:
0198: /**
0199: * This CMR field and its related CMR field share the same RelationDataManager
0200: */
0201: private RelationDataManager relationManager;
0202:
0203: /**
0204: * Creates a cmr field for the entity based on the metadata.
0205: */
0206: public JDBCCMRFieldBridge(JDBCEntityBridge entity,
0207: JDBCStoreManager manager,
0208: JDBCRelationshipRoleMetaData metadata)
0209: throws DeploymentException {
0210: this .entity = entity;
0211: this .manager = manager;
0212: this .metadata = metadata;
0213: this .jdbcContextIndex = ((JDBCEntityBridge) manager
0214: .getEntityBridge()).getNextJDBCContextIndex();
0215:
0216: // Creat the log
0217: String categoryName = this .getClass().getName() + "."
0218: + manager.getMetaData().getName() + ".";
0219: if (metadata.getCMRFieldName() != null) {
0220: categoryName += metadata.getCMRFieldName();
0221: } else {
0222: categoryName += metadata.getRelatedRole().getEntity()
0223: .getName()
0224: + "-" + metadata.getRelatedRole().getCMRFieldName();
0225: }
0226: this .log = Logger.getLogger(categoryName);
0227: }
0228:
0229: public RelationDataManager getRelationDataManager() {
0230: return relationManager;
0231: }
0232:
0233: public void resolveRelationship() throws DeploymentException {
0234: //
0235: // Set handles to the related entity's container, cache,
0236: // manager, and invoker
0237: //
0238:
0239: // Related Entity Name
0240: String relatedEntityName = metadata.getRelatedRole()
0241: .getEntity().getName();
0242:
0243: // Related Entity
0244: Catalog catalog = (Catalog) manager
0245: .getApplicationData("CATALOG");
0246: relatedEntity = (JDBCEntityBridge) catalog
0247: .getEntityByEJBName(relatedEntityName);
0248: if (relatedEntity == null) {
0249: throw new DeploymentException("Related entity not found: "
0250: + "entity=" + entity.getEntityName() + ", "
0251: + "cmrField=" + getFieldName() + ", "
0252: + "relatedEntity=" + relatedEntityName);
0253: }
0254:
0255: // Related CMR Field
0256: JDBCCMRFieldBridge[] cmrFields = (JDBCCMRFieldBridge[]) relatedEntity
0257: .getCMRFields();
0258: for (int i = 0; i < cmrFields.length; ++i) {
0259: JDBCCMRFieldBridge cmrField = cmrFields[i];
0260: if (metadata.getRelatedRole() == cmrField.getMetaData()) {
0261: relatedCMRField = cmrField;
0262: break;
0263: }
0264: }
0265:
0266: // if we didn't find the related CMR field throw an exception
0267: // with a detailed message
0268: if (relatedCMRField == null) {
0269: String message = "Related CMR field not found in "
0270: + relatedEntity.getEntityName()
0271: + " for relationship from";
0272:
0273: message += entity.getEntityName() + ".";
0274: if (getFieldName() != null) {
0275: message += getFieldName();
0276: } else {
0277: message += "<no-field>";
0278: }
0279:
0280: message += " to ";
0281: message += relatedEntityName + ".";
0282: if (metadata.getRelatedRole().getCMRFieldName() != null) {
0283: message += metadata.getRelatedRole().getCMRFieldName();
0284: } else {
0285: message += "<no-field>";
0286: }
0287:
0288: throw new DeploymentException(message);
0289: }
0290:
0291: // Related Manager
0292: relatedManager = (JDBCStoreManager) relatedEntity.getManager();
0293:
0294: // Related Container
0295: EntityContainer relatedContainer = relatedManager
0296: .getContainer();
0297: this .relatedContainerRef = new WeakReference(relatedContainer);
0298:
0299: // related findByPrimaryKey
0300: Class homeClass = (relatedContainer.getLocalHomeClass() != null ? relatedContainer
0301: .getLocalHomeClass()
0302: : relatedContainer.getHomeClass());
0303: try {
0304: relatedFindByPrimaryKey = homeClass.getMethod(
0305: "findByPrimaryKey", new Class[] { relatedEntity
0306: .getPrimaryKeyClass() });
0307: } catch (Exception e) {
0308: throw new DeploymentException("findByPrimaryKey("
0309: + relatedEntity.getPrimaryKeyClass().getName()
0310: + " pk) was not found in " + homeClass.getName());
0311: }
0312:
0313: //
0314: // Initialize the key fields
0315: //
0316: if (metadata.getRelationMetaData().isTableMappingStyle()) {
0317: // initialize relation table key fields
0318: Collection tableKeys = metadata.getKeyFields();
0319: List keyFieldsList = new ArrayList(tableKeys.size());
0320:
0321: // first phase is to create fk fields
0322: Map pkFieldsToFKFields = new HashMap(tableKeys.size());
0323: for (Iterator i = tableKeys.iterator(); i.hasNext();) {
0324: JDBCCMPFieldMetaData cmpFieldMetaData = (JDBCCMPFieldMetaData) i
0325: .next();
0326: FieldBridge pkField = entity
0327: .getFieldByName(cmpFieldMetaData.getFieldName());
0328: if (pkField == null) {
0329: throw new DeploymentException(
0330: "Primary key not found for key-field "
0331: + cmpFieldMetaData.getFieldName());
0332: }
0333: pkFieldsToFKFields.put(pkField,
0334: new JDBCCMP2xFieldBridge(manager,
0335: cmpFieldMetaData));
0336: }
0337: // second step is to order fk fields to match the order of pk fields
0338: JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
0339: for (int i = 0; i < pkFields.length; ++i) {
0340: Object fkField = pkFieldsToFKFields.get(pkFields[i]);
0341: if (fkField == null) {
0342: throw new DeploymentException("Primary key "
0343: + pkFields[i].getFieldName()
0344: + " is not mapped.");
0345: }
0346: keyFieldsList.add(fkField);
0347: }
0348: tableKeyFields = (JDBCCMP2xFieldBridge[]) keyFieldsList
0349: .toArray(new JDBCCMP2xFieldBridge[keyFieldsList
0350: .size()]);
0351:
0352: dataSource = metadata.getRelationMetaData().getDataSource();
0353: } else {
0354: initializeForeignKeyFields();
0355: dataSource = hasForeignKey() ? entity.getDataSource()
0356: : relatedEntity.getDataSource();
0357: }
0358:
0359: // Fix table name
0360: //
0361: // This code doesn't work here... The problem each side will generate
0362: // the table name and this will only work for simple generation.
0363: qualifiedTableName = SQLUtil.fixTableName(metadata
0364: .getRelationMetaData().getDefaultTableName(),
0365: dataSource);
0366: tableName = SQLUtil
0367: .getTableNameWithoutSchema(qualifiedTableName);
0368:
0369: relationManager = relatedCMRField.initRelationManager(this );
0370: }
0371:
0372: /**
0373: * The third phase of deployment. The method is called when relationships are already resolved.
0374: *
0375: * @throws DeploymentException
0376: */
0377: public void start() throws DeploymentException {
0378: cascadeDeleteStrategy = CascadeDeleteStrategy
0379: .getCascadeDeleteStrategy(this );
0380: }
0381:
0382: public boolean removeFromRelations(EntityEnterpriseContext ctx,
0383: Object[] oldRelationsRef) {
0384: load(ctx);
0385:
0386: FieldState fieldState = getFieldState(ctx);
0387: List value = fieldState.getValue();
0388:
0389: boolean removed = false;
0390: if (!value.isEmpty()) {
0391: if (hasFKFieldsMappedToCMPFields) {
0392: if (isForeignKeyValid(value.get(0))) {
0393: cascadeDeleteStrategy.removedIds(ctx,
0394: oldRelationsRef, value);
0395: removed = true;
0396: }
0397: } else {
0398: cascadeDeleteStrategy.removedIds(ctx, oldRelationsRef,
0399: value);
0400: removed = true;
0401: }
0402: }
0403: return removed;
0404: }
0405:
0406: public void cascadeDelete(EntityEnterpriseContext ctx,
0407: List oldValues) throws RemoveException, RemoteException {
0408: cascadeDeleteStrategy.cascadeDelete(ctx, oldValues);
0409: }
0410:
0411: public boolean isBatchCascadeDelete() {
0412: return (cascadeDeleteStrategy instanceof CascadeDeleteStrategy.BatchCascadeDeleteStrategy);
0413: }
0414:
0415: /**
0416: * Gets the manager of this entity.
0417: */
0418: public JDBCStoreManager getJDBCStoreManager() {
0419: return manager;
0420: }
0421:
0422: /**
0423: * Gets bridge for this entity.
0424: */
0425: public JDBCAbstractEntityBridge getEntity() {
0426: return entity;
0427: }
0428:
0429: /**
0430: * Gets the metadata of the relationship role that this field represents.
0431: */
0432: public JDBCRelationshipRoleMetaData getMetaData() {
0433: return metadata;
0434: }
0435:
0436: /**
0437: * Gets the relation metadata.
0438: */
0439: public JDBCRelationMetaData getRelationMetaData() {
0440: return metadata.getRelationMetaData();
0441: }
0442:
0443: /**
0444: * Gets the name of this field.
0445: */
0446: public String getFieldName() {
0447: return metadata.getCMRFieldName();
0448: }
0449:
0450: /**
0451: * Gets the name of the relation table if relevent.
0452: */
0453: public String getQualifiedTableName() {
0454: return qualifiedTableName;
0455: }
0456:
0457: public String getTableName() {
0458: return tableName;
0459: }
0460:
0461: /**
0462: * Gets the datasource of the relation table if relevent.
0463: */
0464: public DataSource getDataSource() {
0465: return dataSource;
0466: }
0467:
0468: /**
0469: * Gets the read ahead meta data.
0470: */
0471: public JDBCReadAheadMetaData getReadAhead() {
0472: return metadata.getReadAhead();
0473: }
0474:
0475: public JDBCType getJDBCType() {
0476: return jdbcType;
0477: }
0478:
0479: public boolean isPrimaryKeyMember() {
0480: return false;
0481: }
0482:
0483: /**
0484: * Does this cmr field have foreign keys.
0485: */
0486: public boolean hasForeignKey() {
0487: return foreignKeyFields != null;
0488: }
0489:
0490: /**
0491: * Returns true if all FK fields are mapped to PK fields
0492: */
0493: public boolean allFkFieldsMappedToPkFields() {
0494: return allFKFieldsMappedToPKFields;
0495: }
0496:
0497: /**
0498: * Is this a collection valued field.
0499: */
0500: public boolean isCollectionValued() {
0501: return metadata.getRelatedRole().isMultiplicityMany();
0502: }
0503:
0504: /**
0505: * Is this a single valued field.
0506: */
0507: public boolean isSingleValued() {
0508: return metadata.getRelatedRole().isMultiplicityOne();
0509: }
0510:
0511: /**
0512: * Gets the key fields that this entity maintains in the relation table.
0513: */
0514: public JDBCFieldBridge[] getTableKeyFields() {
0515: return tableKeyFields;
0516: }
0517:
0518: /**
0519: * Gets the foreign key fields of this entity (i.e., related entities pk fields)
0520: */
0521: public JDBCFieldBridge[] getForeignKeyFields() {
0522: return foreignKeyFields;
0523: }
0524:
0525: /**
0526: * The related entity's cmr field for this relationship.
0527: */
0528: public JDBCAbstractCMRFieldBridge getRelatedCMRField() {
0529: return relatedCMRField;
0530: }
0531:
0532: /**
0533: * The related manger.
0534: */
0535: public JDBCStoreManager getRelatedManager() {
0536: return relatedManager;
0537: }
0538:
0539: /**
0540: * The related entity.
0541: */
0542: public EntityBridge getRelatedEntity() {
0543: return relatedEntity;
0544: }
0545:
0546: /**
0547: * The related entity.
0548: */
0549: public JDBCEntityBridge getRelatedJDBCEntity() {
0550: return relatedEntity;
0551: }
0552:
0553: /**
0554: * The related container
0555: */
0556: private final EntityContainer getRelatedContainer() {
0557: return (EntityContainer) relatedContainerRef.get();
0558: }
0559:
0560: /**
0561: * The related entity's local home interface.
0562: */
0563: public final Class getRelatedLocalInterface() {
0564: return getRelatedContainer().getLocalClass();
0565: }
0566:
0567: /**
0568: * The related entity's local container invoker.
0569: */
0570: public final LocalProxyFactory getRelatedInvoker() {
0571: return getRelatedContainer().getLocalProxyFactory();
0572: }
0573:
0574: /**
0575: * @param ctx - entity's context
0576: * @return true if entity is loaded, false - otherwise.
0577: */
0578: public boolean isLoaded(EntityEnterpriseContext ctx) {
0579: return getFieldState(ctx).isLoaded;
0580: }
0581:
0582: /**
0583: * Establishes relationships with related entities waited for passed in context
0584: * to be created.
0585: *
0586: * @param ctx - entity's context.
0587: */
0588: public void addRelatedPKsWaitedForMe(EntityEnterpriseContext ctx) {
0589: final Map relatedPKsMap = getRelatedPKsWaitingForMyPK();
0590: synchronized (relatedPKsMap) {
0591: List relatedPKsWaitingForMe = (List) relatedPKsMap.get(ctx
0592: .getId());
0593: if (relatedPKsWaitingForMe != null) {
0594: for (Iterator waitingPKsIter = relatedPKsWaitingForMe
0595: .iterator(); waitingPKsIter.hasNext();) {
0596: Object waitingPK = waitingPKsIter.next();
0597: waitingPKsIter.remove();
0598: if (isForeignKeyValid(waitingPK)) {
0599: createRelationLinks(ctx, waitingPK);
0600: }
0601: }
0602: }
0603: }
0604: }
0605:
0606: /**
0607: * Is this field readonly?
0608: */
0609: public boolean isReadOnly() {
0610: return getRelationMetaData().isReadOnly();
0611: }
0612:
0613: /**
0614: * Had the read time expired?
0615: */
0616: public boolean isReadTimedOut(EntityEnterpriseContext ctx) {
0617: // if we are read/write then we are always timed out
0618: if (!isReadOnly()) {
0619: return true;
0620: }
0621:
0622: // if read-time-out is -1 then we never time out.
0623: if (getRelationMetaData().getReadTimeOut() == -1) {
0624: return false;
0625: }
0626:
0627: long readInterval = System.currentTimeMillis()
0628: - getFieldState(ctx).getLastRead();
0629: return readInterval > getRelationMetaData().getReadTimeOut();
0630: }
0631:
0632: /**
0633: * @param ctx - entity's context.
0634: * @return the value of this field.
0635: */
0636: public Object getValue(EntityEnterpriseContext ctx) {
0637: // no user checks yet, but this is where they would go
0638: return getInstanceValue(ctx);
0639: }
0640:
0641: /**
0642: * Sets new value.
0643: *
0644: * @param ctx - entity's context;
0645: * @param value - new value.
0646: */
0647: public void setValue(EntityEnterpriseContext ctx, Object value) {
0648: if (isReadOnly()) {
0649: throw new EJBException("Field is read-only: fieldName="
0650: + getFieldName());
0651: }
0652:
0653: if (!JDBCEntityBridge.isEjbCreateDone(ctx)) {
0654: throw new IllegalStateException(
0655: "A CMR field cannot be set "
0656: + "in ejbCreate; this should be done in the ejbPostCreate "
0657: + "method instead [EJB 2.0 Spec. 10.5.2].");
0658: }
0659:
0660: if (isCollectionValued() && value == null) {
0661: throw new IllegalArgumentException(
0662: "null cannot be assigned to a "
0663: + "collection-valued cmr-field [EJB 2.0 Spec. 10.3.8].");
0664: }
0665: /*
0666: if(allFKFieldsMappedToPKFields)
0667: {
0668: throw new IllegalStateException(
0669: "Can't modify relationship: CMR field "
0670: + entity.getEntityName() + "." + getFieldName()
0671: + " has foreign key fields mapped to the primary key columns."
0672: + " Primary key may only be set once in ejbCreate [EJB 2.0 Spec. 10.3.5].");
0673: }
0674: */
0675:
0676: setInstanceValue(ctx, value);
0677: }
0678:
0679: /**
0680: * Gets the value of the cmr field for the instance associated with
0681: * the context.
0682: */
0683: public Object getInstanceValue(EntityEnterpriseContext myCtx) {
0684: load(myCtx);
0685:
0686: FieldState fieldState = getFieldState(myCtx);
0687: if (isCollectionValued()) {
0688: return fieldState.getRelationSet();
0689: }
0690:
0691: // only return one
0692: try {
0693: List value = fieldState.getValue();
0694: if (!value.isEmpty()) {
0695: Object fk = value.get(0);
0696: return getRelatedEntityByFK(fk);
0697: } else if (foreignKeyFields != null) {
0698: // for those completely mapped to CMP fields and created in this current tx !!!
0699: Object relatedId = getRelatedIdFromContext(myCtx);
0700: if (relatedId != null) {
0701: return getRelatedEntityByFK(relatedId);
0702: }
0703: }
0704: return null;
0705: } catch (EJBException e) {
0706: throw e;
0707: } catch (Exception e) {
0708: throw new EJBException(e);
0709: }
0710: }
0711:
0712: /**
0713: * Returns related entity's local interface.
0714: * If there are foreign key fields mapped to CMP fields, existence of related entity is checked
0715: * with findByPrimaryKey and if, in this case, related instance is not found, null is returned.
0716: * If foreign key fields mapped to its own columns then existence of related entity is not checked
0717: * and just its local object is returned.
0718: *
0719: * @param fk - foreign key value.
0720: * @return related local object instance.
0721: */
0722: public EJBLocalObject getRelatedEntityByFK(Object fk) {
0723: EJBLocalObject relatedLocalObject = null;
0724: final EntityContainer relatedContainer = getRelatedContainer();
0725:
0726: if (hasFKFieldsMappedToCMPFields
0727: && relatedManager.getReadAheadCache()
0728: .getPreloadDataMap(fk, false) == null // not in preload cache
0729: ) {
0730: EJBLocalHome relatedHome = relatedContainer
0731: .getLocalProxyFactory().getEJBLocalHome();
0732: try {
0733: relatedLocalObject = (EJBLocalObject) relatedFindByPrimaryKey
0734: .invoke(relatedHome, new Object[] { fk });
0735: } catch (Exception ignore) {
0736: // no such entity. it is ok to ignore
0737: }
0738: } else {
0739: relatedLocalObject = relatedContainer
0740: .getLocalProxyFactory().getEntityEJBLocalObject(fk);
0741: }
0742:
0743: return relatedLocalObject;
0744: }
0745:
0746: /**
0747: * This method is called only for CMR fields with foreign key fields mapped to CMP fields
0748: * to check the validity of the foreign key value.
0749: *
0750: * @param fk the foreign key to check
0751: * @return true if there is related entity with the equal primary key
0752: */
0753: public boolean isForeignKeyValid(Object fk) {
0754: boolean valid;
0755: if (relatedManager.getReadAheadCache().getPreloadDataMap(fk,
0756: false) != null) {
0757: valid = true;
0758: } else {
0759: EJBLocalHome relatedHome = getRelatedContainer()
0760: .getLocalProxyFactory().getEJBLocalHome();
0761: try {
0762: relatedFindByPrimaryKey.invoke(relatedHome,
0763: new Object[] { fk });
0764: valid = true;
0765: } catch (Exception ignore) {
0766: // no such entity. it is ok to ignore
0767: valid = false;
0768: }
0769: }
0770: return valid;
0771: }
0772:
0773: /**
0774: * Sets the value of the cmr field for the instance associated with
0775: * the context.
0776: */
0777: public void setInstanceValue(EntityEnterpriseContext myCtx,
0778: Object newValue) {
0779: // validate new value first
0780: List newPks;
0781: if (newValue instanceof Collection) {
0782: Collection col = (Collection) newValue;
0783: if (!col.isEmpty()) {
0784: newPks = new ArrayList(col.size());
0785: for (Iterator iter = col.iterator(); iter.hasNext();) {
0786: Object localObject = iter.next();
0787: if (localObject != null) {
0788: Object relatedId = getRelatedPrimaryKey(localObject);
0789:
0790: // check whether new value modifies the primary key if there are FK fields mapped to PK fields
0791: if (relatedPKFieldsByMyPKFields.size() > 0) {
0792: checkSetForeignKey(myCtx, relatedId);
0793: }
0794:
0795: newPks.add(relatedId);
0796: }
0797: }
0798: } else {
0799: newPks = Collections.EMPTY_LIST;
0800: }
0801: } else {
0802: if (newValue != null) {
0803: newPks = Collections
0804: .singletonList(getRelatedPrimaryKey(newValue));
0805: } else {
0806: newPks = Collections.EMPTY_LIST;
0807: }
0808: }
0809:
0810: // load the current value
0811: load(myCtx);
0812: FieldState fieldState = getFieldState(myCtx);
0813:
0814: // is this just setting our own relation set back
0815: if (newValue == fieldState.getRelationSet()) {
0816: return;
0817: }
0818:
0819: try {
0820: // Remove old value(s)
0821: List value = fieldState.getValue();
0822: if (!value.isEmpty()) {
0823: Object[] curPks = value
0824: .toArray(new Object[value.size()]);
0825: for (int i = 0; i < curPks.length; ++i) {
0826: destroyRelationLinks(myCtx, curPks[i]);
0827: }
0828: }
0829:
0830: // Add new value(s)
0831: for (int i = 0; i < newPks.size(); ++i) {
0832: createRelationLinks(myCtx, newPks.get(i));
0833: }
0834: } catch (RuntimeException e) {
0835: throw e;
0836: } catch (Exception e) {
0837: throw new EJBException(e);
0838: }
0839: }
0840:
0841: /**
0842: * Checks whether new foreign key value conflicts with primary key value
0843: * in case of foreign key to primary key mapping.
0844: *
0845: * @param myCtx - entity's context;
0846: * @param newValue - new foreign key value.
0847: * @throws IllegalStateException - if new foreign key value changes
0848: * primary key value, otherwise returns silently.
0849: */
0850: private void checkSetForeignKey(EntityEnterpriseContext myCtx,
0851: Object newValue) throws IllegalStateException {
0852: JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
0853: for (int i = 0; i < pkFields.length; ++i) {
0854: JDBCCMP2xFieldBridge pkField = (JDBCCMP2xFieldBridge) pkFields[i];
0855: JDBCCMP2xFieldBridge relatedPkField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyPKFields
0856: .get(pkField);
0857: if (relatedPkField != null) {
0858: Object comingValue = relatedPkField
0859: .getPrimaryKeyValue(newValue);
0860: Object currentValue = pkField.getInstanceValue(myCtx);
0861:
0862: // they shouldn't be null
0863: if (!comingValue.equals(currentValue)) {
0864: throw new IllegalStateException(
0865: "Can't create relationship: CMR field "
0866: + entity.getEntityName()
0867: + "."
0868: + getFieldName()
0869: + " has foreign key fields mapped to the primary key columns."
0870: + " Primary key may only be set once in ejbCreate [EJB 2.0 Spec. 10.3.5]."
0871: + " primary key value is "
0872: + currentValue
0873: + " overriding value is "
0874: + comingValue);
0875: }
0876: }
0877: }
0878: }
0879:
0880: /**
0881: * Creates the relation links between the instance associated with the
0882: * context and the related instance (just the id is passed in).
0883: * <p/>
0884: * This method calls a.addRelation(b) and b.addRelation(a)
0885: */
0886: public void createRelationLinks(EntityEnterpriseContext myCtx,
0887: Object relatedId) {
0888: createRelationLinks(myCtx, relatedId, true);
0889: }
0890:
0891: public void createRelationLinks(EntityEnterpriseContext myCtx,
0892: Object relatedId, boolean updateForeignKey) {
0893: if (isReadOnly()) {
0894: throw new EJBException("Field is read-only: "
0895: + getFieldName());
0896: }
0897:
0898: // If my multiplicity is one, then we need to free the new related context
0899: // from its old relationship.
0900: Transaction tx = getTransaction();
0901: if (metadata.isMultiplicityOne()) {
0902: Object oldRelatedId = relatedCMRField.invokeGetRelatedId(
0903: tx, relatedId);
0904: if (oldRelatedId != null) {
0905: invokeRemoveRelation(tx, oldRelatedId, relatedId);
0906: relatedCMRField.invokeRemoveRelation(tx, relatedId,
0907: oldRelatedId);
0908: }
0909: }
0910:
0911: addRelation(myCtx, relatedId, updateForeignKey);
0912: relatedCMRField.invokeAddRelation(tx, relatedId, myCtx.getId());
0913: }
0914:
0915: /**
0916: * Destroys the relation links between the instance associated with the
0917: * context and the related instance (just the id is passed in).
0918: * <p/>
0919: * This method calls a.removeRelation(b) and b.removeRelation(a)
0920: */
0921: public void destroyRelationLinks(EntityEnterpriseContext myCtx,
0922: Object relatedId) {
0923: destroyRelationLinks(myCtx, relatedId, true);
0924: }
0925:
0926: /**
0927: * Destroys the relation links between the instance associated with the
0928: * context and the related instance (just the id is passed in).
0929: * <p/>
0930: * This method calls a.removeRelation(b) and b.removeRelation(a)
0931: * <p/>
0932: * If updateValueCollection is false, the related id collection is not
0933: * updated. This form is only used by the RelationSet iterator.
0934: */
0935: public void destroyRelationLinks(EntityEnterpriseContext myCtx,
0936: Object relatedId, boolean updateValueCollection) {
0937: destroyRelationLinks(myCtx, relatedId, updateValueCollection,
0938: true);
0939: }
0940:
0941: public void destroyRelationLinks(EntityEnterpriseContext myCtx,
0942: Object relatedId, boolean updateValueCollection,
0943: boolean updateForeignKey) {
0944: if (isReadOnly()) {
0945: throw new EJBException("Field is read-only: "
0946: + getFieldName());
0947: }
0948:
0949: removeRelation(myCtx, relatedId, updateValueCollection,
0950: updateForeignKey);
0951: relatedCMRField.invokeRemoveRelation(getTransaction(),
0952: relatedId, myCtx.getId());
0953: }
0954:
0955: /**
0956: * Schedules children for cascade delete.
0957: */
0958: public void scheduleChildrenForCascadeDelete(
0959: EntityEnterpriseContext ctx) {
0960: load(ctx);
0961: FieldState fieldState = getFieldState(ctx);
0962: List value = fieldState.getValue();
0963: if (!value.isEmpty()) {
0964: Transaction tx = getTransaction();
0965: for (int i = 0; i < value.size(); ++i) {
0966: relatedCMRField.invokeScheduleForCascadeDelete(tx,
0967: value.get(i));
0968: }
0969: }
0970: }
0971:
0972: /**
0973: * Schedules children for batch cascade delete.
0974: */
0975: public void scheduleChildrenForBatchCascadeDelete(
0976: EntityEnterpriseContext ctx) {
0977: load(ctx);
0978: FieldState fieldState = getFieldState(ctx);
0979: List value = fieldState.getValue();
0980: if (!value.isEmpty()) {
0981: Transaction tx = getTransaction();
0982: for (int i = 0; i < value.size(); ++i) {
0983: relatedCMRField.invokeScheduleForBatchCascadeDelete(tx,
0984: value.get(i));
0985: }
0986: }
0987: }
0988:
0989: /**
0990: * Schedules the instance with myId for cascade delete.
0991: */
0992: private Object invokeScheduleForCascadeDelete(Transaction tx,
0993: Object myId) {
0994: try {
0995: EntityCache instanceCache = (EntityCache) manager
0996: .getContainer().getInstanceCache();
0997: SecurityActions actions = SecurityActions.UTIL
0998: .getSecurityActions();
0999:
1000: CMRInvocation invocation = new CMRInvocation();
1001: invocation
1002: .setCmrMessage(CMRMessage.SCHEDULE_FOR_CASCADE_DELETE);
1003: invocation.setEntrancy(Entrancy.NON_ENTRANT);
1004: invocation.setId(instanceCache.createCacheKey(myId));
1005: invocation.setArguments(new Object[] { this });
1006: invocation.setTransaction(tx);
1007: invocation.setPrincipal(actions.getPrincipal());
1008: invocation.setCredential(actions.getCredential());
1009: invocation.setType(InvocationType.LOCAL);
1010: return manager.getContainer().invoke(invocation);
1011: } catch (EJBException e) {
1012: throw e;
1013: } catch (Exception e) {
1014: throw new EJBException(
1015: "Error in scheduleForCascadeDelete()", e);
1016: }
1017: }
1018:
1019: /**
1020: * Schedules the instance with myId for batch cascade delete.
1021: */
1022: private Object invokeScheduleForBatchCascadeDelete(Transaction tx,
1023: Object myId) {
1024: try {
1025: EntityCache instanceCache = (EntityCache) manager
1026: .getContainer().getInstanceCache();
1027: SecurityActions actions = SecurityActions.UTIL
1028: .getSecurityActions();
1029:
1030: CMRInvocation invocation = new CMRInvocation();
1031: invocation
1032: .setCmrMessage(CMRMessage.SCHEDULE_FOR_BATCH_CASCADE_DELETE);
1033: invocation.setEntrancy(Entrancy.NON_ENTRANT);
1034: invocation.setId(instanceCache.createCacheKey(myId));
1035: invocation.setArguments(new Object[] { this });
1036: invocation.setTransaction(tx);
1037: invocation.setPrincipal(actions.getPrincipal());
1038: invocation.setCredential(actions.getCredential());
1039: invocation.setType(InvocationType.LOCAL);
1040: return manager.getContainer().invoke(invocation);
1041: } catch (EJBException e) {
1042: throw e;
1043: } catch (Exception e) {
1044: throw new EJBException(
1045: "Error in scheduleForBatchCascadeDelete()", e);
1046: }
1047: }
1048:
1049: /**
1050: * Invokes the getRelatedId on the related CMR field via the container
1051: * invocation interceptor chain.
1052: */
1053: private Object invokeGetRelatedId(Transaction tx, Object myId) {
1054: try {
1055: EntityCache instanceCache = (EntityCache) manager
1056: .getContainer().getInstanceCache();
1057: SecurityActions actions = SecurityActions.UTIL
1058: .getSecurityActions();
1059:
1060: CMRInvocation invocation = new CMRInvocation();
1061: invocation.setCmrMessage(CMRMessage.GET_RELATED_ID);
1062: invocation.setEntrancy(Entrancy.NON_ENTRANT);
1063: invocation.setId(instanceCache.createCacheKey(myId));
1064: invocation.setArguments(new Object[] { this });
1065: invocation.setTransaction(tx);
1066: invocation.setPrincipal(actions.getPrincipal());
1067: invocation.setCredential(actions.getCredential());
1068: invocation.setType(InvocationType.LOCAL);
1069: return manager.getContainer().invoke(invocation);
1070: } catch (EJBException e) {
1071: throw e;
1072: } catch (Exception e) {
1073: throw new EJBException("Error in getRelatedId", e);
1074: }
1075: }
1076:
1077: /**
1078: * Invokes the addRelation on the related CMR field via the container
1079: * invocation interceptor chain.
1080: */
1081: private void invokeAddRelation(Transaction tx, Object myId,
1082: Object relatedId) {
1083: try {
1084: EntityCache instanceCache = (EntityCache) manager
1085: .getContainer().getInstanceCache();
1086: SecurityActions actions = SecurityActions.UTIL
1087: .getSecurityActions();
1088:
1089: CMRInvocation invocation = new CMRInvocation();
1090: invocation.setCmrMessage(CMRMessage.ADD_RELATION);
1091: invocation.setEntrancy(Entrancy.NON_ENTRANT);
1092: invocation.setId(instanceCache.createCacheKey(myId));
1093: invocation.setArguments(new Object[] { this , relatedId });
1094: invocation.setTransaction(tx);
1095: invocation.setPrincipal(actions.getPrincipal());
1096: invocation.setCredential(actions.getCredential());
1097: invocation.setType(InvocationType.LOCAL);
1098: manager.getContainer().invoke(invocation);
1099: } catch (EJBException e) {
1100: throw e;
1101: } catch (Exception e) {
1102: throw new EJBException("Error in addRelation", e);
1103: }
1104: }
1105:
1106: /**
1107: * Invokes the removeRelation on the related CMR field via the container
1108: * invocation interceptor chain.
1109: */
1110: private void invokeRemoveRelation(Transaction tx, Object myId,
1111: Object relatedId) {
1112: try {
1113: EntityCache instanceCache = (EntityCache) manager
1114: .getContainer().getInstanceCache();
1115: SecurityActions actions = SecurityActions.UTIL
1116: .getSecurityActions();
1117:
1118: CMRInvocation invocation = new CMRInvocation();
1119: invocation.setCmrMessage(CMRMessage.REMOVE_RELATION);
1120: invocation.setEntrancy(Entrancy.NON_ENTRANT);
1121: invocation.setId(instanceCache.createCacheKey(myId));
1122: invocation.setArguments(new Object[] { this , relatedId });
1123: invocation.setTransaction(tx);
1124: invocation.setPrincipal(actions.getPrincipal());
1125: invocation.setCredential(actions.getCredential());
1126: invocation.setType(InvocationType.LOCAL);
1127: manager.getContainer().invoke(invocation);
1128: } catch (EJBException e) {
1129: throw e;
1130: } catch (Exception e) {
1131: throw new EJBException("Error in removeRelation", e);
1132: }
1133: }
1134:
1135: /**
1136: * Get the related entity's id. This only works on single valued cmr fields.
1137: */
1138: public Object getRelatedId(EntityEnterpriseContext myCtx) {
1139: if (isCollectionValued()) {
1140: throw new EJBException(
1141: "getRelatedId may only be called on a cmr-field with a multiplicity of one.");
1142: }
1143:
1144: load(myCtx);
1145: List value = getFieldState(myCtx).getValue();
1146: return value.isEmpty() ? null : value.get(0);
1147: }
1148:
1149: /**
1150: * Creates a new instance of related id based on foreign key value in the context.
1151: *
1152: * @param ctx - entity's context.
1153: * @return related entity's id.
1154: */
1155: public Object getRelatedIdFromContext(EntityEnterpriseContext ctx) {
1156: Object relatedId = null;
1157: Object fkFieldValue;
1158: for (int i = 0; i < foreignKeyFields.length; ++i) {
1159: JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1160: fkFieldValue = fkField.getInstanceValue(ctx);
1161: if (fkFieldValue == null) {
1162: return null;
1163: }
1164: JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields
1165: .get(fkField);
1166: relatedId = relatedPKField.setPrimaryKeyValue(relatedId,
1167: fkFieldValue);
1168: }
1169: return relatedId;
1170: }
1171:
1172: /**
1173: * Adds the foreign key to the set of related ids, and updates any foreign key fields.
1174: */
1175: public void addRelation(EntityEnterpriseContext myCtx, Object fk) {
1176: addRelation(myCtx, fk, true);
1177: relationManager.addRelation(this , myCtx.getId(),
1178: relatedCMRField, fk);
1179: }
1180:
1181: private void addRelation(EntityEnterpriseContext myCtx, Object fk,
1182: boolean updateForeignKey) {
1183: checkSetForeignKey(myCtx, fk);
1184:
1185: if (isReadOnly()) {
1186: throw new EJBException("Field is read-only: "
1187: + getFieldName());
1188: }
1189:
1190: if (!JDBCEntityBridge.isEjbCreateDone(myCtx)) {
1191: throw new IllegalStateException(
1192: "A CMR field cannot be set or added "
1193: + "to a relationship in ejbCreate; this should be done in the "
1194: + "ejbPostCreate method instead [EJB 2.0 Spec. 10.5.2].");
1195: }
1196:
1197: // add to current related set
1198: FieldState myState = getFieldState(myCtx);
1199: myState.addRelation(fk);
1200:
1201: // set the foreign key, if we have one.
1202: if (hasForeignKey() && updateForeignKey) {
1203: setForeignKey(myCtx, fk);
1204: }
1205: }
1206:
1207: /**
1208: * Removes the foreign key to the set of related ids, and updates any foreign key fields.
1209: */
1210: public void removeRelation(EntityEnterpriseContext myCtx, Object fk) {
1211: removeRelation(myCtx, fk, true, true);
1212: relationManager.removeRelation(this , myCtx.getId(),
1213: relatedCMRField, fk);
1214: }
1215:
1216: private void removeRelation(EntityEnterpriseContext myCtx,
1217: Object fk, boolean updateValueCollection,
1218: boolean updateForeignKey) {
1219: if (isReadOnly()) {
1220: throw new EJBException("Field is read-only: "
1221: + getFieldName());
1222: }
1223:
1224: // remove from current related set
1225: if (updateValueCollection) {
1226: FieldState myState = getFieldState(myCtx);
1227: myState.removeRelation(fk);
1228: }
1229:
1230: // set the foreign key to null, if we have one.
1231: if (hasForeignKey() && updateForeignKey) {
1232: setForeignKey(myCtx, null);
1233: }
1234: }
1235:
1236: /**
1237: * loads the collection of related ids
1238: * NOTE: after loading, the field might not be in a clean state as we support adding and removing
1239: * relations while the field is not loaded. The actual value of the field will be the value loaded
1240: * plus added relations and minus removed relations while the field was not loaded.
1241: */
1242: private void load(EntityEnterpriseContext myCtx) {
1243: // if we are already loaded we're done
1244: FieldState fieldState = getFieldState(myCtx);
1245: if (fieldState.isLoaded()) {
1246: return;
1247: }
1248:
1249: // check the preload cache
1250: if (log.isTraceEnabled()) {
1251: log.trace("Read ahead cahce load: cmrField="
1252: + getFieldName() + " pk=" + myCtx.getId());
1253: }
1254:
1255: manager.getReadAheadCache().load(myCtx);
1256: if (fieldState.isLoaded()) {
1257: return;
1258: }
1259:
1260: // load the value from the database
1261: Collection values;
1262: if (hasForeignKey()) {
1263: // WARN: this method will load foreign keys if they are not yet loaded and
1264: // changes relationship lazy loading in advanced training labs.
1265: // i.e. it will load lazy cmp fields first of this entity and then will lazy load the related entity
1266: // instead of loading this entity JOIN related entity in one query.
1267: //Object fk = getRelatedIdFromContext(myCtx);
1268:
1269: boolean loadWithManager = false;
1270: Object fk = null;
1271: for (int i = 0; i < foreignKeyFields.length; ++i) {
1272: JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1273: // if the field is not loaded then load relationship with manager
1274: if (!fkField.isLoaded(myCtx)) {
1275: loadWithManager = true;
1276: break;
1277: }
1278:
1279: Object fkFieldValue = fkField.getInstanceValue(myCtx);
1280: // if one of the fk is null, the whole fk is considered to be null
1281: if (fkFieldValue == null) {
1282: fk = null;
1283: break;
1284: }
1285: JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields
1286: .get(fkField);
1287: fk = relatedPKField
1288: .setPrimaryKeyValue(fk, fkFieldValue);
1289: }
1290:
1291: if (loadWithManager) {
1292: values = manager.loadRelation(this , myCtx.getId());
1293: } else {
1294: values = (fk == null ? Collections.EMPTY_LIST
1295: : Collections.singletonList(fk));
1296: }
1297: } else {
1298: values = manager.loadRelation(this , myCtx.getId());
1299: }
1300: load(myCtx, values);
1301: }
1302:
1303: public void load(EntityEnterpriseContext myCtx, Collection values) {
1304: // did we get more then one value for a single valued field
1305: if (isSingleValued() && values.size() > 1) {
1306: throw new EJBException(
1307: "Data contains multiple values, but this cmr field is single valued: "
1308: + values);
1309: }
1310:
1311: // add the new values
1312: FieldState fieldState = getFieldState(myCtx);
1313: fieldState.loadRelations(values);
1314:
1315: // set the foreign key, if we have one.
1316: if (hasForeignKey()) {
1317: // update the states and locked values of FK fields
1318: if (!values.isEmpty()) {
1319: Object loadedValue = values.iterator().next();
1320: for (int i = 0; i < foreignKeyFields.length; ++i) {
1321: JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1322: Object fieldValue = fkField
1323: .getPrimaryKeyValue(loadedValue);
1324: fkField.updateState(myCtx, fieldValue);
1325: }
1326: }
1327:
1328: // set the real FK value
1329: List realValue = fieldState.getValue();
1330: Object fk = realValue.isEmpty() ? null : realValue.get(0);
1331: setForeignKey(myCtx, fk);
1332: }
1333:
1334: JDBCEntityBridge.setCreated(myCtx);
1335: }
1336:
1337: /**
1338: * Sets the foreign key field value.
1339: */
1340: public void setForeignKey(EntityEnterpriseContext myCtx, Object fk) {
1341: if (!hasForeignKey()) {
1342: throw new EJBException(getFieldName()
1343: + " CMR field does not have a foreign key to set.");
1344: }
1345:
1346: for (int i = 0; i < foreignKeyFields.length; ++i) {
1347: JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1348: Object fieldValue = fkField.getPrimaryKeyValue(fk);
1349: fkField.setInstanceValue(myCtx, fieldValue);
1350: }
1351: }
1352:
1353: /**
1354: * Initialized the foreign key fields.
1355: */
1356: public void initInstance(EntityEnterpriseContext ctx) {
1357: // mark this field as loaded
1358: getFieldState(ctx).loadRelations(Collections.EMPTY_SET);
1359:
1360: if (foreignKeyFields == null) {
1361: return;
1362: }
1363:
1364: for (int i = 0; i < foreignKeyFields.length; ++i) {
1365: JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
1366: if (!foreignKeyField.isFKFieldMappedToCMPField()) {
1367: foreignKeyField.setInstanceValue(ctx, null);
1368: }
1369: }
1370: }
1371:
1372: /**
1373: * resets the persistence context of the foreign key fields
1374: */
1375: public void resetPersistenceContext(EntityEnterpriseContext ctx) {
1376: // only resetStats if the read has timed out
1377: if (!isReadTimedOut(ctx)) {
1378: return;
1379: }
1380:
1381: // clear the field state
1382: JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1383: // invalidate current field state
1384: /*
1385: FieldState currentFieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
1386: if(currentFieldState != null)
1387: currentFieldState.invalidate();
1388: */
1389: jdbcCtx.setFieldState(jdbcContextIndex, null);
1390:
1391: if (foreignKeyFields == null) {
1392: return;
1393: }
1394:
1395: for (int i = 0; i < foreignKeyFields.length; ++i) {
1396: JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
1397: if (!foreignKeyField.isFKFieldMappedToCMPField()) {
1398: foreignKeyField.resetPersistenceContext(ctx);
1399: }
1400: }
1401: }
1402:
1403: public int setInstanceParameters(PreparedStatement ps,
1404: int parameterIndex, EntityEnterpriseContext ctx) {
1405: if (foreignKeyFields == null) {
1406: return parameterIndex;
1407: }
1408:
1409: List value = getFieldState(ctx).getValue();
1410: Object fk = (value.isEmpty() ? null : value.get(0));
1411:
1412: for (int i = 0; i < foreignKeyFields.length; ++i) {
1413: parameterIndex = foreignKeyFields[i]
1414: .setPrimaryKeyParameters(ps, parameterIndex, fk);
1415: }
1416:
1417: return parameterIndex;
1418: }
1419:
1420: public int loadInstanceResults(ResultSet rs, int parameterIndex,
1421: EntityEnterpriseContext ctx) {
1422: if (!hasForeignKey()) {
1423: return parameterIndex;
1424: }
1425:
1426: // load the value from the database
1427: Object[] ref = new Object[1];
1428: parameterIndex = loadArgumentResults(rs, parameterIndex, ref);
1429:
1430: // only actually set the value if the state is not already loaded
1431: FieldState fieldState = getFieldState(ctx);
1432: if (!fieldState.isLoaded()) {
1433: if (ref[0] != null) {
1434: load(ctx, Collections.singleton(ref[0]));
1435: } else {
1436: load(ctx, Collections.EMPTY_SET);
1437: }
1438: }
1439: return parameterIndex;
1440: }
1441:
1442: public int loadArgumentResults(ResultSet rs, int parameterIndex,
1443: Object[] fkRef) {
1444: if (foreignKeyFields == null) {
1445: return parameterIndex;
1446: }
1447:
1448: boolean fkIsNull = false;
1449:
1450: // value of this field, will be filled in below
1451: Object[] argumentRef = new Object[1];
1452: for (int i = 0; i < foreignKeyFields.length; ++i) {
1453: JDBCCMPFieldBridge field = foreignKeyFields[i];
1454: parameterIndex = field.loadArgumentResults(rs,
1455: parameterIndex, argumentRef);
1456:
1457: if (fkIsNull) {
1458: continue;
1459: }
1460: if (field.getPrimaryKeyField() != null) {
1461: // if there is a null field among FK fields, the whole FK field is considered null.
1462: // NOTE: don't throw exception in this case, it's ok if FK is partly mapped to a PK
1463: // NOTE2: we still need to iterate through foreign key fields and 'load' them to
1464: // return correct parameterIndex.
1465: if (argumentRef[0] == null) {
1466: fkRef[0] = null;
1467: fkIsNull = true;
1468: } else {
1469: // if we don't have a pk object yet create one
1470: if (fkRef[0] == null) {
1471: fkRef[0] = relatedEntity
1472: .createPrimaryKeyInstance();
1473: }
1474: try {
1475: // Set this field's value into the primary key object.
1476: field.getPrimaryKeyField().set(fkRef[0],
1477: argumentRef[0]);
1478: } catch (Exception e) {
1479: // Non recoverable internal exception
1480: throw new EJBException(
1481: "Internal error setting foreign-key field "
1482: + getFieldName(), e);
1483: }
1484: }
1485: } else {
1486: // This field is the primary key, so no extraction is necessary.
1487: fkRef[0] = argumentRef[0];
1488: }
1489: }
1490: return parameterIndex;
1491: }
1492:
1493: /**
1494: * This method is never called.
1495: * In case of a CMR with foreign key fields, only the foreign key fields are asked for the dirty state.
1496: */
1497: public boolean isDirty(EntityEnterpriseContext ctx) {
1498: return foreignKeyFields == null ? relationManager.isDirty()
1499: : false;
1500: }
1501:
1502: public boolean invalidateCache(EntityEnterpriseContext ctx) {
1503: JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1504: FieldState fieldState = (FieldState) jdbcCtx
1505: .getFieldState(jdbcContextIndex);
1506: return fieldState == null ? false : fieldState.isChanged();
1507: }
1508:
1509: /**
1510: * This method is never called.
1511: * In case of a CMR
1512: * - with foreign key fields, the foreign key fields are cleaned when necessary according to CMP fields'
1513: * behaviour.
1514: * - from m:m relationship, added/removed key pairs are cleared in application tx data map on sync.
1515: */
1516: public void setClean(EntityEnterpriseContext ctx) {
1517: throw new UnsupportedOperationException();
1518: }
1519:
1520: public boolean isCMPField() {
1521: return false;
1522: }
1523:
1524: public JDBCEntityPersistenceStore getManager() {
1525: return manager;
1526: }
1527:
1528: public boolean hasFKFieldsMappedToCMPFields() {
1529: return hasFKFieldsMappedToCMPFields;
1530: }
1531:
1532: public void addRelatedPKWaitingForMyPK(Object myPK, Object relatedPK) {
1533: Map relatedPKsWaitingForMyPK = getRelatedPKsWaitingForMyPK();
1534: synchronized (relatedPKsWaitingForMyPK) {
1535: List relatedPKs = (List) relatedPKsWaitingForMyPK.get(myPK);
1536: if (relatedPKs == null) {
1537: relatedPKs = new ArrayList(1);
1538: relatedPKsWaitingForMyPK.put(myPK, relatedPKs);
1539: }
1540: relatedPKs.add(relatedPK);
1541: }
1542: }
1543:
1544: public void removeRelatedPKWaitingForMyPK(Object myPK,
1545: Object relatedPK) {
1546: final Map relatedPKMap = getRelatedPKsWaitingForMyPK();
1547: synchronized (relatedPKMap) {
1548: List relatedPKs = (List) relatedPKMap.get(myPK);
1549: if (relatedPKs != null) {
1550: relatedPKs.remove(relatedPK);
1551: }
1552: }
1553: }
1554:
1555: /**
1556: * Gets the field state object from the persistence context.
1557: */
1558: private FieldState getFieldState(EntityEnterpriseContext ctx) {
1559: JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1560: FieldState fieldState = (FieldState) jdbcCtx
1561: .getFieldState(jdbcContextIndex);
1562: if (fieldState == null) {
1563: fieldState = new FieldState(ctx);
1564: jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
1565: }
1566: return fieldState;
1567: }
1568:
1569: /**
1570: * Initializes foreign key fields
1571: *
1572: * @throws DeploymentException
1573: */
1574: private void initializeForeignKeyFields()
1575: throws DeploymentException {
1576: Collection foreignKeys = metadata.getRelatedRole()
1577: .getKeyFields();
1578:
1579: // temporary map used later to write fk fields in special order
1580: Map fkFieldsByRelatedPKFields = new HashMap();
1581: for (Iterator i = foreignKeys.iterator(); i.hasNext();) {
1582: JDBCCMPFieldMetaData fkFieldMetaData = (JDBCCMPFieldMetaData) i
1583: .next();
1584: JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedEntity
1585: .getFieldByName(fkFieldMetaData.getFieldName());
1586:
1587: // now determine whether the fk is mapped to a pk column
1588: String fkColumnName = fkFieldMetaData.getColumnName();
1589: JDBCCMP2xFieldBridge fkField = null;
1590:
1591: // look among the CMP fields for the field with the same column name
1592: JDBCFieldBridge[] tableFields = entity.getTableFields();
1593: for (int tableInd = 0; tableInd < tableFields.length
1594: && fkField == null; ++tableInd) {
1595: JDBCCMP2xFieldBridge cmpField = (JDBCCMP2xFieldBridge) tableFields[tableInd];
1596: if (fkColumnName.equals(cmpField.getColumnName())) {
1597: hasFKFieldsMappedToCMPFields = true;
1598:
1599: // construct the foreign key field
1600: fkField = new JDBCCMP2xFieldBridge(
1601: (JDBCStoreManager) cmpField.getManager(), // this cmpField's manager
1602: relatedPKField.getFieldName(),
1603: relatedPKField.getFieldType(),
1604: cmpField.getJDBCType(), // this cmpField's jdbc type
1605: relatedPKField.isReadOnly(), relatedPKField
1606: .getReadTimeOut(), relatedPKField
1607: .getPrimaryKeyClass(),
1608: relatedPKField.getPrimaryKeyField(),
1609: cmpField, // CMP field I am mapped to
1610: this , fkColumnName);
1611:
1612: if (cmpField.isPrimaryKeyMember()) {
1613: relatedPKFieldsByMyPKFields.put(cmpField,
1614: relatedPKField);
1615: }
1616: }
1617: }
1618:
1619: // if the fk is not a part of pk then create a new field
1620: if (fkField == null) {
1621: fkField = new JDBCCMP2xFieldBridge(manager,
1622: fkFieldMetaData, manager.getJDBCTypeFactory()
1623: .getJDBCType(fkFieldMetaData));
1624: }
1625:
1626: fkFieldsByRelatedPKFields.put(relatedPKField, fkField); // temporary map
1627: relatedPKFieldsByMyFKFields.put(fkField, relatedPKField);
1628: }
1629:
1630: // Note: this important to order the foreign key fields so that their order matches
1631: // the order of related entity's pk fields in case of complex primary keys.
1632: // The order is important in fk-constraint generation and in SELECT when loading
1633: if (fkFieldsByRelatedPKFields.size() > 0) {
1634: JDBCFieldBridge[] relatedPKFields = relatedEntity
1635: .getPrimaryKeyFields();
1636: List fkList = new ArrayList(relatedPKFields.length);
1637: for (int i = 0; i < relatedPKFields.length; ++i) {
1638: JDBCCMPFieldBridge fkField = (JDBCCMPFieldBridge) fkFieldsByRelatedPKFields
1639: .remove(relatedPKFields[i]);
1640: fkList.add(fkField);
1641: }
1642: foreignKeyFields = (JDBCCMP2xFieldBridge[]) fkList
1643: .toArray(new JDBCCMP2xFieldBridge[fkList.size()]);
1644: } else {
1645: foreignKeyFields = null;
1646: }
1647:
1648: // are all FK fields mapped to PK fields?
1649: allFKFieldsMappedToPKFields = relatedPKFieldsByMyPKFields
1650: .size() > 0
1651: && relatedPKFieldsByMyPKFields.size() == foreignKeyFields.length;
1652:
1653: if (foreignKeyFields != null) {
1654: jdbcType = new CMRJDBCType(Arrays.asList(foreignKeyFields));
1655: }
1656: }
1657:
1658: private Transaction getTransaction() {
1659: try {
1660: EntityContainer container = getJDBCStoreManager()
1661: .getContainer();
1662: TransactionManager tm = container.getTransactionManager();
1663: return tm.getTransaction();
1664: } catch (SystemException e) {
1665: throw new EJBException(
1666: "Error getting transaction from the transaction manager",
1667: e);
1668: }
1669: }
1670:
1671: /**
1672: * @return Map of lists of waiting related PK values keyed by not yet created this side's PK value.
1673: */
1674: private Map getRelatedPKsWaitingForMyPK() {
1675: return (Map) relatedPKValuesWaitingForMyPK.get();
1676: }
1677:
1678: private RelationDataManager initRelationManager(
1679: JDBCCMRFieldBridge relatedField) {
1680: if (relationManager == null) {
1681: if (metadata.getRelationMetaData().isTableMappingStyle()) {
1682: relationManager = new M2MRelationManager(this ,
1683: relatedField);
1684: } else {
1685: relationManager = EMPTY_RELATION_MANAGER;
1686: }
1687: }
1688: return relationManager;
1689: }
1690:
1691: private Object getRelatedPrimaryKey(Object localObject) {
1692: Object relatedId;
1693: if (relatedEntity.getLocalInterface().isAssignableFrom(
1694: localObject.getClass())) {
1695: EJBLocalObject local = (EJBLocalObject) localObject;
1696: try {
1697: relatedId = local.getPrimaryKey();
1698: } catch (NoSuchObjectLocalException e) {
1699: throw new IllegalArgumentException(e.getMessage());
1700: }
1701:
1702: /*
1703: if(relatedManager.wasCascadeDeleted(relatedId))
1704: {
1705: throw new IllegalArgumentException("The instance was cascade-deleted: pk=" + relatedId);
1706: }
1707: */
1708: } else {
1709: throw new IllegalArgumentException(
1710: "The values of this field must be of type "
1711: + relatedEntity.getLocalInterface()
1712: .getName());
1713: }
1714: return relatedId;
1715: }
1716:
1717: public String toString() {
1718: return entity.getEntityName() + '.' + getFieldName();
1719: }
1720:
1721: private final class FieldState {
1722: private final EntityEnterpriseContext ctx;
1723: private List[] setHandle = new List[1];
1724: private Set addedRelations;
1725: private Set removedRelations;
1726: private Set relationSet;
1727: private boolean isLoaded = false;
1728: private final long lastRead = -1;
1729:
1730: private boolean changed;
1731:
1732: public FieldState(EntityEnterpriseContext ctx) {
1733: this .ctx = ctx;
1734: setHandle[0] = new ArrayList();
1735: }
1736:
1737: /**
1738: * Get the current value (list of primary keys).
1739: */
1740: public List getValue() {
1741: if (!isLoaded) {
1742: throw new EJBException("CMR field value not loaded yet");
1743: }
1744: return Collections.unmodifiableList(setHandle[0]);
1745: }
1746:
1747: /**
1748: * Has this relation been loaded.
1749: */
1750: public boolean isLoaded() {
1751: return isLoaded;
1752: }
1753:
1754: /**
1755: * When was this value last read from the datastore.
1756: */
1757: public long getLastRead() {
1758: return lastRead;
1759: }
1760:
1761: /**
1762: * Add this foreign to the relationship.
1763: */
1764: public void addRelation(Object fk) {
1765: if (isLoaded) {
1766: setHandle[0].add(fk);
1767: } else {
1768: if (removedRelations == null) {
1769: removedRelations = new HashSet();
1770: addedRelations = new HashSet();
1771: }
1772: removedRelations.remove(fk);
1773: addedRelations.add(fk);
1774: }
1775:
1776: changed = true;
1777: }
1778:
1779: /**
1780: * Remove this foreign to the relationship.
1781: */
1782: public void removeRelation(Object fk) {
1783: if (isLoaded) {
1784: setHandle[0].remove(fk);
1785: } else {
1786: if (removedRelations == null) {
1787: removedRelations = new HashSet();
1788: addedRelations = new HashSet();
1789: }
1790: addedRelations.remove(fk);
1791: removedRelations.add(fk);
1792: }
1793:
1794: changed = true;
1795: }
1796:
1797: /**
1798: * loads the collection of related ids
1799: */
1800: public void loadRelations(Collection values) {
1801: // check if we are aleready loaded
1802: if (isLoaded) {
1803: throw new EJBException(
1804: "CMR field value is already loaded");
1805: }
1806:
1807: // just in the case where there are lingering values
1808: setHandle[0].clear();
1809:
1810: // add the new values
1811: setHandle[0].addAll(values);
1812:
1813: if (removedRelations != null) {
1814: // remove the already removed values
1815: setHandle[0].removeAll(removedRelations);
1816: removedRelations = null;
1817: }
1818:
1819: if (addedRelations != null) {
1820: // add the already added values
1821: // but remove FKs we are going to add to avoid duplication
1822: setHandle[0].removeAll(addedRelations);
1823: setHandle[0].addAll(addedRelations);
1824: addedRelations = null;
1825: }
1826:
1827: // mark the field loaded
1828: isLoaded = true;
1829: }
1830:
1831: /**
1832: * Get the current relation set or create a new one.
1833: */
1834: public Set getRelationSet() {
1835: if (!isLoaded) {
1836: throw new EJBException("CMR field value not loaded yet");
1837: }
1838:
1839: if (ctx.isReadOnly()) {
1840: // we are in a read-only invocation, so return a snapshot set
1841: return new RelationSet(JDBCCMRFieldBridge.this , ctx,
1842: new List[] { new ArrayList(setHandle[0]) },
1843: true);
1844: }
1845:
1846: // if we already have a relationset use it
1847: if (relationSet != null) {
1848: return relationSet;
1849: }
1850:
1851: // construct a new relationshet
1852: try {
1853: // get the curent transaction
1854: EntityContainer container = getJDBCStoreManager()
1855: .getContainer();
1856: TransactionManager tm = container
1857: .getTransactionManager();
1858: Transaction tx = tm.getTransaction();
1859:
1860: // if whe have a valid transaction...
1861: if (tx != null
1862: && (tx.getStatus() == Status.STATUS_ACTIVE || tx
1863: .getStatus() == Status.STATUS_PREPARING)) {
1864: // crete the relation set and register for a tx callback
1865: relationSet = new RelationSet(
1866: JDBCCMRFieldBridge.this , ctx, setHandle,
1867: false);
1868: TxSynchronization sync = new TxSynchronization(
1869: FieldState.this );
1870: tx.registerSynchronization(sync);
1871: } else {
1872: // if there is no transaction create a pre-failed list
1873: relationSet = new RelationSet(
1874: JDBCCMRFieldBridge.this , ctx, new List[1],
1875: false);
1876: }
1877:
1878: return relationSet;
1879: } catch (SystemException e) {
1880: throw new EJBException(
1881: "Error while creating RelationSet", e);
1882: } catch (RollbackException e) {
1883: throw new EJBException(
1884: "Error while creating RelationSet", e);
1885: }
1886: }
1887:
1888: /**
1889: * Invalidate the current relationship set.
1890: */
1891: public void invalidate() {
1892: // make a new set handle and copy the currentList to the new handle
1893: // this will cause old references to the relationSet to throw an
1894: // IllegalStateException if accesses, but will not cause a reload
1895: // in Commit Option A
1896: List currentList = null;
1897: if (setHandle != null && setHandle.length > 0) {
1898: currentList = setHandle[0];
1899: setHandle[0] = null;
1900: }
1901: setHandle = new List[1];
1902: setHandle[0] = currentList;
1903:
1904: relationSet = null;
1905: changed = false;
1906: }
1907:
1908: public boolean isChanged() {
1909: return changed;
1910: }
1911: }
1912:
1913: private final static class CMRJDBCType implements JDBCType {
1914: private final String[] columnNames;
1915: private final Class[] javaTypes;
1916: private final int[] jdbcTypes;
1917: private final String[] sqlTypes;
1918: private final boolean[] notNull;
1919:
1920: private CMRJDBCType(List fields) {
1921: List columnNamesList = new ArrayList();
1922: List javaTypesList = new ArrayList();
1923: List jdbcTypesList = new ArrayList();
1924: List sqlTypesList = new ArrayList();
1925: List notNullList = new ArrayList();
1926:
1927: for (Iterator iter = fields.iterator(); iter.hasNext();) {
1928: JDBCCMPFieldBridge field = (JDBCCMPFieldBridge) iter
1929: .next();
1930: JDBCType type = field.getJDBCType();
1931: for (int i = 0; i < type.getColumnNames().length; i++) {
1932: columnNamesList.add(type.getColumnNames()[i]);
1933: javaTypesList.add(type.getJavaTypes()[i]);
1934: jdbcTypesList.add(new Integer(
1935: type.getJDBCTypes()[i]));
1936: sqlTypesList.add(type.getSQLTypes()[i]);
1937: notNullList.add(new Boolean(type.getNotNull()[i]));
1938: }
1939: }
1940: columnNames = (String[]) columnNamesList
1941: .toArray(new String[columnNamesList.size()]);
1942: javaTypes = (Class[]) javaTypesList
1943: .toArray(new Class[javaTypesList.size()]);
1944: sqlTypes = (String[]) sqlTypesList
1945: .toArray(new String[sqlTypesList.size()]);
1946:
1947: jdbcTypes = new int[jdbcTypesList.size()];
1948: for (int i = 0; i < jdbcTypes.length; i++) {
1949: jdbcTypes[i] = ((Integer) jdbcTypesList.get(i))
1950: .intValue();
1951: }
1952:
1953: notNull = new boolean[notNullList.size()];
1954: for (int i = 0; i < notNull.length; i++) {
1955: notNull[i] = ((Boolean) notNullList.get(i))
1956: .booleanValue();
1957: }
1958: }
1959:
1960: public String[] getColumnNames() {
1961: return columnNames;
1962: }
1963:
1964: public Class[] getJavaTypes() {
1965: return javaTypes;
1966: }
1967:
1968: public int[] getJDBCTypes() {
1969: return jdbcTypes;
1970: }
1971:
1972: public String[] getSQLTypes() {
1973: return sqlTypes;
1974: }
1975:
1976: public boolean[] getNotNull() {
1977: return notNull;
1978: }
1979:
1980: public boolean[] getAutoIncrement() {
1981: return new boolean[] { false };
1982: }
1983:
1984: public Object getColumnValue(int index, Object value) {
1985: throw new UnsupportedOperationException();
1986: }
1987:
1988: public Object setColumnValue(int index, Object value,
1989: Object columnValue) {
1990: throw new UnsupportedOperationException();
1991: }
1992:
1993: public boolean hasMapper() {
1994: throw new UnsupportedOperationException(
1995: "hasMapper is not implemented.");
1996: }
1997:
1998: public boolean isSearchable() {
1999: throw new UnsupportedOperationException(
2000: "isSearchable is not implemented.");
2001: }
2002:
2003: public JDBCResultSetReader[] getResultSetReaders() {
2004: // foreign key fields has their result set readers
2005: throw new UnsupportedOperationException();
2006: }
2007:
2008: public JDBCParameterSetter[] getParameterSetter() {
2009: throw new UnsupportedOperationException();
2010: }
2011: }
2012:
2013: private final static class TxSynchronization implements
2014: Synchronization {
2015: private final WeakReference fieldStateRef;
2016:
2017: private TxSynchronization(FieldState fieldState) {
2018: if (fieldState == null) {
2019: throw new IllegalArgumentException("fieldState is null");
2020: }
2021: this .fieldStateRef = new WeakReference(fieldState);
2022: }
2023:
2024: public void beforeCompletion() {
2025: // REVIEW: THIS WILL NOT BE INVOKED ON A ROLLBACK
2026: // Be Careful where you put this invalidate
2027: // If you put it in afterCompletion, the beanlock will probably
2028: // be released before the invalidate and you will have a race
2029: FieldState fieldState = (FieldState) fieldStateRef.get();
2030: if (fieldState != null) {
2031: fieldState.invalidate();
2032: }
2033: }
2034:
2035: public void afterCompletion(int status) {
2036: }
2037: }
2038:
2039: public static interface RelationDataManager {
2040: void addRelation(JDBCCMRFieldBridge field, Object id,
2041: JDBCCMRFieldBridge relatedField, Object relatedId);
2042:
2043: void removeRelation(JDBCCMRFieldBridge field, Object id,
2044: JDBCCMRFieldBridge relatedField, Object relatedId);
2045:
2046: boolean isDirty();
2047:
2048: RelationData getRelationData();
2049: }
2050:
2051: private static final RelationDataManager EMPTY_RELATION_MANAGER = new RelationDataManager() {
2052: public void addRelation(JDBCCMRFieldBridge field, Object id,
2053: JDBCCMRFieldBridge relatedField, Object relatedId) {
2054: }
2055:
2056: public void removeRelation(JDBCCMRFieldBridge field, Object id,
2057: JDBCCMRFieldBridge relatedField, Object relatedId) {
2058: }
2059:
2060: public boolean isDirty() {
2061: return false;
2062: }
2063:
2064: public RelationData getRelationData() {
2065: throw new UnsupportedOperationException();
2066: }
2067: };
2068:
2069: public static class M2MRelationManager implements
2070: RelationDataManager {
2071: private final JDBCCMRFieldBridge leftField;
2072: private final JDBCCMRFieldBridge rightField;
2073:
2074: private final TransactionLocal relationData = new TransactionLocal() {
2075: protected Object initialValue() {
2076: return new RelationData(leftField, rightField);
2077: }
2078: };
2079:
2080: public M2MRelationManager(JDBCCMRFieldBridge leftField,
2081: JDBCCMRFieldBridge rightField) {
2082: this .leftField = leftField;
2083: this .rightField = rightField;
2084: }
2085:
2086: public void addRelation(JDBCCMRFieldBridge field, Object id,
2087: JDBCCMRFieldBridge relatedField, Object relatedId) {
2088: final RelationData local = getRelationData();
2089: local.addRelation(field, id, relatedField, relatedId);
2090: }
2091:
2092: public void removeRelation(JDBCCMRFieldBridge field, Object id,
2093: JDBCCMRFieldBridge relatedField, Object relatedId) {
2094: RelationData local = getRelationData();
2095: local.removeRelation(field, id, relatedField, relatedId);
2096: }
2097:
2098: public boolean isDirty() {
2099: RelationData local = getRelationData();
2100: return local.isDirty();
2101: }
2102:
2103: public RelationData getRelationData() {
2104: final RelationData local = (RelationData) relationData
2105: .get();
2106: return local;
2107: }
2108: }
2109:
2110: interface SecurityActions {
2111: class UTIL {
2112: static SecurityActions getSecurityActions() {
2113: return System.getSecurityManager() == null ? NON_PRIVILEGED
2114: : PRIVILEGED;
2115: }
2116: }
2117:
2118: SecurityActions NON_PRIVILEGED = new SecurityActions() {
2119: public Principal getPrincipal() {
2120: return SecurityAssociation.getPrincipal();
2121: }
2122:
2123: public Object getCredential() {
2124: return SecurityAssociation.getCredential();
2125: }
2126: };
2127:
2128: SecurityActions PRIVILEGED = new SecurityActions() {
2129: private final PrivilegedAction getPrincipalAction = new PrivilegedAction() {
2130: public Object run() {
2131: return SecurityAssociation.getPrincipal();
2132: }
2133: };
2134:
2135: private final PrivilegedAction getCredentialAction = new PrivilegedAction() {
2136: public Object run() {
2137: return SecurityAssociation.getCredential();
2138: }
2139: };
2140:
2141: public Principal getPrincipal() {
2142: return (Principal) AccessController
2143: .doPrivileged(getPrincipalAction);
2144: }
2145:
2146: public Object getCredential() {
2147: return AccessController
2148: .doPrivileged(getCredentialAction);
2149: }
2150: };
2151:
2152: Principal getPrincipal();
2153:
2154: Object getCredential();
2155: }
2156: }
|