1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb.plugins.cmp.jdbc.bridge;
23
24 import java.lang.ref.WeakReference;
25 import java.lang.reflect.Method;
26 import javax.sql.DataSource;
27 import java.sql.PreparedStatement;
28 import java.sql.ResultSet;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.HashMap;
38 import java.util.Arrays;
39 import java.rmi.RemoteException;
40 import javax.ejb.EJBException;
41 import javax.ejb.EJBLocalObject;
42 import javax.ejb.EJBLocalHome;
43 import javax.ejb.RemoveException;
44 import javax.ejb.NoSuchObjectLocalException;
45 import javax.transaction.Status;
46 import javax.transaction.Synchronization;
47 import javax.transaction.SystemException;
48 import javax.transaction.Transaction;
49 import javax.transaction.TransactionManager;
50 import javax.transaction.RollbackException;
51
52 import org.jboss.deployment.DeploymentException;
53 import org.jboss.ejb.EntityCache;
54 import org.jboss.ejb.EntityContainer;
55 import org.jboss.ejb.EntityEnterpriseContext;
56 import org.jboss.ejb.LocalProxyFactory;
57 import org.jboss.ejb.plugins.cmp.bridge.EntityBridge;
58 import org.jboss.ejb.plugins.cmp.bridge.FieldBridge;
59 import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
60 import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
61 import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
62 import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
63 import org.jboss.ejb.plugins.cmp.jdbc.CascadeDeleteStrategy;
64 import org.jboss.ejb.plugins.cmp.jdbc.RelationData;
65 import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
66 import org.jboss.ejb.plugins.cmp.jdbc.JDBCParameterSetter;
67 import org.jboss.ejb.plugins.cmp.jdbc.JDBCResultSetReader;
68 import org.jboss.tm.TransactionLocal;
69 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
70 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
71 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
72 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
73 import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
74 import org.jboss.ejb.plugins.lock.Entrancy;
75 import org.jboss.invocation.InvocationType;
76 import org.jboss.logging.Logger;
77 import org.jboss.security.SecurityContext;
78
79 /**
80 * JDBCCMRFieldBridge a bean relationship. This class only supports
81 * relationships between entities managed by a JDBCStoreManager in the same
82 * application.
83 * <p/>
84 * Life-cycle:
85 * Tied to the EntityBridge.
86 * <p/>
87 * Multiplicity:
88 * One for each role that entity has.
89 *
90 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
91 * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
92 * @version $Revision: 62678 $
93 */
94 public final class JDBCCMRFieldBridge extends JDBCAbstractCMRFieldBridge
95 {
96 /**
97 * The entity bridge to which this cmr field belongs.
98 */
99 private final JDBCEntityBridge entity;
100 /**
101 * The manager of this entity.
102 */
103 private final JDBCStoreManager manager;
104 /**
105 * Metadata of the relationship role that this field represents.
106 */
107 private final JDBCRelationshipRoleMetaData metadata;
108 /**
109 * The data source used to acess the relation table if relevant.
110 */
111 private DataSource dataSource;
112 /**
113 * The relation table name if relevent.
114 */
115 private String qualifiedTableName;
116 private String tableName;
117 /**
118 * The key fields that this entity maintains in the relation table.
119 */
120 private JDBCCMP2xFieldBridge[] tableKeyFields;
121 /**
122 * JDBCType for the foreign key fields. Basically, this is an ordered
123 * merge of the JDBCType of the foreign key field.
124 */
125 private JDBCType jdbcType;
126 /**
127 * The related entity's container.
128 */
129 private WeakReference relatedContainerRef;
130 /**
131 * The related entity's jdbc store manager
132 */
133 private JDBCStoreManager relatedManager;
134 /**
135 * The related entity.
136 */
137 private JDBCEntityBridge relatedEntity;
138 /**
139 * The related entity's cmr field for this relationship.
140 */
141 private JDBCCMRFieldBridge relatedCMRField;
142 /**
143 * da log.
144 */
145 private final Logger log;
146
147 /**
148 * Foreign key fields of this entity (i.e., related entities pk fields)
149 */
150 private JDBCCMP2xFieldBridge[] foreignKeyFields;
151 /**
152 * Indicates whether all FK fields are mapped to PK fields
153 */
154 private boolean allFKFieldsMappedToPKFields;
155 /**
156 * This map contains related PK fields that are mapped through FK fields to this entity's PK fields
157 */
158 private final Map relatedPKFieldsByMyPKFields = new HashMap();
159 /**
160 * This map contains related PK fields keyed by FK fields
161 */
162 private final Map relatedPKFieldsByMyFKFields = new HashMap();
163 /**
164 * Indicates whether there are foreign key fields mapped to CMP fields
165 */
166 private boolean hasFKFieldsMappedToCMPFields;
167
168 // Map for lists of related PK values keyed by this side's PK values.
169 // The values are put/removed by related entities when its fields representing
170 // foreign key are changed. When entity with this CMR is created, this map is checked
171 // for waiting for it entities. Relationship with waiting entities is established,
172 // removing waiting entities' primary keys from the map.
173 // NOTE: this map is used only for foreign key fields mapped to CMP fields.
174 private final TransactionLocal relatedPKValuesWaitingForMyPK = new TransactionLocal()
175 {
176 protected Object initialValue()
177 {
178 return new HashMap();
179 }
180 };
181
182 /**
183 * FindByPrimaryKey method used to find related instances in case when FK fields mapped to PK fields
184 */
185 private Method relatedFindByPrimaryKey;
186
187 /**
188 * index of the field in the JDBCContext
189 */
190 private final int jdbcContextIndex;
191
192 /**
193 * cascade-delete strategy
194 */
195 private CascadeDeleteStrategy cascadeDeleteStrategy;
196
197 /**
198 * This CMR field and its related CMR field share the same RelationDataManager
199 */
200 private RelationDataManager relationManager;
201
202 /**
203 * Creates a cmr field for the entity based on the metadata.
204 */
205 public JDBCCMRFieldBridge(JDBCEntityBridge entity,
206 JDBCStoreManager manager,
207 JDBCRelationshipRoleMetaData metadata)
208 throws DeploymentException
209 {
210 this.entity = entity;
211 this.manager = manager;
212 this.metadata = metadata;
213 this.jdbcContextIndex = ((JDBCEntityBridge) manager.getEntityBridge()).getNextJDBCContextIndex();
214
215 // Creat the log
216 String categoryName = this.getClass().getName() +
217 "." + manager.getMetaData().getName() + ".";
218 if(metadata.getCMRFieldName() != null)
219 {
220 categoryName += metadata.getCMRFieldName();
221 }
222 else
223 {
224 categoryName += metadata.getRelatedRole().getEntity().getName() +
225 "-" + metadata.getRelatedRole().getCMRFieldName();
226 }
227 this.log = Logger.getLogger(categoryName);
228 }
229
230 public RelationDataManager getRelationDataManager()
231 {
232 return relationManager;
233 }
234
235 public void resolveRelationship() throws DeploymentException
236 {
237 //
238 // Set handles to the related entity's container, cache,
239 // manager, and invoker
240 //
241
242 // Related Entity Name
243 String relatedEntityName = metadata.getRelatedRole().getEntity().getName();
244
245 // Related Entity
246 Catalog catalog = (Catalog) manager.getApplicationData("CATALOG");
247 relatedEntity = (JDBCEntityBridge) catalog.getEntityByEJBName(relatedEntityName);
248 if(relatedEntity == null)
249 {
250 throw new DeploymentException("Related entity not found: " +
251 "entity=" +
252 entity.getEntityName() +
253 ", " +
254 "cmrField=" +
255 getFieldName() +
256 ", " +
257 "relatedEntity=" + relatedEntityName);
258 }
259
260 // Related CMR Field
261 JDBCCMRFieldBridge[] cmrFields = (JDBCCMRFieldBridge[]) relatedEntity.getCMRFields();
262 for(int i = 0; i < cmrFields.length; ++i)
263 {
264 JDBCCMRFieldBridge cmrField = cmrFields[i];
265 if(metadata.getRelatedRole() == cmrField.getMetaData())
266 {
267 relatedCMRField = cmrField;
268 break;
269 }
270 }
271
272 // if we didn't find the related CMR field throw an exception
273 // with a detailed message
274 if(relatedCMRField == null)
275 {
276 String message = "Related CMR field not found in " +
277 relatedEntity.getEntityName() + " for relationship from";
278
279 message += entity.getEntityName() + ".";
280 if(getFieldName() != null)
281 {
282 message += getFieldName();
283 }
284 else
285 {
286 message += "<no-field>";
287 }
288
289 message += " to ";
290 message += relatedEntityName + ".";
291 if(metadata.getRelatedRole().getCMRFieldName() != null)
292 {
293 message += metadata.getRelatedRole().getCMRFieldName();
294 }
295 else
296 {
297 message += "<no-field>";
298 }
299
300 throw new DeploymentException(message);
301 }
302
303 // Related Manager
304 relatedManager = (JDBCStoreManager) relatedEntity.getManager();
305
306 // Related Container
307 EntityContainer relatedContainer = relatedManager.getContainer();
308 this.relatedContainerRef = new WeakReference(relatedContainer);
309
310 // related findByPrimaryKey
311 Class homeClass = (relatedContainer.getLocalHomeClass() != null ?
312 relatedContainer.getLocalHomeClass() : relatedContainer.getHomeClass());
313 try
314 {
315 relatedFindByPrimaryKey =
316 homeClass.getMethod("findByPrimaryKey", new Class[]{relatedEntity.getPrimaryKeyClass()});
317 }
318 catch(Exception e)
319 {
320 throw new DeploymentException("findByPrimaryKey(" +
321 relatedEntity.getPrimaryKeyClass().getName()
322 + " pk) was not found in " + homeClass.getName());
323 }
324
325 //
326 // Initialize the key fields
327 //
328 if(metadata.getRelationMetaData().isTableMappingStyle())
329 {
330 // initialize relation table key fields
331 Collection tableKeys = metadata.getKeyFields();
332 List keyFieldsList = new ArrayList(tableKeys.size());
333
334 // first phase is to create fk fields
335 Map pkFieldsToFKFields = new HashMap(tableKeys.size());
336 for(Iterator i = tableKeys.iterator(); i.hasNext();)
337 {
338 JDBCCMPFieldMetaData cmpFieldMetaData = (JDBCCMPFieldMetaData) i.next();
339 FieldBridge pkField = entity.getFieldByName(cmpFieldMetaData.getFieldName());
340 if(pkField == null)
341 {
342 throw new DeploymentException("Primary key not found for key-field " + cmpFieldMetaData.getFieldName());
343 }
344 pkFieldsToFKFields.put(pkField, new JDBCCMP2xFieldBridge(manager, cmpFieldMetaData));
345 }
346 // second step is to order fk fields to match the order of pk fields
347 JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
348 for(int i = 0; i < pkFields.length; ++i)
349 {
350 Object fkField = pkFieldsToFKFields.get(pkFields[i]);
351 if(fkField == null)
352 {
353 throw new DeploymentException("Primary key " + pkFields[i].getFieldName() + " is not mapped.");
354 }
355 keyFieldsList.add(fkField);
356 }
357 tableKeyFields = (JDBCCMP2xFieldBridge[]) keyFieldsList.toArray(
358 new JDBCCMP2xFieldBridge[keyFieldsList.size()]);
359
360 dataSource = metadata.getRelationMetaData().getDataSource();
361 }
362 else
363 {
364 initializeForeignKeyFields();
365 dataSource = hasForeignKey() ? entity.getDataSource() : relatedEntity.getDataSource();
366 }
367
368 // Fix table name
369 //
370 // This code doesn't work here... The problem each side will generate
371 // the table name and this will only work for simple generation.
372 qualifiedTableName = SQLUtil.fixTableName(metadata.getRelationMetaData().getDefaultTableName(), dataSource);
373 tableName = SQLUtil.getTableNameWithoutSchema(qualifiedTableName);
374
375 relationManager = relatedCMRField.initRelationManager(this);
376 }
377
378 /**
379 * The third phase of deployment. The method is called when relationships are already resolved.
380 *
381 * @throws DeploymentException
382 */
383 public void start() throws DeploymentException
384 {
385 cascadeDeleteStrategy = CascadeDeleteStrategy.getCascadeDeleteStrategy(this);
386 }
387
388 public boolean removeFromRelations(EntityEnterpriseContext ctx, Object[] oldRelationsRef)
389 {
390 load(ctx);
391
392 FieldState fieldState = getFieldState(ctx);
393 List value = fieldState.getValue();
394
395 boolean removed = false;
396 if(!value.isEmpty())
397 {
398 if(hasFKFieldsMappedToCMPFields)
399 {
400 if(isForeignKeyValid(value.get(0)))
401 {
402 cascadeDeleteStrategy.removedIds(ctx, oldRelationsRef, value);
403 removed = true;
404 }
405 }
406 else
407 {
408 cascadeDeleteStrategy.removedIds(ctx, oldRelationsRef, value);
409 removed = true;
410 }
411 }
412 return removed;
413 }
414
415 public void cascadeDelete(EntityEnterpriseContext ctx, List oldValues)
416 throws RemoveException, RemoteException
417 {
418 cascadeDeleteStrategy.cascadeDelete(ctx, oldValues);
419 }
420
421 public boolean isBatchCascadeDelete()
422 {
423 return (cascadeDeleteStrategy instanceof CascadeDeleteStrategy.BatchCascadeDeleteStrategy);
424 }
425
426 /**
427 * Gets the manager of this entity.
428 */
429 public JDBCStoreManager getJDBCStoreManager()
430 {
431 return manager;
432 }
433
434 /**
435 * Gets bridge for this entity.
436 */
437 public JDBCAbstractEntityBridge getEntity()
438 {
439 return entity;
440 }
441
442 /**
443 * Gets the metadata of the relationship role that this field represents.
444 */
445 public JDBCRelationshipRoleMetaData getMetaData()
446 {
447 return metadata;
448 }
449
450 /**
451 * Gets the relation metadata.
452 */
453 public JDBCRelationMetaData getRelationMetaData()
454 {
455 return metadata.getRelationMetaData();
456 }
457
458 /**
459 * Gets the name of this field.
460 */
461 public String getFieldName()
462 {
463 return metadata.getCMRFieldName();
464 }
465
466 /**
467 * Gets the name of the relation table if relevent.
468 */
469 public String getQualifiedTableName()
470 {
471 return qualifiedTableName;
472 }
473
474 public String getTableName()
475 {
476 return tableName;
477 }
478
479 /**
480 * Gets the datasource of the relation table if relevent.
481 */
482 public DataSource getDataSource()
483 {
484 return dataSource;
485 }
486
487 /**
488 * Gets the read ahead meta data.
489 */
490 public JDBCReadAheadMetaData getReadAhead()
491 {
492 return metadata.getReadAhead();
493 }
494
495 public JDBCType getJDBCType()
496 {
497 return jdbcType;
498 }
499
500 public boolean isPrimaryKeyMember()
501 {
502 return false;
503 }
504
505 /**
506 * Does this cmr field have foreign keys.
507 */
508 public boolean hasForeignKey()
509 {
510 return foreignKeyFields != null;
511 }
512
513 /**
514 * Returns true if all FK fields are mapped to PK fields
515 */
516 public boolean allFkFieldsMappedToPkFields()
517 {
518 return allFKFieldsMappedToPKFields;
519 }
520
521 /**
522 * Is this a collection valued field.
523 */
524 public boolean isCollectionValued()
525 {
526 return metadata.getRelatedRole().isMultiplicityMany();
527 }
528
529 /**
530 * Is this a single valued field.
531 */
532 public boolean isSingleValued()
533 {
534 return metadata.getRelatedRole().isMultiplicityOne();
535 }
536
537 /**
538 * Gets the key fields that this entity maintains in the relation table.
539 */
540 public JDBCFieldBridge[] getTableKeyFields()
541 {
542 return tableKeyFields;
543 }
544
545 /**
546 * Gets the foreign key fields of this entity (i.e., related entities pk fields)
547 */
548 public JDBCFieldBridge[] getForeignKeyFields()
549 {
550 return foreignKeyFields;
551 }
552
553 /**
554 * The related entity's cmr field for this relationship.
555 */
556 public JDBCAbstractCMRFieldBridge getRelatedCMRField()
557 {
558 return relatedCMRField;
559 }
560
561 /**
562 * The related manger.
563 */
564 public JDBCStoreManager getRelatedManager()
565 {
566 return relatedManager;
567 }
568
569 /**
570 * The related entity.
571 */
572 public EntityBridge getRelatedEntity()
573 {
574 return relatedEntity;
575 }
576
577 /**
578 * The related entity.
579 */
580 public JDBCEntityBridge getRelatedJDBCEntity()
581 {
582 return relatedEntity;
583 }
584
585 /**
586 * The related container
587 */
588 private final EntityContainer getRelatedContainer()
589 {
590 return (EntityContainer) relatedContainerRef.get();
591 }
592
593 /**
594 * The related entity's local home interface.
595 */
596 public final Class getRelatedLocalInterface()
597 {
598 return getRelatedContainer().getLocalClass();
599 }
600
601 /**
602 * The related entity's local container invoker.
603 */
604 public final LocalProxyFactory getRelatedInvoker()
605 {
606 return getRelatedContainer().getLocalProxyFactory();
607 }
608
609 /**
610 * @param ctx - entity's context
611 * @return true if entity is loaded, false - otherwise.
612 */
613 public boolean isLoaded(EntityEnterpriseContext ctx)
614 {
615 return getFieldState(ctx).isLoaded;
616 }
617
618 /**
619 * Establishes relationships with related entities waited for passed in context
620 * to be created.
621 *
622 * @param ctx - entity's context.
623 */
624 public void addRelatedPKsWaitedForMe(EntityEnterpriseContext ctx)
625 {
626 final Map relatedPKsMap = getRelatedPKsWaitingForMyPK();
627 synchronized(relatedPKsMap)
628 {
629 List relatedPKsWaitingForMe = (List) relatedPKsMap.get(ctx.getId());
630 if(relatedPKsWaitingForMe != null)
631 {
632 for(Iterator waitingPKsIter = relatedPKsWaitingForMe.iterator(); waitingPKsIter.hasNext();)
633 {
634 Object waitingPK = waitingPKsIter.next();
635 waitingPKsIter.remove();
636 if(isForeignKeyValid(waitingPK))
637 {
638 createRelationLinks(ctx, waitingPK);
639 }
640 }
641 }
642 }
643 }
644
645 /**
646 * Is this field readonly?
647 */
648 public boolean isReadOnly()
649 {
650 return getRelationMetaData().isReadOnly();
651 }
652
653 /**
654 * Had the read time expired?
655 */
656 public boolean isReadTimedOut(EntityEnterpriseContext ctx)
657 {
658 // if we are read/write then we are always timed out
659 if(!isReadOnly())
660 {
661 return true;
662 }
663
664 // if read-time-out is -1 then we never time out.
665 if(getRelationMetaData().getReadTimeOut() == -1)
666 {
667 return false;
668 }
669
670 long readInterval = System.currentTimeMillis() - getFieldState(ctx).getLastRead();
671 return readInterval > getRelationMetaData().getReadTimeOut();
672 }
673
674 /**
675 * @param ctx - entity's context.
676 * @return the value of this field.
677 */
678 public Object getValue(EntityEnterpriseContext ctx)
679 {
680 // no user checks yet, but this is where they would go
681 return getInstanceValue(ctx);
682 }
683
684 /**
685 * Sets new value.
686 *
687 * @param ctx - entity's context;
688 * @param value - new value.
689 */
690 public void setValue(EntityEnterpriseContext ctx, Object value)
691 {
692 if(isReadOnly())
693 {
694 throw new EJBException("Field is read-only: fieldName=" + getFieldName());
695 }
696
697 if(!JDBCEntityBridge.isEjbCreateDone(ctx))
698 {
699 throw new IllegalStateException("A CMR field cannot be set " +
700 "in ejbCreate; this should be done in the ejbPostCreate " +
701 "method instead [EJB 2.0 Spec. 10.5.2].");
702 }
703
704 if(isCollectionValued() && value == null)
705 {
706 throw new IllegalArgumentException("null cannot be assigned to a " +
707 "collection-valued cmr-field [EJB 2.0 Spec. 10.3.8].");
708 }
709 /*
710 if(allFKFieldsMappedToPKFields)
711 {
712 throw new IllegalStateException(
713 "Can't modify relationship: CMR field "
714 + entity.getEntityName() + "." + getFieldName()
715 + " has foreign key fields mapped to the primary key columns."
716 + " Primary key may only be set once in ejbCreate [EJB 2.0 Spec. 10.3.5].");
717 }
718 */
719
720 setInstanceValue(ctx, value);
721 }
722
723 /**
724 * Gets the value of the cmr field for the instance associated with
725 * the context.
726 */
727 public Object getInstanceValue(EntityEnterpriseContext myCtx)
728 {
729 load(myCtx);
730
731 FieldState fieldState = getFieldState(myCtx);
732 if(isCollectionValued())
733 {
734 return fieldState.getRelationSet();
735 }
736
737 // only return one
738 try
739 {
740 List value = fieldState.getValue();
741 if(!value.isEmpty())
742 {
743 Object fk = value.get(0);
744 return getRelatedEntityByFK(fk);
745 }
746 else if(foreignKeyFields != null)
747 {
748 // for those completely mapped to CMP fields and created in this current tx !!!
749 Object relatedId = getRelatedIdFromContext(myCtx);
750 if(relatedId != null)
751 {
752 return getRelatedEntityByFK(relatedId);
753 }
754 }
755 return null;
756 }
757 catch(EJBException e)
758 {
759 throw e;
760 }
761 catch(Exception e)
762 {
763 throw new EJBException(e);
764 }
765 }
766
767 /**
768 * Returns related entity's local interface.
769 * If there are foreign key fields mapped to CMP fields, existence of related entity is checked
770 * with findByPrimaryKey and if, in this case, related instance is not found, null is returned.
771 * If foreign key fields mapped to its own columns then existence of related entity is not checked
772 * and just its local object is returned.
773 *
774 * @param fk - foreign key value.
775 * @return related local object instance.
776 */
777 public EJBLocalObject getRelatedEntityByFK(Object fk)
778 {
779 EJBLocalObject relatedLocalObject = null;
780 final EntityContainer relatedContainer = getRelatedContainer();
781
782 if(hasFKFieldsMappedToCMPFields
783 && relatedManager.getReadAheadCache().getPreloadDataMap(fk, false) == null // not in preload cache
784 )
785 {
786 EJBLocalHome relatedHome = relatedContainer.getLocalProxyFactory().getEJBLocalHome();
787 try
788 {
789 relatedLocalObject = (EJBLocalObject) relatedFindByPrimaryKey.invoke(relatedHome, new Object[]{fk});
790 }
791 catch(Exception ignore)
792 {
793 // no such entity. it is ok to ignore
794 }
795 }
796 else
797 {
798 relatedLocalObject = relatedContainer.getLocalProxyFactory().getEntityEJBLocalObject(fk);
799 }
800
801 return relatedLocalObject;
802 }
803
804 /**
805 * This method is called only for CMR fields with foreign key fields mapped to CMP fields
806 * to check the validity of the foreign key value.
807 *
808 * @param fk the foreign key to check
809 * @return true if there is related entity with the equal primary key
810 */
811 public boolean isForeignKeyValid(Object fk)
812 {
813 boolean valid;
814 if(relatedManager.getReadAheadCache().getPreloadDataMap(fk, false) != null)
815 {
816 valid = true;
817 }
818 else
819 {
820 EJBLocalHome relatedHome = getRelatedContainer().getLocalProxyFactory().getEJBLocalHome();
821 try
822 {
823 relatedFindByPrimaryKey.invoke(relatedHome, new Object[]{fk});
824 valid = true;
825 }
826 catch(Exception ignore)
827 {
828 // no such entity. it is ok to ignore
829 valid = false;
830 }
831 }
832 return valid;
833 }
834
835 /**
836 * Sets the value of the cmr field for the instance associated with
837 * the context.
838 */
839 public void setInstanceValue(EntityEnterpriseContext myCtx, Object newValue)
840 {
841 // validate new value first
842 List newPks;
843 if(newValue instanceof Collection)
844 {
845 Collection col = (Collection) newValue;
846 if(!col.isEmpty())
847 {
848 newPks = new ArrayList(col.size());
849 for(Iterator iter = col.iterator(); iter.hasNext();)
850 {
851 Object localObject = iter.next();
852 if(localObject != null)
853 {
854 Object relatedId = getRelatedPrimaryKey(localObject);
855
856 // check whether new value modifies the primary key if there are FK fields mapped to PK fields
857 if(relatedPKFieldsByMyPKFields.size() > 0)
858 {
859 checkSetForeignKey(myCtx, relatedId);
860 }
861
862 newPks.add(relatedId);
863 }
864 }
865 }
866 else
867 {
868 newPks = Collections.EMPTY_LIST;
869 }
870 }
871 else
872 {
873 if(newValue != null)
874 {
875 newPks = Collections.singletonList(getRelatedPrimaryKey(newValue));
876 }
877 else
878 {
879 newPks = Collections.EMPTY_LIST;
880 }
881 }
882
883 // load the current value
884 load(myCtx);
885 FieldState fieldState = getFieldState(myCtx);
886
887 // is this just setting our own relation set back
888 if(newValue == fieldState.getRelationSet())
889 {
890 return;
891 }
892
893 try
894 {
895 // Remove old value(s)
896 List value = fieldState.getValue();
897 if(!value.isEmpty())
898 {
899 Object[] curPks = value.toArray(new Object[value.size()]);
900 for(int i = 0; i < curPks.length; ++i)
901 {
902 destroyRelationLinks(myCtx, curPks[i]);
903 }
904 }
905
906 // Add new value(s)
907 for(int i = 0; i < newPks.size(); ++i)
908 {
909 createRelationLinks(myCtx, newPks.get(i));
910 }
911 }
912 catch(RuntimeException e)
913 {
914 throw e;
915 }
916 catch(Exception e)
917 {
918 throw new EJBException(e);
919 }
920 }
921
922 /**
923 * Checks whether new foreign key value conflicts with primary key value
924 * in case of foreign key to primary key mapping.
925 *
926 * @param myCtx - entity's context;
927 * @param newValue - new foreign key value.
928 * @throws IllegalStateException - if new foreign key value changes
929 * primary key value, otherwise returns silently.
930 */
931 private void checkSetForeignKey(EntityEnterpriseContext myCtx, Object newValue)
932 throws IllegalStateException
933 {
934 JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
935 for(int i = 0; i < pkFields.length; ++i)
936 {
937 JDBCCMP2xFieldBridge pkField = (JDBCCMP2xFieldBridge) pkFields[i];
938 JDBCCMP2xFieldBridge relatedPkField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyPKFields.get(pkField);
939 if(relatedPkField != null)
940 {
941 Object comingValue = relatedPkField.getPrimaryKeyValue(newValue);
942 Object currentValue = pkField.getInstanceValue(myCtx);
943
944 // they shouldn't be null
945 if(!comingValue.equals(currentValue))
946 {
947 throw new IllegalStateException("Can't create relationship: CMR field "
948 +
949 entity.getEntityName() +
950 "." +
951 getFieldName()
952 +
953 " has foreign key fields mapped to the primary key columns."
954 +
955 " Primary key may only be set once in ejbCreate [EJB 2.0 Spec. 10.3.5]."
956 +
957 " primary key value is " +
958 currentValue
959 + " overriding value is " + comingValue);
960 }
961 }
962 }
963 }
964
965 /**
966 * Creates the relation links between the instance associated with the
967 * context and the related instance (just the id is passed in).
968 * <p/>
969 * This method calls a.addRelation(b) and b.addRelation(a)
970 */
971 public void createRelationLinks(EntityEnterpriseContext myCtx, Object relatedId)
972 {
973 createRelationLinks(myCtx, relatedId, true);
974 }
975
976 public void createRelationLinks(EntityEnterpriseContext myCtx, Object relatedId, boolean updateForeignKey)
977 {
978 if(isReadOnly())
979 {
980 throw new EJBException("Field is read-only: " + getFieldName());
981 }
982
983 // If my multiplicity is one, then we need to free the new related context
984 // from its old relationship.
985 Transaction tx = getTransaction();
986 if(metadata.isMultiplicityOne())
987 {
988 Object oldRelatedId = relatedCMRField.invokeGetRelatedId(tx, relatedId);
989 if(oldRelatedId != null)
990 {
991 invokeRemoveRelation(tx, oldRelatedId, relatedId);
992 relatedCMRField.invokeRemoveRelation(tx, relatedId, oldRelatedId);
993 }
994 }
995
996 addRelation(myCtx, relatedId, updateForeignKey);
997 relatedCMRField.invokeAddRelation(tx, relatedId, myCtx.getId());
998 }
999
1000 /**
1001 * Destroys the relation links between the instance associated with the
1002 * context and the related instance (just the id is passed in).
1003 * <p/>
1004 * This method calls a.removeRelation(b) and b.removeRelation(a)
1005 */
1006 public void destroyRelationLinks(EntityEnterpriseContext myCtx, Object relatedId)
1007 {
1008 destroyRelationLinks(myCtx, relatedId, true);
1009 }
1010
1011 /**
1012 * Destroys the relation links between the instance associated with the
1013 * context and the related instance (just the id is passed in).
1014 * <p/>
1015 * This method calls a.removeRelation(b) and b.removeRelation(a)
1016 * <p/>
1017 * If updateValueCollection is false, the related id collection is not
1018 * updated. This form is only used by the RelationSet iterator.
1019 */
1020 public void destroyRelationLinks(EntityEnterpriseContext myCtx,
1021 Object relatedId,
1022 boolean updateValueCollection)
1023 {
1024 destroyRelationLinks(myCtx, relatedId, updateValueCollection, true);
1025 }
1026
1027 public void destroyRelationLinks(EntityEnterpriseContext myCtx,
1028 Object relatedId,
1029 boolean updateValueCollection,
1030 boolean updateForeignKey)
1031 {
1032 if(isReadOnly())
1033 {
1034 throw new EJBException("Field is read-only: " + getFieldName());
1035 }
1036
1037 removeRelation(myCtx, relatedId, updateValueCollection, updateForeignKey);
1038 relatedCMRField.invokeRemoveRelation(getTransaction(), relatedId, myCtx.getId());
1039 }
1040
1041 /**
1042 * Schedules children for cascade delete.
1043 */
1044 public void scheduleChildrenForCascadeDelete(EntityEnterpriseContext ctx)
1045 {
1046 load(ctx);
1047 FieldState fieldState = getFieldState(ctx);
1048 List value = fieldState.getValue();
1049 if(!value.isEmpty())
1050 {
1051 Transaction tx = getTransaction();
1052 for(int i = 0; i < value.size(); ++i)
1053 {
1054 relatedCMRField.invokeScheduleForCascadeDelete(tx, value.get(i));
1055 }
1056 }
1057 }
1058
1059 /**
1060 * Schedules children for batch cascade delete.
1061 */
1062 public void scheduleChildrenForBatchCascadeDelete(EntityEnterpriseContext ctx)
1063 {
1064 load(ctx);
1065 FieldState fieldState = getFieldState(ctx);
1066 List value = fieldState.getValue();
1067 if(!value.isEmpty())
1068 {
1069 Transaction tx = getTransaction();
1070 for(int i = 0; i < value.size(); ++i)
1071 {
1072 relatedCMRField.invokeScheduleForBatchCascadeDelete(tx, value.get(i));
1073 }
1074 }
1075 }
1076
1077 /**
1078 * Schedules the instance with myId for cascade delete.
1079 */
1080 private Object invokeScheduleForCascadeDelete(Transaction tx, Object myId)
1081 {
1082 try
1083 {
1084 EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
1085 SecurityContext sc = SecurityActions.getSecurityContext();
1086
1087 CMRInvocation invocation = new CMRInvocation();
1088 invocation.setCmrMessage(CMRMessage.SCHEDULE_FOR_CASCADE_DELETE);
1089 invocation.setEntrancy(Entrancy.NON_ENTRANT);
1090 invocation.setId(instanceCache.createCacheKey(myId));
1091 invocation.setArguments(new Object[]{this});
1092 invocation.setTransaction(tx);
1093 invocation.setPrincipal(sc.getUtil().getUserPrincipal());
1094 invocation.setCredential(sc.getUtil().getCredential());
1095 invocation.setType(InvocationType.LOCAL);
1096 return manager.getContainer().invoke(invocation);
1097 }
1098 catch(EJBException e)
1099 {
1100 throw e;
1101 }
1102 catch(Exception e)
1103 {
1104 throw new EJBException("Error in scheduleForCascadeDelete()", e);
1105 }
1106 }
1107
1108 /**
1109 * Schedules the instance with myId for batch cascade delete.
1110 */
1111 private Object invokeScheduleForBatchCascadeDelete(Transaction tx, Object myId)
1112 {
1113 try
1114 {
1115 EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
1116 SecurityContext sc = SecurityActions.getSecurityContext();
1117
1118 CMRInvocation invocation = new CMRInvocation();
1119 invocation.setCmrMessage(CMRMessage.SCHEDULE_FOR_BATCH_CASCADE_DELETE);
1120 invocation.setEntrancy(Entrancy.NON_ENTRANT);
1121 invocation.setId(instanceCache.createCacheKey(myId));
1122 invocation.setArguments(new Object[]{this});
1123 invocation.setTransaction(tx);
1124 invocation.setPrincipal(sc.getUtil().getUserPrincipal());
1125 invocation.setCredential(sc.getUtil().getCredential());
1126 invocation.setType(InvocationType.LOCAL);
1127 return manager.getContainer().invoke(invocation);
1128 }
1129 catch(EJBException e)
1130 {
1131 throw e;
1132 }
1133 catch(Exception e)
1134 {
1135 throw new EJBException("Error in scheduleForBatchCascadeDelete()", e);
1136 }
1137 }
1138
1139 /**
1140 * Invokes the getRelatedId on the related CMR field via the container
1141 * invocation interceptor chain.
1142 */
1143 private Object invokeGetRelatedId(Transaction tx, Object myId)
1144 {
1145 try
1146 {
1147 EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
1148 SecurityContext sc = SecurityActions.getSecurityContext();
1149
1150 CMRInvocation invocation = new CMRInvocation();
1151 invocation.setCmrMessage(CMRMessage.GET_RELATED_ID);
1152 invocation.setEntrancy(Entrancy.NON_ENTRANT);
1153 invocation.setId(instanceCache.createCacheKey(myId));
1154 invocation.setArguments(new Object[]{this});
1155 invocation.setTransaction(tx);
1156 invocation.setPrincipal(sc.getUtil().getUserPrincipal());
1157 invocation.setCredential(sc.getUtil().getCredential());
1158 invocation.setType(InvocationType.LOCAL);
1159 return manager.getContainer().invoke(invocation);
1160 }
1161 catch(EJBException e)
1162 {
1163 throw e;
1164 }
1165 catch(Exception e)
1166 {
1167 throw new EJBException("Error in getRelatedId", e);
1168 }
1169 }
1170
1171 /**
1172 * Invokes the addRelation on the related CMR field via the container
1173 * invocation interceptor chain.
1174 */
1175 private void invokeAddRelation(Transaction tx, Object myId, Object relatedId)
1176 {
1177 try
1178 {
1179 SecurityContext sc = SecurityActions.getSecurityContext();
1180 EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
1181
1182 CMRInvocation invocation = new CMRInvocation();
1183 invocation.setCmrMessage(CMRMessage.ADD_RELATION);
1184 invocation.setEntrancy(Entrancy.NON_ENTRANT);
1185 invocation.setId(instanceCache.createCacheKey(myId));
1186 invocation.setArguments(new Object[]{this, relatedId});
1187 invocation.setTransaction(tx);
1188 invocation.setPrincipal(sc.getUtil().getUserPrincipal());
1189 invocation.setCredential(sc.getUtil().getCredential());
1190 invocation.setType(InvocationType.LOCAL);
1191 manager.getContainer().invoke(invocation);
1192 }
1193 catch(EJBException e)
1194 {
1195 throw e;
1196 }
1197 catch(Exception e)
1198 {
1199 throw new EJBException("Error in addRelation", e);
1200 }
1201 }
1202
1203 /**
1204 * Invokes the removeRelation on the related CMR field via the container
1205 * invocation interceptor chain.
1206 */
1207 private void invokeRemoveRelation(Transaction tx, Object myId, Object relatedId)
1208 {
1209 try
1210 {
1211 EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
1212 SecurityContext sc = SecurityActions.getSecurityContext();
1213
1214 CMRInvocation invocation = new CMRInvocation();
1215 invocation.setCmrMessage(CMRMessage.REMOVE_RELATION);
1216 invocation.setEntrancy(Entrancy.NON_ENTRANT);
1217 invocation.setId(instanceCache.createCacheKey(myId));
1218 invocation.setArguments(new Object[]{this, relatedId});
1219 invocation.setTransaction(tx);
1220 invocation.setPrincipal(sc.getUtil().getUserPrincipal());
1221 invocation.setCredential(sc.getUtil().getCredential());
1222 invocation.setType(InvocationType.LOCAL);
1223 manager.getContainer().invoke(invocation);
1224 }
1225 catch(EJBException e)
1226 {
1227 throw e;
1228 }
1229 catch(Exception e)
1230 {
1231 throw new EJBException("Error in removeRelation", e);
1232 }
1233 }
1234
1235 /**
1236 * Get the related entity's id. This only works on single valued cmr fields.
1237 */
1238 public Object getRelatedId(EntityEnterpriseContext myCtx)
1239 {
1240 if(isCollectionValued())
1241 {
1242 throw new EJBException("getRelatedId may only be called on a cmr-field with a multiplicity of one.");
1243 }
1244
1245 load(myCtx);
1246 List value = getFieldState(myCtx).getValue();
1247 return value.isEmpty() ? null : value.get(0);
1248 }
1249
1250 /**
1251 * Creates a new instance of related id based on foreign key value in the context.
1252 *
1253 * @param ctx - entity's context.
1254 * @return related entity's id.
1255 */
1256 public Object getRelatedIdFromContext(EntityEnterpriseContext ctx)
1257 {
1258 Object relatedId = null;
1259 Object fkFieldValue;
1260 for(int i = 0; i < foreignKeyFields.length; ++i)
1261 {
1262 JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1263 fkFieldValue = fkField.getInstanceValue(ctx);
1264 if(fkFieldValue == null)
1265 {
1266 return null;
1267 }
1268 JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields.get(fkField);
1269 relatedId = relatedPKField.setPrimaryKeyValue(relatedId, fkFieldValue);
1270 }
1271 return relatedId;
1272 }
1273
1274 /**
1275 * Adds the foreign key to the set of related ids, and updates any foreign key fields.
1276 */
1277 public void addRelation(EntityEnterpriseContext myCtx, Object fk)
1278 {
1279 addRelation(myCtx, fk, true);
1280 relationManager.addRelation(this, myCtx.getId(), relatedCMRField, fk);
1281 }
1282
1283 private void addRelation(EntityEnterpriseContext myCtx, Object fk, boolean updateForeignKey)
1284 {
1285 checkSetForeignKey(myCtx, fk);
1286
1287 if(isReadOnly())
1288 {
1289 throw new EJBException("Field is read-only: " + getFieldName());
1290 }
1291
1292 if(!JDBCEntityBridge.isEjbCreateDone(myCtx))
1293 {
1294 throw new IllegalStateException("A CMR field cannot be set or added " +
1295 "to a relationship in ejbCreate; this should be done in the " +
1296 "ejbPostCreate method instead [EJB 2.0 Spec. 10.5.2].");
1297 }
1298
1299 // add to current related set
1300 FieldState myState = getFieldState(myCtx);
1301 myState.addRelation(fk);
1302
1303 // set the foreign key, if we have one.
1304 if(hasForeignKey() && updateForeignKey)
1305 {
1306 setForeignKey(myCtx, fk);
1307 }
1308 }
1309
1310 /**
1311 * Removes the foreign key to the set of related ids, and updates any foreign key fields.
1312 */
1313 public void removeRelation(EntityEnterpriseContext myCtx, Object fk)
1314 {
1315 removeRelation(myCtx, fk, true, true);
1316 relationManager.removeRelation(this, myCtx.getId(), relatedCMRField, fk);
1317 }
1318
1319 private void removeRelation(EntityEnterpriseContext myCtx,
1320 Object fk,
1321 boolean updateValueCollection,
1322 boolean updateForeignKey)
1323 {
1324 if(isReadOnly())
1325 {
1326 throw new EJBException("Field is read-only: " + getFieldName());
1327 }
1328
1329 // remove from current related set
1330 if(updateValueCollection)
1331 {
1332 FieldState myState = getFieldState(myCtx);
1333 myState.removeRelation(fk);
1334 }
1335
1336 // set the foreign key to null, if we have one.
1337 if(hasForeignKey() && updateForeignKey)
1338 {
1339 setForeignKey(myCtx, null);
1340 }
1341 }
1342
1343 /**
1344 * loads the collection of related ids
1345 * NOTE: after loading, the field might not be in a clean state as we support adding and removing
1346 * relations while the field is not loaded. The actual value of the field will be the value loaded
1347 * plus added relations and minus removed relations while the field was not loaded.
1348 */
1349 private void load(EntityEnterpriseContext myCtx)
1350 {
1351 // if we are already loaded we're done
1352 FieldState fieldState = getFieldState(myCtx);
1353 if(fieldState.isLoaded())
1354 {
1355 return;
1356 }
1357
1358 // check the preload cache
1359 if(log.isTraceEnabled())
1360 {
1361 log.trace("Read ahead cahce load: cmrField=" + getFieldName() + " pk=" + myCtx.getId());
1362 }
1363
1364 manager.getReadAheadCache().load(myCtx);
1365 if(fieldState.isLoaded())
1366 {
1367 return;
1368 }
1369
1370 // load the value from the database
1371 Collection values;
1372 if(hasForeignKey())
1373 {
1374 // WARN: this method will load foreign keys if they are not yet loaded and
1375 // changes relationship lazy loading in advanced training labs.
1376 // i.e. it will load lazy cmp fields first of this entity and then will lazy load the related entity
1377 // instead of loading this entity JOIN related entity in one query.
1378 //Object fk = getRelatedIdFromContext(myCtx);
1379
1380 boolean loadWithManager = false;
1381 Object fk = null;
1382 for(int i = 0; i < foreignKeyFields.length; ++i)
1383 {
1384 JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1385 // if the field is not loaded then load relationship with manager
1386 if(!fkField.isLoaded(myCtx))
1387 {
1388 loadWithManager = true;
1389 break;
1390 }
1391
1392 Object fkFieldValue = fkField.getInstanceValue(myCtx);
1393 // if one of the fk is null, the whole fk is considered to be null
1394 if(fkFieldValue == null)
1395 {
1396 fk = null;
1397 break;
1398 }
1399 JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields.get(fkField);
1400 fk = relatedPKField.setPrimaryKeyValue(fk, fkFieldValue);
1401 }
1402
1403 if(loadWithManager)
1404 {
1405 values = manager.loadRelation(this, myCtx.getId());
1406 }
1407 else
1408 {
1409 values = (fk == null ? Collections.EMPTY_LIST : Collections.singletonList(fk));
1410 }
1411 }
1412 else
1413 {
1414 values = manager.loadRelation(this, myCtx.getId());
1415 }
1416 load(myCtx, values);
1417 }
1418
1419 public void load(EntityEnterpriseContext myCtx, Collection values)
1420 {
1421 // did we get more then one value for a single valued field
1422 if(isSingleValued() && values.size() > 1)
1423 {
1424 throw new EJBException("Data contains multiple values, but this cmr field is single valued: " + values);
1425 }
1426
1427 // add the new values
1428 FieldState fieldState = getFieldState(myCtx);
1429 fieldState.loadRelations(values);
1430
1431 // set the foreign key, if we have one.
1432 if(hasForeignKey())
1433 {
1434 // update the states and locked values of FK fields
1435 if(!values.isEmpty())
1436 {
1437 Object loadedValue = values.iterator().next();
1438 for(int i = 0; i < foreignKeyFields.length; ++i)
1439 {
1440 JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1441 Object fieldValue = fkField.getPrimaryKeyValue(loadedValue);
1442 fkField.updateState(myCtx, fieldValue);
1443 }
1444 }
1445
1446 // set the real FK value
1447 List realValue = fieldState.getValue();
1448 Object fk = realValue.isEmpty() ? null : realValue.get(0);
1449 setForeignKey(myCtx, fk);
1450 }
1451
1452 JDBCEntityBridge.setCreated(myCtx);
1453 }
1454
1455 /**
1456 * Sets the foreign key field value.
1457 */
1458 public void setForeignKey(EntityEnterpriseContext myCtx, Object fk)
1459 {
1460 if(!hasForeignKey())
1461 {
1462 throw new EJBException(getFieldName() + " CMR field does not have a foreign key to set.");
1463 }
1464
1465 for(int i = 0; i < foreignKeyFields.length; ++i)
1466 {
1467 JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
1468 Object fieldValue = fkField.getPrimaryKeyValue(fk);
1469 fkField.setInstanceValue(myCtx, fieldValue);
1470 }
1471 }
1472
1473 /**
1474 * Initialized the foreign key fields.
1475 */
1476 public void initInstance(EntityEnterpriseContext ctx)
1477 {
1478 // mark this field as loaded
1479 getFieldState(ctx).loadRelations(Collections.EMPTY_SET);
1480
1481 if(foreignKeyFields == null)
1482 {
1483 return;
1484 }
1485
1486 for(int i = 0; i < foreignKeyFields.length; ++i)
1487 {
1488 JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
1489 if(!foreignKeyField.isFKFieldMappedToCMPField())
1490 {
1491 foreignKeyField.setInstanceValue(ctx, null);
1492 }
1493 }
1494 }
1495
1496 /**
1497 * resets the persistence context of the foreign key fields
1498 */
1499 public void resetPersistenceContext(EntityEnterpriseContext ctx)
1500 {
1501 // only resetStats if the read has timed out
1502 if(!isReadTimedOut(ctx))
1503 {
1504 return;
1505 }
1506
1507 // clear the field state
1508 JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1509 // invalidate current field state
1510 /*
1511 FieldState currentFieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
1512 if(currentFieldState != null)
1513 currentFieldState.invalidate();
1514 */
1515 jdbcCtx.setFieldState(jdbcContextIndex, null);
1516
1517 if(foreignKeyFields == null)
1518 {
1519 return;
1520 }
1521
1522 for(int i = 0; i < foreignKeyFields.length; ++i)
1523 {
1524 JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
1525 if(!foreignKeyField.isFKFieldMappedToCMPField())
1526 {
1527 foreignKeyField.resetPersistenceContext(ctx);
1528 }
1529 }
1530 }
1531
1532 public int setInstanceParameters(PreparedStatement ps,
1533 int parameterIndex,
1534 EntityEnterpriseContext ctx)
1535 {
1536 if(foreignKeyFields == null)
1537 {
1538 return parameterIndex;
1539 }
1540
1541 List value = getFieldState(ctx).getValue();
1542 Object fk = (value.isEmpty() ? null : value.get(0));
1543
1544 for(int i = 0; i < foreignKeyFields.length; ++i)
1545 {
1546 parameterIndex = foreignKeyFields[i].setPrimaryKeyParameters(ps, parameterIndex, fk);
1547 }
1548
1549 return parameterIndex;
1550 }
1551
1552 public int loadInstanceResults(ResultSet rs,
1553 int parameterIndex,
1554 EntityEnterpriseContext ctx)
1555 {
1556 if(!hasForeignKey())
1557 {
1558 return parameterIndex;
1559 }
1560
1561 // load the value from the database
1562 Object[] ref = new Object[1];
1563 parameterIndex = loadArgumentResults(rs, parameterIndex, ref);
1564
1565 // only actually set the value if the state is not already loaded
1566 FieldState fieldState = getFieldState(ctx);
1567 if(!fieldState.isLoaded())
1568 {
1569 if(ref[0] != null)
1570 {
1571 load(ctx, Collections.singleton(ref[0]));
1572 }
1573 else
1574 {
1575 load(ctx, Collections.EMPTY_SET);
1576 }
1577 }
1578 return parameterIndex;
1579 }
1580
1581 public int loadArgumentResults(ResultSet rs, int parameterIndex, Object[] fkRef)
1582 {
1583 if(foreignKeyFields == null)
1584 {
1585 return parameterIndex;
1586 }
1587
1588 boolean fkIsNull = false;
1589
1590 // value of this field, will be filled in below
1591 Object[] argumentRef = new Object[1];
1592 for(int i = 0; i < foreignKeyFields.length; ++i)
1593 {
1594 JDBCCMPFieldBridge field = foreignKeyFields[i];
1595 parameterIndex = field.loadArgumentResults(rs, parameterIndex, argumentRef);
1596
1597 if(fkIsNull)
1598 {
1599 continue;
1600 }
1601 if(field.getPrimaryKeyField() != null)
1602 {
1603 // if there is a null field among FK fields, the whole FK field is considered null.
1604 // NOTE: don't throw exception in this case, it's ok if FK is partly mapped to a PK
1605 // NOTE2: we still need to iterate through foreign key fields and 'load' them to
1606 // return correct parameterIndex.
1607 if(argumentRef[0] == null)
1608 {
1609 fkRef[0] = null;
1610 fkIsNull = true;
1611 }
1612 else
1613 {
1614 // if we don't have a pk object yet create one
1615 if(fkRef[0] == null)
1616 {
1617 fkRef[0] = relatedEntity.createPrimaryKeyInstance();
1618 }
1619 try
1620 {
1621 // Set this field's value into the primary key object.
1622 field.getPrimaryKeyField().set(fkRef[0], argumentRef[0]);
1623 }
1624 catch(Exception e)
1625 {
1626 // Non recoverable internal exception
1627 throw new EJBException("Internal error setting foreign-key field " + getFieldName(), e);
1628 }
1629 }
1630 }
1631 else
1632 {
1633 // This field is the primary key, so no extraction is necessary.
1634 fkRef[0] = argumentRef[0];
1635 }
1636 }
1637 return parameterIndex;
1638 }
1639
1640 /**
1641 * This method is never called.
1642 * In case of a CMR with foreign key fields, only the foreign key fields are asked for the dirty state.
1643 */
1644 public boolean isDirty(EntityEnterpriseContext ctx)
1645 {
1646 return foreignKeyFields == null ? relationManager.isDirty() : false;
1647 }
1648
1649 public boolean invalidateCache(EntityEnterpriseContext ctx)
1650 {
1651 JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1652 FieldState fieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
1653 return fieldState == null ? false : fieldState.isChanged();
1654 }
1655
1656 /**
1657 * This method is never called.
1658 * In case of a CMR
1659 * - with foreign key fields, the foreign key fields are cleaned when necessary according to CMP fields'
1660 * behaviour.
1661 * - from m:m relationship, added/removed key pairs are cleared in application tx data map on sync.
1662 */
1663 public void setClean(EntityEnterpriseContext ctx)
1664 {
1665 throw new UnsupportedOperationException();
1666 }
1667
1668 public boolean isCMPField()
1669 {
1670 return false;
1671 }
1672
1673 public JDBCEntityPersistenceStore getManager()
1674 {
1675 return manager;
1676 }
1677
1678 public boolean hasFKFieldsMappedToCMPFields()
1679 {
1680 return hasFKFieldsMappedToCMPFields;
1681 }
1682
1683 public void addRelatedPKWaitingForMyPK(Object myPK, Object relatedPK)
1684 {
1685 Map relatedPKsWaitingForMyPK = getRelatedPKsWaitingForMyPK();
1686 synchronized(relatedPKsWaitingForMyPK)
1687 {
1688 List relatedPKs = (List) relatedPKsWaitingForMyPK.get(myPK);
1689 if(relatedPKs == null)
1690 {
1691 relatedPKs = new ArrayList(1);
1692 relatedPKsWaitingForMyPK.put(myPK, relatedPKs);
1693 }
1694 relatedPKs.add(relatedPK);
1695 }
1696 }
1697
1698 public void removeRelatedPKWaitingForMyPK(Object myPK, Object relatedPK)
1699 {
1700 final Map relatedPKMap = getRelatedPKsWaitingForMyPK();
1701 synchronized(relatedPKMap)
1702 {
1703 List relatedPKs = (List) relatedPKMap.get(myPK);
1704 if(relatedPKs != null)
1705 {
1706 relatedPKs.remove(relatedPK);
1707 }
1708 }
1709 }
1710
1711 /**
1712 * Gets the field state object from the persistence context.
1713 */
1714 private FieldState getFieldState(EntityEnterpriseContext ctx)
1715 {
1716 JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
1717 FieldState fieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
1718 if(fieldState == null)
1719 {
1720 fieldState = new FieldState(ctx);
1721 jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
1722 }
1723 return fieldState;
1724 }
1725
1726 /**
1727 * Initializes foreign key fields
1728 *
1729 * @throws DeploymentException
1730 */
1731 private void initializeForeignKeyFields()
1732 throws DeploymentException
1733 {
1734 Collection foreignKeys = metadata.getRelatedRole().getKeyFields();
1735
1736 // temporary map used later to write fk fields in special order
1737 Map fkFieldsByRelatedPKFields = new HashMap();
1738 for(Iterator i = foreignKeys.iterator(); i.hasNext();)
1739 {
1740 JDBCCMPFieldMetaData fkFieldMetaData = (JDBCCMPFieldMetaData) i.next();
1741 JDBCCMP2xFieldBridge relatedPKField =
1742 (JDBCCMP2xFieldBridge) relatedEntity.getFieldByName(fkFieldMetaData.getFieldName());
1743
1744 // now determine whether the fk is mapped to a pk column
1745 String fkColumnName = fkFieldMetaData.getColumnName();
1746 JDBCCMP2xFieldBridge fkField = null;
1747
1748 // look among the CMP fields for the field with the same column name
1749 JDBCFieldBridge[] tableFields = entity.getTableFields();
1750 for(int tableInd = 0; tableInd < tableFields.length && fkField == null; ++tableInd)
1751 {
1752 JDBCCMP2xFieldBridge cmpField = (JDBCCMP2xFieldBridge) tableFields[tableInd];
1753 if(fkColumnName.equals(cmpField.getColumnName()))
1754 {
1755 hasFKFieldsMappedToCMPFields = true;
1756
1757 // construct the foreign key field
1758 fkField = new JDBCCMP2xFieldBridge((JDBCStoreManager) cmpField.getManager(), // this cmpField's manager
1759 relatedPKField.getFieldName(),
1760 relatedPKField.getFieldType(),
1761 cmpField.getJDBCType(), // this cmpField's jdbc type
1762 relatedPKField.isReadOnly(),
1763 relatedPKField.getReadTimeOut(),
1764 relatedPKField.getPrimaryKeyClass(),
1765 relatedPKField.getPrimaryKeyField(),
1766 cmpField, // CMP field I am mapped to
1767 this,
1768 fkColumnName);
1769
1770 if(cmpField.isPrimaryKeyMember())
1771 {
1772 relatedPKFieldsByMyPKFields.put(cmpField, relatedPKField);
1773 }
1774 }
1775 }
1776
1777 // if the fk is not a part of pk then create a new field
1778 if(fkField == null)
1779 {
1780 fkField = new JDBCCMP2xFieldBridge(manager,
1781 fkFieldMetaData,
1782 manager.getJDBCTypeFactory().getJDBCType(fkFieldMetaData));
1783 }
1784
1785 fkFieldsByRelatedPKFields.put(relatedPKField, fkField); // temporary map
1786 relatedPKFieldsByMyFKFields.put(fkField, relatedPKField);
1787 }
1788
1789 // Note: this important to order the foreign key fields so that their order matches
1790 // the order of related entity's pk fields in case of complex primary keys.
1791 // The order is important in fk-constraint generation and in SELECT when loading
1792 if(fkFieldsByRelatedPKFields.size() > 0)
1793 {
1794 JDBCFieldBridge[] relatedPKFields = relatedEntity.getPrimaryKeyFields();
1795 List fkList = new ArrayList(relatedPKFields.length);
1796 for(int i = 0; i < relatedPKFields.length; ++i)
1797 {
1798 JDBCCMPFieldBridge fkField = (JDBCCMPFieldBridge) fkFieldsByRelatedPKFields.remove(relatedPKFields[i]);
1799 fkList.add(fkField);
1800 }
1801 foreignKeyFields = (JDBCCMP2xFieldBridge[]) fkList.toArray(new JDBCCMP2xFieldBridge[fkList.size()]);
1802 }
1803 else
1804 {
1805 foreignKeyFields = null;
1806 }
1807
1808 // are all FK fields mapped to PK fields?
1809 allFKFieldsMappedToPKFields = relatedPKFieldsByMyPKFields.size() > 0
1810 && relatedPKFieldsByMyPKFields.size() == foreignKeyFields.length;
1811
1812 if(foreignKeyFields != null)
1813 {
1814 jdbcType = new CMRJDBCType(Arrays.asList(foreignKeyFields));
1815 }
1816 }
1817
1818 private Transaction getTransaction()
1819 {
1820 try
1821 {
1822 EntityContainer container = getJDBCStoreManager().getContainer();
1823 TransactionManager tm = container.getTransactionManager();
1824 return tm.getTransaction();
1825 }
1826 catch(SystemException e)
1827 {
1828 throw new EJBException("Error getting transaction from the transaction manager", e);
1829 }
1830 }
1831
1832 /**
1833 * @return Map of lists of waiting related PK values keyed by not yet created this side's PK value.
1834 */
1835 private Map getRelatedPKsWaitingForMyPK()
1836 {
1837 return (Map) relatedPKValuesWaitingForMyPK.get();
1838 }
1839
1840 private RelationDataManager initRelationManager(JDBCCMRFieldBridge relatedField)
1841 {
1842 if(relationManager == null)
1843 {
1844 if(metadata.getRelationMetaData().isTableMappingStyle())
1845 {
1846 relationManager = new M2MRelationManager(this, relatedField);
1847 }
1848 else
1849 {
1850 relationManager = EMPTY_RELATION_MANAGER;
1851 }
1852 }
1853 return relationManager;
1854 }
1855
1856 private Object getRelatedPrimaryKey(Object localObject)
1857 {
1858 Object relatedId;
1859 if(relatedEntity.getLocalInterface().isAssignableFrom(localObject.getClass()))
1860 {
1861 EJBLocalObject local = (EJBLocalObject) localObject;
1862 try
1863 {
1864 relatedId = local.getPrimaryKey();
1865 }
1866 catch(NoSuchObjectLocalException e)
1867 {
1868 throw new IllegalArgumentException(e.getMessage());
1869 }
1870
1871 /*
1872 if(relatedManager.wasCascadeDeleted(relatedId))
1873 {
1874 throw new IllegalArgumentException("The instance was cascade-deleted: pk=" + relatedId);
1875 }
1876 */
1877 }
1878 else
1879 {
1880 throw new IllegalArgumentException("The values of this field must be of type " +
1881 relatedEntity.getLocalInterface().getName());
1882 }
1883 return relatedId;
1884 }
1885
1886 public String toString()
1887 {
1888 return entity.getEntityName() + '.' + getFieldName();
1889 }
1890
1891 private final class FieldState
1892 {
1893 private final EntityEnterpriseContext ctx;
1894 private List[] setHandle = new List[1];
1895 private Set addedRelations;
1896 private Set removedRelations;
1897 private Set relationSet;
1898 private boolean isLoaded = false;
1899 private final long lastRead = -1;
1900
1901 private boolean changed;
1902
1903 public FieldState(EntityEnterpriseContext ctx)
1904 {
1905 this.ctx = ctx;
1906 setHandle[0] = new ArrayList();
1907 }
1908
1909 /**
1910 * Get the current value (list of primary keys).
1911 */
1912 public List getValue()
1913 {
1914 if(!isLoaded)
1915 {
1916 throw new EJBException("CMR field value not loaded yet");
1917 }
1918 return Collections.unmodifiableList(setHandle[0]);
1919 }
1920
1921 /**
1922 * Has this relation been loaded.
1923 */
1924 public boolean isLoaded()
1925 {
1926 return isLoaded;
1927 }
1928
1929 /**
1930 * When was this value last read from the datastore.
1931 */
1932 public long getLastRead()
1933 {
1934 return lastRead;
1935 }
1936
1937 /**
1938 * Add this foreign to the relationship.
1939 */
1940 public void addRelation(Object fk)
1941 {
1942 if(isLoaded)
1943 {
1944 setHandle[0].add(fk);
1945 }
1946 else
1947 {
1948 if(removedRelations == null)
1949 {
1950 removedRelations = new HashSet();
1951 addedRelations = new HashSet();
1952 }
1953 removedRelations.remove(fk);
1954 addedRelations.add(fk);
1955 }
1956
1957 changed = true;
1958 }
1959
1960 /**
1961 * Remove this foreign to the relationship.
1962 */
1963 public void removeRelation(Object fk)
1964 {
1965 if(isLoaded)
1966 {
1967 setHandle[0].remove(fk);
1968 }
1969 else
1970 {
1971 if(removedRelations == null)
1972 {
1973 removedRelations = new HashSet();
1974 addedRelations = new HashSet();
1975 }
1976 addedRelations.remove(fk);
1977 removedRelations.add(fk);
1978 }
1979
1980 changed = true;
1981 }
1982
1983 /**
1984 * loads the collection of related ids
1985 */
1986 public void loadRelations(Collection values)
1987 {
1988 // check if we are aleready loaded
1989 if(isLoaded)
1990 {
1991 throw new EJBException("CMR field value is already loaded");
1992 }
1993
1994 // just in the case where there are lingering values
1995 setHandle[0].clear();
1996
1997 // add the new values
1998 setHandle[0].addAll(values);
1999
2000 if(removedRelations != null)
2001 {
2002 // remove the already removed values
2003 setHandle[0].removeAll(removedRelations);
2004 removedRelations = null;
2005 }
2006
2007 if(addedRelations != null)
2008 {
2009 // add the already added values
2010 // but remove FKs we are going to add to avoid duplication
2011 setHandle[0].removeAll(addedRelations);
2012 setHandle[0].addAll(addedRelations);
2013 addedRelations = null;
2014 }
2015
2016 // mark the field loaded
2017 isLoaded = true;
2018 }
2019
2020 /**
2021 * Get the current relation set or create a new one.
2022 */
2023 public Set getRelationSet()
2024 {
2025 if(!isLoaded)
2026 {
2027 throw new EJBException("CMR field value not loaded yet");
2028 }
2029
2030 if(ctx.isReadOnly())
2031 {
2032 // we are in a read-only invocation, so return a snapshot set
2033 return new RelationSet(JDBCCMRFieldBridge.this,
2034 ctx,
2035 new List[]{new ArrayList(setHandle[0])},
2036 true);
2037 }
2038
2039 // if we already have a relationset use it
2040 if(relationSet != null)
2041 {
2042 return relationSet;
2043 }
2044
2045 // construct a new relationshet
2046 try
2047 {
2048 // get the curent transaction
2049 EntityContainer container = getJDBCStoreManager().getContainer();
2050 TransactionManager tm = container.getTransactionManager();
2051 Transaction tx = tm.getTransaction();
2052
2053 // if whe have a valid transaction...
2054 if(tx != null && (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_PREPARING))
2055 {
2056 // crete the relation set and register for a tx callback
2057 relationSet = new RelationSet(JDBCCMRFieldBridge.this, ctx, setHandle, false);
2058 TxSynchronization sync = new TxSynchronization(FieldState.this);
2059 tx.registerSynchronization(sync);
2060 }
2061 else
2062 {
2063 // if there is no transaction create a pre-failed list
2064 relationSet = new RelationSet(JDBCCMRFieldBridge.this, ctx, new List[1], false);
2065 }
2066
2067 return relationSet;
2068 }
2069 catch(SystemException e)
2070 {
2071 throw new EJBException("Error while creating RelationSet", e);
2072 }
2073 catch(RollbackException e)
2074 {
2075 throw new EJBException("Error while creating RelationSet", e);
2076 }
2077 }
2078
2079 /**
2080 * Invalidate the current relationship set.
2081 */
2082 public void invalidate()
2083 {
2084 // make a new set handle and copy the currentList to the new handle
2085 // this will cause old references to the relationSet to throw an
2086 // IllegalStateException if accesses, but will not cause a reload
2087 // in Commit Option A
2088 List currentList = null;
2089 if(setHandle != null && setHandle.length > 0)
2090 {
2091 currentList = setHandle[0];
2092 setHandle[0] = null;
2093 }
2094 setHandle = new List[1];
2095 setHandle[0] = currentList;
2096
2097 relationSet = null;
2098 changed = false;
2099 }
2100
2101 public boolean isChanged()
2102 {
2103 return changed;
2104 }
2105 }
2106
2107 private final static class CMRJDBCType implements JDBCType
2108 {
2109 private final String[] columnNames;
2110 private final Class[] javaTypes;
2111 private final int[] jdbcTypes;
2112 private final String[] sqlTypes;
2113 private final boolean[] notNull;
2114
2115 private CMRJDBCType(List fields)
2116 {
2117 List columnNamesList = new ArrayList();
2118 List javaTypesList = new ArrayList();
2119 List jdbcTypesList = new ArrayList();
2120 List sqlTypesList = new ArrayList();
2121 List notNullList = new ArrayList();
2122
2123 for(Iterator iter = fields.iterator(); iter.hasNext();)
2124 {
2125 JDBCCMPFieldBridge field = (JDBCCMPFieldBridge) iter.next();
2126 JDBCType type = field.getJDBCType();
2127 for(int i = 0; i < type.getColumnNames().length; i++)
2128 {
2129 columnNamesList.add(type.getColumnNames()[i]);
2130 javaTypesList.add(type.getJavaTypes()[i]);
2131 jdbcTypesList.add(new Integer(type.getJDBCTypes()[i]));
2132 sqlTypesList.add(type.getSQLTypes()[i]);
2133 notNullList.add(new Boolean(type.getNotNull()[i]));
2134 }
2135 }
2136 columnNames = (String[]) columnNamesList.toArray(new String[columnNamesList.size()]);
2137 javaTypes = (Class[]) javaTypesList.toArray(new Class[javaTypesList.size()]);
2138 sqlTypes = (String[]) sqlTypesList.toArray(new String[sqlTypesList.size()]);
2139
2140 jdbcTypes = new int[jdbcTypesList.size()];
2141 for(int i = 0; i < jdbcTypes.length; i++)
2142 {
2143 jdbcTypes[i] = ((Integer) jdbcTypesList.get(i)).intValue();
2144 }
2145
2146 notNull = new boolean[notNullList.size()];
2147 for(int i = 0; i < notNull.length; i++)
2148 {
2149 notNull[i] = ((Boolean) notNullList.get(i)).booleanValue();
2150 }
2151 }
2152
2153 public String[] getColumnNames()
2154 {
2155 return columnNames;
2156 }
2157
2158 public Class[] getJavaTypes()
2159 {
2160 return javaTypes;
2161 }
2162
2163 public int[] getJDBCTypes()
2164 {
2165 return jdbcTypes;
2166 }
2167
2168 public String[] getSQLTypes()
2169 {
2170 return sqlTypes;
2171 }
2172
2173 public boolean[] getNotNull()
2174 {
2175 return notNull;
2176 }
2177
2178 public boolean[] getAutoIncrement()
2179 {
2180 return new boolean[]{false};
2181 }
2182
2183 public Object getColumnValue(int index, Object value)
2184 {
2185 throw new UnsupportedOperationException();
2186 }
2187
2188 public Object setColumnValue(int index, Object value, Object columnValue)
2189 {
2190 throw new UnsupportedOperationException();
2191 }
2192
2193 public boolean hasMapper()
2194 {
2195 throw new UnsupportedOperationException("hasMapper is not implemented.");
2196 }
2197
2198 public boolean isSearchable()
2199 {
2200 throw new UnsupportedOperationException("isSearchable is not implemented.");
2201 }
2202
2203 public JDBCResultSetReader[] getResultSetReaders()
2204 {
2205 // foreign key fields has their result set readers
2206 throw new UnsupportedOperationException();
2207 }
2208
2209 public JDBCParameterSetter[] getParameterSetter()
2210 {
2211 throw new UnsupportedOperationException();
2212 }
2213 }
2214
2215 private final static class TxSynchronization implements Synchronization
2216 {
2217 private final WeakReference fieldStateRef;
2218
2219 private TxSynchronization(FieldState fieldState)
2220 {
2221 if(fieldState == null)
2222 {
2223 throw new IllegalArgumentException("fieldState is null");
2224 }
2225 this.fieldStateRef = new WeakReference(fieldState);
2226 }
2227
2228 public void beforeCompletion()
2229 {
2230 // REVIEW: THIS WILL NOT BE INVOKED ON A ROLLBACK
2231 // Be Careful where you put this invalidate
2232 // If you put it in afterCompletion, the beanlock will probably
2233 // be released before the invalidate and you will have a race
2234 FieldState fieldState = (FieldState) fieldStateRef.get();
2235 if(fieldState != null)
2236 {
2237 fieldState.invalidate();
2238 }
2239 }
2240
2241 public void afterCompletion(int status)
2242 {
2243 }
2244 }
2245
2246 public static interface RelationDataManager
2247 {
2248 void addRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId);
2249
2250 void removeRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId);
2251
2252 boolean isDirty();
2253
2254 RelationData getRelationData();
2255 }
2256
2257 private static final RelationDataManager EMPTY_RELATION_MANAGER = new RelationDataManager()
2258 {
2259 public void addRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId)
2260 {
2261 }
2262
2263 public void removeRelation(JDBCCMRFieldBridge field,
2264 Object id,
2265 JDBCCMRFieldBridge relatedField,
2266 Object relatedId)
2267 {
2268 }
2269
2270 public boolean isDirty()
2271 {
2272 return false;
2273 }
2274
2275 public RelationData getRelationData()
2276 {
2277 throw new UnsupportedOperationException();
2278 }
2279 };
2280
2281 public static class M2MRelationManager
2282 implements RelationDataManager
2283 {
2284 private final JDBCCMRFieldBridge leftField;
2285 private final JDBCCMRFieldBridge rightField;
2286
2287 private final TransactionLocal relationData = new TransactionLocal()
2288 {
2289 protected Object initialValue()
2290 {
2291 return new RelationData(leftField, rightField);
2292 }
2293 };
2294
2295 public M2MRelationManager(JDBCCMRFieldBridge leftField, JDBCCMRFieldBridge rightField)
2296 {
2297 this.leftField = leftField;
2298 this.rightField = rightField;
2299 }
2300
2301 public void addRelation(JDBCCMRFieldBridge field,
2302 Object id,
2303 JDBCCMRFieldBridge relatedField,
2304 Object relatedId)
2305 {
2306 final RelationData local = getRelationData();
2307 local.addRelation(field, id, relatedField, relatedId);
2308 }
2309
2310 public void removeRelation(JDBCCMRFieldBridge field,
2311 Object id,
2312 JDBCCMRFieldBridge relatedField,
2313 Object relatedId)
2314 {
2315 RelationData local = getRelationData();
2316 local.removeRelation(field, id, relatedField, relatedId);
2317 }
2318
2319 public boolean isDirty()
2320 {
2321 RelationData local = getRelationData();
2322 return local.isDirty();
2323 }
2324
2325 public RelationData getRelationData()
2326 {
2327 final RelationData local = (RelationData) relationData.get();
2328 return local;
2329 }
2330 }
2331
2332 /*interface SecurityActions
2333 {
2334 class UTIL
2335 {
2336 static SecurityActions getSecurityActions()
2337 {
2338 return System.getSecurityManager() == null ? NON_PRIVILEGED : PRIVILEGED;
2339 }
2340 }
2341
2342 SecurityActions NON_PRIVILEGED = new SecurityActions()
2343 {
2344 public Principal getPrincipal()
2345 {
2346 return SecurityAssociation.getPrincipal();
2347 }
2348
2349 public Object getCredential()
2350 {
2351 return SecurityAssociation.getCredential();
2352 }
2353 };
2354
2355 SecurityActions PRIVILEGED = new SecurityActions()
2356 {
2357 private final PrivilegedAction getPrincipalAction = new PrivilegedAction()
2358 {
2359 public Object run()
2360 {
2361 return SecurityAssociation.getPrincipal();
2362 }
2363 };
2364
2365 private final PrivilegedAction getCredentialAction = new PrivilegedAction()
2366 {
2367 public Object run()
2368 {
2369 return SecurityAssociation.getCredential();
2370 }
2371 };
2372
2373 public Principal getPrincipal()
2374 {
2375 return (Principal) AccessController.doPrivileged(getPrincipalAction);
2376 }
2377
2378 public Object getCredential()
2379 {
2380 return AccessController.doPrivileged(getCredentialAction);
2381 }
2382 };
2383
2384 Principal getPrincipal();
2385
2386 Object getCredential();
2387 }*/
2388 }