KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > cmp > jdbc > bridge > JDBCCMP2xFieldBridge


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.reflect.Field JavaDoc;
25
26 import javax.ejb.EJBException JavaDoc;
27
28 import org.jboss.deployment.DeploymentException;
29 import org.jboss.ejb.EntityEnterpriseContext;
30
31 import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
32 import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
33 import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
34 import org.jboss.ejb.plugins.cmp.jdbc.CMPFieldStateFactory;
35 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
36
37 /**
38  * JDBCCMP2xFieldBridge is a concrete implementation of JDBCCMPFieldBridge for
39  * CMP version 2.x. Instance data is stored in the entity persistence context.
40  * Whenever a field is changed it is compared to the current value and sets
41  * a dirty flag if the value has changed.
42  *
43  * Life-cycle:
44  * Tied to the EntityBridge.
45  *
46  * Multiplicity:
47  * One for each entity bean cmp field.
48  *
49  * @author <a HREF="mailto:dain@daingroup.com">Dain Sundstrom</a>
50  * @author <a HREF="mailto:alex@jboss.org">Alex Loubyansky</a>
51  * @version $Revision: 37459 $
52  */

53 public class JDBCCMP2xFieldBridge extends JDBCAbstractCMPFieldBridge
54 {
55    /** column name (used only at deployment time to check whether fields mapped to the same column) */
56    private final String JavaDoc columnName;
57
58    /** CMP field this foreign key field is mapped to */
59    private final JDBCCMP2xFieldBridge cmpFieldIAmMappedTo;
60
61    /** this is used for foreign key fields mapped to CMP fields (check ChainLink) */
62    private ChainLink cmrChainLink;
63
64    // Constructors
65

66    public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
67                                JDBCCMPFieldMetaData metadata)
68       throws DeploymentException
69    {
70       super(manager, metadata);
71       cmpFieldIAmMappedTo = null;
72       columnName = metadata.getColumnName();
73    }
74
75    public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
76                                JDBCCMPFieldMetaData metadata,
77                                CMPFieldStateFactory stateFactory,
78                                boolean checkDirtyAfterGet)
79       throws DeploymentException
80    {
81       this(manager, metadata);
82       this.stateFactory = stateFactory;
83       this.checkDirtyAfterGet = checkDirtyAfterGet;
84    }
85
86    public JDBCCMP2xFieldBridge(JDBCCMP2xFieldBridge cmpField,
87                                CMPFieldStateFactory stateFactory,
88                                boolean checkDirtyAfterGet)
89       throws DeploymentException
90    {
91       this(
92          (JDBCStoreManager) cmpField.getManager(),
93          cmpField.getFieldName(),
94          cmpField.getFieldType(),
95          cmpField.getJDBCType(),
96          cmpField.isReadOnly(), // should always be false?
97
cmpField.getReadTimeOut(),
98          cmpField.getPrimaryKeyClass(),
99          cmpField.getPrimaryKeyField(),
100          cmpField,
101          null, // it should not be a foreign key
102
cmpField.getColumnName()
103       );
104       this.stateFactory = stateFactory;
105       this.checkDirtyAfterGet = checkDirtyAfterGet;
106    }
107
108    /**
109     * This constructor creates a foreign key field.
110     */

111    public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
112                                JDBCCMPFieldMetaData metadata,
113                                JDBCType jdbcType)
114       throws DeploymentException
115    {
116       super(manager, metadata, jdbcType);
117       cmpFieldIAmMappedTo = null;
118       columnName = metadata.getColumnName();
119    }
120
121    /**
122     * This constructor is used to create a foreign key field instance that is
123     * a part of primary key field. See JDBCCMRFieldBridge.
124     */

125    public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
126                                String JavaDoc fieldName,
127                                Class JavaDoc fieldType,
128                                JDBCType jdbcType,
129                                boolean readOnly,
130                                long readTimeOut,
131                                Class JavaDoc primaryKeyClass,
132                                Field JavaDoc primaryKeyField,
133                                JDBCCMP2xFieldBridge cmpFieldIAmMappedTo,
134                                JDBCCMRFieldBridge myCMRField,
135                                String JavaDoc columnName)
136       throws DeploymentException
137    {
138       super(
139          manager,
140          fieldName,
141          fieldType,
142          jdbcType,
143          readOnly,
144          readTimeOut,
145          primaryKeyClass,
146          primaryKeyField,
147          cmpFieldIAmMappedTo.getFieldIndex(),
148          cmpFieldIAmMappedTo.getTableIndex(),
149          cmpFieldIAmMappedTo.checkDirtyAfterGet,
150          cmpFieldIAmMappedTo.stateFactory
151       );
152       this.cmpFieldIAmMappedTo = cmpFieldIAmMappedTo;
153       if(myCMRField != null)
154       {
155          cmrChainLink = new CMRChainLink(myCMRField);
156          cmpFieldIAmMappedTo.addCMRChainLink(cmrChainLink);
157       }
158       this.columnName = columnName;
159    }
160
161    // Public
162

163    public JDBCCMP2xFieldBridge getCmpFieldIAmMappedTo()
164    {
165       return cmpFieldIAmMappedTo;
166    }
167
168    public ChainLink getCmrChainLink()
169    {
170       return cmrChainLink;
171    }
172
173    public boolean isFKFieldMappedToCMPField()
174    {
175       return cmpFieldIAmMappedTo != null && this.cmrChainLink != null;
176    }
177
178    public String JavaDoc getColumnName()
179    {
180       return columnName;
181    }
182
183    // JDBCFieldBridge implementation
184

185    public Object JavaDoc getInstanceValue(EntityEnterpriseContext ctx)
186    {
187       FieldState fieldState = getLoadedState(ctx);
188       return fieldState.getValue();
189    }
190
191    public void setInstanceValue(EntityEnterpriseContext ctx, Object JavaDoc value)
192    {
193       FieldState fieldState = getFieldState(ctx);
194
195       // update current value
196
if(cmpFieldIAmMappedTo != null && cmpFieldIAmMappedTo.isPrimaryKeyMember())
197       {
198          // if this field shares the column with the primary key field and new value
199
// changes the primary key then we are in an illegal state.
200
if(value != null)
201          {
202             if(fieldState.isLoaded() && fieldState.isValueChanged(value))
203             {
204                throw new IllegalStateException JavaDoc(
205                   "New value [" + value + "] of a foreign key field "
206                   + getFieldName()
207                   + " changed the value of a primary key field "
208                   + cmpFieldIAmMappedTo.getFieldName()
209                   + "[" + fieldState.value + "]"
210                );
211             }
212             else
213             {
214                fieldState.setValue(value);
215             }
216          }
217       }
218       else
219       {
220          if(cmrChainLink != null
221             && JDBCEntityBridge.isEjbCreateDone(ctx)
222             && fieldState.isLoaded()
223             && fieldState.isValueChanged(value))
224          {
225             cmrChainLink.execute(ctx, fieldState, value);
226          }
227
228          fieldState.setValue(value);
229       }
230
231       // we are loading the field right now so it isLoaded
232
fieldState.setLoaded();
233    }
234
235    public void lockInstanceValue(EntityEnterpriseContext ctx)
236    {
237       getFieldState(ctx).lockValue();
238    }
239
240    public boolean isLoaded(EntityEnterpriseContext ctx)
241    {
242       return getFieldState(ctx).isLoaded();
243    }
244
245    /**
246     * Has the value of this field changes since the last time clean was called.
247     */

248    public boolean isDirty(EntityEnterpriseContext ctx)
249    {
250       return !primaryKeyMember
251          && !readOnly
252          && getFieldState(ctx).isDirty();
253    }
254
255    /**
256     * Mark this field as clean. Saves the current state in context, so it
257     * can be compared when isDirty is called.
258     */

259    public void setClean(EntityEnterpriseContext ctx)
260    {
261       FieldState fieldState = getFieldState(ctx);
262       fieldState.setClean();
263
264       // update last read time
265
if(readOnly && readTimeOut != -1)
266          fieldState.lastRead = System.currentTimeMillis();
267    }
268
269    public void resetPersistenceContext(EntityEnterpriseContext ctx)
270    {
271       if(isReadTimedOut(ctx))
272       {
273          JDBCContext jdbcCtx = (JDBCContext)ctx.getPersistenceContext();
274          FieldState fieldState = (FieldState)jdbcCtx.getFieldState(jdbcContextIndex);
275          if(fieldState != null)
276             fieldState.reset();
277       }
278    }
279
280    public boolean isReadTimedOut(EntityEnterpriseContext ctx)
281    {
282       // if we are read/write then we are always timed out
283
if(!readOnly)
284          return true;
285
286       // if read-time-out is -1 then we never time out.
287
if(readTimeOut == -1)
288          return false;
289
290       long readInterval = System.currentTimeMillis() - getFieldState(ctx).lastRead;
291       return readInterval >= readTimeOut;
292    }
293
294    public Object JavaDoc getLockedValue(EntityEnterpriseContext ctx)
295    {
296       return getLoadedState(ctx).getLockedValue();
297    }
298
299    public void updateState(EntityEnterpriseContext ctx, Object JavaDoc value)
300    {
301       getFieldState(ctx).updateState(value);
302    }
303
304    protected void setDirtyAfterGet(EntityEnterpriseContext ctx)
305    {
306       getFieldState(ctx).setCheckDirty();
307    }
308
309    // Private
310

311    private FieldState getLoadedState(EntityEnterpriseContext ctx)
312    {
313       FieldState fieldState = getFieldState(ctx);
314       if(!fieldState.isLoaded())
315       {
316          manager.loadField(this, ctx);
317          if(!fieldState.isLoaded())
318             throw new EJBException JavaDoc("Could not load field value: " + getFieldName());
319       }
320       return fieldState;
321    }
322
323    private void addCMRChainLink(ChainLink nextCMRChainLink)
324    {
325       if(cmrChainLink == null)
326       {
327          cmrChainLink = new DummyChainLink();
328       }
329       cmrChainLink.setNextLink(nextCMRChainLink);
330    }
331
332    private FieldState getFieldState(EntityEnterpriseContext ctx)
333    {
334       JDBCContext jdbcCtx = (JDBCContext)ctx.getPersistenceContext();
335       FieldState fieldState = (FieldState)jdbcCtx.getFieldState(jdbcContextIndex);
336       if(fieldState == null)
337       {
338          fieldState = new FieldState(jdbcCtx);
339          jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
340       }
341       return fieldState;
342    }
343
344    // Inner
345

346    private class FieldState
347    {
348       /** entity's state this field state belongs to */
349       private JDBCEntityBridge.EntityState entityState;
350       /** current field value */
351       private Object JavaDoc value;
352       /** previous field state. NOTE: it might not be the same as previous field value */
353       private Object JavaDoc state;
354       /** locked field value */
355       private Object JavaDoc lockedValue;
356       /** last time the field was read */
357       private long lastRead = -1;
358
359       public FieldState(JDBCContext jdbcCtx)
360       {
361          this.entityState = jdbcCtx.getEntityState();
362       }
363
364       /**
365        * Reads current field value.
366        * @return current field value.
367        */

368       public Object JavaDoc getValue()
369       {
370          //if(checkDirtyAfterGet)
371
// setCheckDirty();
372
return value;
373       }
374
375       /**
376        * Sets new field value and sets the flag that setter was called on the field
377        * @param newValue new field value.
378        */

379       public void setValue(Object JavaDoc newValue)
380       {
381          this.value = newValue;
382          setCheckDirty();
383       }
384
385       private void setCheckDirty()
386       {
387          entityState.setCheckDirty(tableIndex);
388       }
389
390       /**
391        * @return true if the field is loaded.
392        */

393       public boolean isLoaded()
394       {
395          return entityState.isLoaded(tableIndex);
396       }
397
398       /**
399        * Marks the field as loaded.
400        */

401       public void setLoaded()
402       {
403          entityState.setLoaded(tableIndex);
404       }
405
406       /**
407        * @return true if the field is dirty.
408        */

409       public boolean isDirty()
410       {
411          return isLoaded() && !stateFactory.isStateValid(state, value);
412       }
413
414       /**
415        * Compares current value to a new value. Note, it does not compare
416        * field states, just values.
417        * @param newValue new field value
418        * @return true if field values are not equal.
419        */

420       public boolean isValueChanged(Object JavaDoc newValue)
421       {
422          return value == null ? newValue != null : !value.equals(newValue);
423       }
424
425       /**
426        * Resets masks and updates the state.
427        */

428       public void setClean()
429       {
430          entityState.setClean(tableIndex);
431          updateState(value);
432       }
433
434       /**
435        * Updates the state to some specific value that might be different from the current
436        * field's value. This trick is needed for foreign key fields because they can be
437        * changed while not being loaded. When the owning CMR field is loaded this method is
438        * called with the loaded from the database value. Thus, we have correct state and locked value.
439        * @param value the value loaded from the database.
440        */

441       private void updateState(Object JavaDoc value)
442       {
443          state = stateFactory.getFieldState(value);
444          lockedValue = value;
445       }
446
447       /**
448        * Resets everything.
449        */

450       public void reset()
451       {
452          value = null;
453          state = null;
454          lastRead = -1;
455          entityState.resetFlags(tableIndex);
456       }
457
458       public void lockValue()
459       {
460          if(entityState.lockValue(tableIndex))
461          {
462             //log.debug("locking> " + fieldName + "=" + value);
463
lockedValue = value;
464          }
465       }
466
467       public Object JavaDoc getLockedValue()
468       {
469          return lockedValue;
470       }
471    }
472
473    /**
474     * Represents a link in the chain. The execute method will doExecute each link
475     * in the chain except for the link (originator) execute() was called on.
476     */

477    private abstract static class ChainLink
478    {
479       private ChainLink nextLink;
480
481       public ChainLink()
482       {
483          nextLink = this;
484       }
485
486       public void setNextLink(ChainLink nextLink)
487       {
488          nextLink.nextLink = this.nextLink;
489          this.nextLink = nextLink;
490       }
491
492       public ChainLink getNextLink()
493       {
494          return nextLink;
495       }
496
497       public void execute(EntityEnterpriseContext ctx,
498                           FieldState fieldState,
499                           Object JavaDoc newValue)
500       {
501          nextLink.doExecute(this, ctx, fieldState, newValue);
502       }
503
504       protected abstract void doExecute(ChainLink originator,
505                                         EntityEnterpriseContext ctx,
506                                         FieldState fieldState,
507                                         Object JavaDoc newValue);
508    }
509
510    /**
511     * This chain link contains a CMR field a foreign key of which is mapped to a CMP field.
512     */

513    private static class CMRChainLink
514       extends ChainLink
515    {
516       private final JDBCCMRFieldBridge cmrField;
517
518       public CMRChainLink(JDBCCMRFieldBridge cmrField)
519       {
520          this.cmrField = cmrField;
521       }
522
523       /**
524        * Going down the chain current related id is calculated and stored in oldRelatedId.
525        * When the next link is originator, the flow is going backward:
526        * - field state is updated with new vaue;
527        * - new related id is calculated;
528        * - old relationship is destroyed (if there is one);
529        * - new relationship is established (if it is valid).
530        *
531        * @param originator ChainLink that started execution.
532        * @param ctx EnterpriseEntityContext of the entity.
533        * @param fieldState field's state.
534        * @param newValue new field value.
535        */

536       public void doExecute(ChainLink originator,
537                             EntityEnterpriseContext ctx,
538                             FieldState fieldState,
539                             Object JavaDoc newValue)
540       {
541          // get old related id
542
Object JavaDoc oldRelatedId = cmrField.getRelatedIdFromContext(ctx);
543
544          // invoke down the cmrChain
545
if(originator != getNextLink())
546          {
547             getNextLink().doExecute(originator, ctx, fieldState, newValue);
548          }
549
550          // update field state
551
fieldState.setValue(newValue);
552
553          // get new related id
554
Object JavaDoc newRelatedId = cmrField.getRelatedIdFromContext(ctx);
555
556          // destroy old relationship
557
if(oldRelatedId != null)
558             destroyRelations(oldRelatedId, ctx);
559
560          // establish new relationship
561
if(newRelatedId != null)
562             createRelations(newRelatedId, ctx);
563       }
564
565       private void createRelations(Object JavaDoc newRelatedId, EntityEnterpriseContext ctx)
566       {
567          try
568          {
569             if(cmrField.isForeignKeyValid(newRelatedId))
570             {
571                cmrField.createRelationLinks(ctx, newRelatedId, false);
572             }
573             else
574             {
575                // set foreign key to a new value
576
cmrField.setForeignKey(ctx, newRelatedId);
577                // put calculated relatedId to the waiting list
578
if(ctx.getId() != null)
579                {
580                   JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge)cmrField.getRelatedCMRField();
581                   relatedCMRField.addRelatedPKWaitingForMyPK(newRelatedId, ctx.getId());
582                }
583             }
584          }
585          catch(Exception JavaDoc e)
586          {
587             // no such object
588
}
589       }
590
591       private void destroyRelations(Object JavaDoc oldRelatedId, EntityEnterpriseContext ctx)
592       {
593          JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge)cmrField.getRelatedCMRField();
594          relatedCMRField.removeRelatedPKWaitingForMyPK(oldRelatedId, ctx.getId());
595          try
596          {
597             if(cmrField.isForeignKeyValid(oldRelatedId))
598             {
599                cmrField.destroyRelationLinks(ctx, oldRelatedId, true, false);
600             }
601          }
602          catch(Exception JavaDoc e)
603          {
604             // no such object
605
}
606       }
607    }
608
609    private static class DummyChainLink
610       extends ChainLink
611    {
612       public void doExecute(ChainLink originator,
613                             EntityEnterpriseContext ctx,
614                             FieldState fieldState,
615                             Object JavaDoc newValue)
616       {
617          // invoke down the cmrChain
618
if(originator != getNextLink())
619          {
620             getNextLink().doExecute(originator, ctx, fieldState, newValue);
621          }
622          // update field state
623
fieldState.setValue(newValue);
624       }
625    }
626 }
627
Popular Tags