001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc.bridge;
023:
024: import java.lang.reflect.Field;
025:
026: import javax.ejb.EJBException;
027:
028: import org.jboss.deployment.DeploymentException;
029: import org.jboss.ejb.EntityEnterpriseContext;
030:
031: import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
032: import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
033: import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
034: import org.jboss.ejb.plugins.cmp.jdbc.CMPFieldStateFactory;
035: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
036:
037: /**
038: * JDBCCMP2xFieldBridge is a concrete implementation of JDBCCMPFieldBridge for
039: * CMP version 2.x. Instance data is stored in the entity persistence context.
040: * Whenever a field is changed it is compared to the current value and sets
041: * a dirty flag if the value has changed.
042: *
043: * Life-cycle:
044: * Tied to the EntityBridge.
045: *
046: * Multiplicity:
047: * One for each entity bean cmp field.
048: *
049: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
050: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
051: * @version $Revision: 57209 $
052: */
053: public class JDBCCMP2xFieldBridge extends JDBCAbstractCMPFieldBridge {
054: /** column name (used only at deployment time to check whether fields mapped to the same column) */
055: private final String columnName;
056:
057: /** CMP field this foreign key field is mapped to */
058: private final JDBCCMP2xFieldBridge cmpFieldIAmMappedTo;
059:
060: /** this is used for foreign key fields mapped to CMP fields (check ChainLink) */
061: private ChainLink cmrChainLink;
062:
063: // Constructors
064:
065: public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
066: JDBCCMPFieldMetaData metadata) throws DeploymentException {
067: super (manager, metadata);
068: cmpFieldIAmMappedTo = null;
069: columnName = metadata.getColumnName();
070: }
071:
072: public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
073: JDBCCMPFieldMetaData metadata,
074: CMPFieldStateFactory stateFactory,
075: boolean checkDirtyAfterGet) throws DeploymentException {
076: this (manager, metadata);
077: this .stateFactory = stateFactory;
078: this .checkDirtyAfterGet = checkDirtyAfterGet;
079: }
080:
081: public JDBCCMP2xFieldBridge(JDBCCMP2xFieldBridge cmpField,
082: CMPFieldStateFactory stateFactory,
083: boolean checkDirtyAfterGet) throws DeploymentException {
084: this ((JDBCStoreManager) cmpField.getManager(), cmpField
085: .getFieldName(), cmpField.getFieldType(), cmpField
086: .getJDBCType(), cmpField.isReadOnly(), // should always be false?
087: cmpField.getReadTimeOut(), cmpField
088: .getPrimaryKeyClass(), cmpField
089: .getPrimaryKeyField(), cmpField, null, // it should not be a foreign key
090: cmpField.getColumnName());
091: this .stateFactory = stateFactory;
092: this .checkDirtyAfterGet = checkDirtyAfterGet;
093: }
094:
095: /**
096: * This constructor creates a foreign key field.
097: */
098: public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
099: JDBCCMPFieldMetaData metadata, JDBCType jdbcType)
100: throws DeploymentException {
101: super (manager, metadata, jdbcType);
102: cmpFieldIAmMappedTo = null;
103: columnName = metadata.getColumnName();
104: }
105:
106: /**
107: * This constructor is used to create a foreign key field instance that is
108: * a part of primary key field. See JDBCCMRFieldBridge.
109: */
110: public JDBCCMP2xFieldBridge(JDBCStoreManager manager,
111: String fieldName, Class fieldType, JDBCType jdbcType,
112: boolean readOnly, long readTimeOut, Class primaryKeyClass,
113: Field primaryKeyField,
114: JDBCCMP2xFieldBridge cmpFieldIAmMappedTo,
115: JDBCCMRFieldBridge myCMRField, String columnName)
116: throws DeploymentException {
117: super (manager, fieldName, fieldType, jdbcType, readOnly,
118: readTimeOut, primaryKeyClass, primaryKeyField,
119: cmpFieldIAmMappedTo.getFieldIndex(),
120: cmpFieldIAmMappedTo.getTableIndex(),
121: cmpFieldIAmMappedTo.checkDirtyAfterGet,
122: cmpFieldIAmMappedTo.stateFactory);
123: this .cmpFieldIAmMappedTo = cmpFieldIAmMappedTo;
124: if (myCMRField != null) {
125: cmrChainLink = new CMRChainLink(myCMRField);
126: cmpFieldIAmMappedTo.addCMRChainLink(cmrChainLink);
127: }
128: this .columnName = columnName;
129: }
130:
131: // Public
132:
133: public JDBCCMP2xFieldBridge getCmpFieldIAmMappedTo() {
134: return cmpFieldIAmMappedTo;
135: }
136:
137: public ChainLink getCmrChainLink() {
138: return cmrChainLink;
139: }
140:
141: public boolean isFKFieldMappedToCMPField() {
142: return cmpFieldIAmMappedTo != null && this .cmrChainLink != null;
143: }
144:
145: public String getColumnName() {
146: return columnName;
147: }
148:
149: // JDBCFieldBridge implementation
150:
151: public Object getInstanceValue(EntityEnterpriseContext ctx) {
152: FieldState fieldState = getLoadedState(ctx);
153: return fieldState.getValue();
154: }
155:
156: public void setInstanceValue(EntityEnterpriseContext ctx,
157: Object value) {
158: FieldState fieldState = getFieldState(ctx);
159:
160: // update current value
161: if (cmpFieldIAmMappedTo != null
162: && cmpFieldIAmMappedTo.isPrimaryKeyMember()) {
163: // if this field shares the column with the primary key field and new value
164: // changes the primary key then we are in an illegal state.
165: if (value != null) {
166: if (fieldState.isLoaded()
167: && fieldState.isValueChanged(value)) {
168: throw new IllegalStateException(
169: "New value ["
170: + value
171: + "] of a foreign key field "
172: + getFieldName()
173: + " changed the value of a primary key field "
174: + cmpFieldIAmMappedTo
175: .getFieldName() + "["
176: + fieldState.value + "]");
177: } else {
178: fieldState.setValue(value);
179: }
180: }
181: } else {
182: if (cmrChainLink != null
183: && JDBCEntityBridge.isEjbCreateDone(ctx)
184: && fieldState.isLoaded()
185: && fieldState.isValueChanged(value)) {
186: cmrChainLink.execute(ctx, fieldState, value);
187: }
188:
189: fieldState.setValue(value);
190: }
191:
192: // we are loading the field right now so it isLoaded
193: fieldState.setLoaded();
194: }
195:
196: public void lockInstanceValue(EntityEnterpriseContext ctx) {
197: getFieldState(ctx).lockValue();
198: }
199:
200: public boolean isLoaded(EntityEnterpriseContext ctx) {
201: return getFieldState(ctx).isLoaded();
202: }
203:
204: /**
205: * Has the value of this field changes since the last time clean was called.
206: */
207: public boolean isDirty(EntityEnterpriseContext ctx) {
208: return !primaryKeyMember && !readOnly
209: && getFieldState(ctx).isDirty();
210: }
211:
212: /**
213: * Mark this field as clean. Saves the current state in context, so it
214: * can be compared when isDirty is called.
215: */
216: public void setClean(EntityEnterpriseContext ctx) {
217: FieldState fieldState = getFieldState(ctx);
218: fieldState.setClean();
219:
220: // update last read time
221: if (readOnly && readTimeOut != -1)
222: fieldState.lastRead = System.currentTimeMillis();
223: }
224:
225: public void resetPersistenceContext(EntityEnterpriseContext ctx) {
226: if (isReadTimedOut(ctx)) {
227: JDBCContext jdbcCtx = (JDBCContext) ctx
228: .getPersistenceContext();
229: FieldState fieldState = (FieldState) jdbcCtx
230: .getFieldState(jdbcContextIndex);
231: if (fieldState != null)
232: fieldState.reset();
233: }
234: }
235:
236: public boolean isReadTimedOut(EntityEnterpriseContext ctx) {
237: // if we are read/write then we are always timed out
238: if (!readOnly)
239: return true;
240:
241: // if read-time-out is -1 then we never time out.
242: if (readTimeOut == -1)
243: return false;
244:
245: long readInterval = System.currentTimeMillis()
246: - getFieldState(ctx).lastRead;
247: return readInterval >= readTimeOut;
248: }
249:
250: public Object getLockedValue(EntityEnterpriseContext ctx) {
251: return getLoadedState(ctx).getLockedValue();
252: }
253:
254: public void updateState(EntityEnterpriseContext ctx, Object value) {
255: getFieldState(ctx).updateState(value);
256: }
257:
258: protected void setDirtyAfterGet(EntityEnterpriseContext ctx) {
259: getFieldState(ctx).setCheckDirty();
260: }
261:
262: // Private
263:
264: private FieldState getLoadedState(EntityEnterpriseContext ctx) {
265: FieldState fieldState = getFieldState(ctx);
266: if (!fieldState.isLoaded()) {
267: manager.loadField(this , ctx);
268: if (!fieldState.isLoaded())
269: throw new EJBException("Could not load field value: "
270: + getFieldName());
271: }
272: return fieldState;
273: }
274:
275: private void addCMRChainLink(ChainLink nextCMRChainLink) {
276: if (cmrChainLink == null) {
277: cmrChainLink = new DummyChainLink();
278: }
279: cmrChainLink.setNextLink(nextCMRChainLink);
280: }
281:
282: private FieldState getFieldState(EntityEnterpriseContext ctx) {
283: JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
284: FieldState fieldState = (FieldState) jdbcCtx
285: .getFieldState(jdbcContextIndex);
286: if (fieldState == null) {
287: fieldState = new FieldState(jdbcCtx);
288: jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
289: }
290: return fieldState;
291: }
292:
293: // Inner
294:
295: private class FieldState {
296: /** entity's state this field state belongs to */
297: private JDBCEntityBridge.EntityState entityState;
298: /** current field value */
299: private Object value;
300: /** previous field state. NOTE: it might not be the same as previous field value */
301: private Object state;
302: /** locked field value */
303: private Object lockedValue;
304: /** last time the field was read */
305: private long lastRead = -1;
306:
307: public FieldState(JDBCContext jdbcCtx) {
308: this .entityState = jdbcCtx.getEntityState();
309: }
310:
311: /**
312: * Reads current field value.
313: * @return current field value.
314: */
315: public Object getValue() {
316: //if(checkDirtyAfterGet)
317: // setCheckDirty();
318: return value;
319: }
320:
321: /**
322: * Sets new field value and sets the flag that setter was called on the field
323: * @param newValue new field value.
324: */
325: public void setValue(Object newValue) {
326: this .value = newValue;
327: setCheckDirty();
328: }
329:
330: private void setCheckDirty() {
331: entityState.setCheckDirty(tableIndex);
332: }
333:
334: /**
335: * @return true if the field is loaded.
336: */
337: public boolean isLoaded() {
338: return entityState.isLoaded(tableIndex);
339: }
340:
341: /**
342: * Marks the field as loaded.
343: */
344: public void setLoaded() {
345: entityState.setLoaded(tableIndex);
346: }
347:
348: /**
349: * @return true if the field is dirty.
350: */
351: public boolean isDirty() {
352: return isLoaded()
353: && !stateFactory.isStateValid(state, value);
354: }
355:
356: /**
357: * Compares current value to a new value. Note, it does not compare
358: * field states, just values.
359: * @param newValue new field value
360: * @return true if field values are not equal.
361: */
362: public boolean isValueChanged(Object newValue) {
363: return value == null ? newValue != null : !value
364: .equals(newValue);
365: }
366:
367: /**
368: * Resets masks and updates the state.
369: */
370: public void setClean() {
371: entityState.setClean(tableIndex);
372: updateState(value);
373: }
374:
375: /**
376: * Updates the state to some specific value that might be different from the current
377: * field's value. This trick is needed for foreign key fields because they can be
378: * changed while not being loaded. When the owning CMR field is loaded this method is
379: * called with the loaded from the database value. Thus, we have correct state and locked value.
380: * @param value the value loaded from the database.
381: */
382: private void updateState(Object value) {
383: state = stateFactory.getFieldState(value);
384: lockedValue = value;
385: }
386:
387: /**
388: * Resets everything.
389: */
390: public void reset() {
391: value = null;
392: state = null;
393: lastRead = -1;
394: entityState.resetFlags(tableIndex);
395: }
396:
397: public void lockValue() {
398: if (entityState.lockValue(tableIndex)) {
399: //log.debug("locking> " + fieldName + "=" + value);
400: lockedValue = value;
401: }
402: }
403:
404: public Object getLockedValue() {
405: return lockedValue;
406: }
407: }
408:
409: /**
410: * Represents a link in the chain. The execute method will doExecute each link
411: * in the chain except for the link (originator) execute() was called on.
412: */
413: private abstract static class ChainLink {
414: private ChainLink nextLink;
415:
416: public ChainLink() {
417: nextLink = this ;
418: }
419:
420: public void setNextLink(ChainLink nextLink) {
421: nextLink.nextLink = this .nextLink;
422: this .nextLink = nextLink;
423: }
424:
425: public ChainLink getNextLink() {
426: return nextLink;
427: }
428:
429: public void execute(EntityEnterpriseContext ctx,
430: FieldState fieldState, Object newValue) {
431: nextLink.doExecute(this , ctx, fieldState, newValue);
432: }
433:
434: protected abstract void doExecute(ChainLink originator,
435: EntityEnterpriseContext ctx, FieldState fieldState,
436: Object newValue);
437: }
438:
439: /**
440: * This chain link contains a CMR field a foreign key of which is mapped to a CMP field.
441: */
442: private static class CMRChainLink extends ChainLink {
443: private final JDBCCMRFieldBridge cmrField;
444:
445: public CMRChainLink(JDBCCMRFieldBridge cmrField) {
446: this .cmrField = cmrField;
447: }
448:
449: /**
450: * Going down the chain current related id is calculated and stored in oldRelatedId.
451: * When the next link is originator, the flow is going backward:
452: * - field state is updated with new vaue;
453: * - new related id is calculated;
454: * - old relationship is destroyed (if there is one);
455: * - new relationship is established (if it is valid).
456: *
457: * @param originator ChainLink that started execution.
458: * @param ctx EnterpriseEntityContext of the entity.
459: * @param fieldState field's state.
460: * @param newValue new field value.
461: */
462: public void doExecute(ChainLink originator,
463: EntityEnterpriseContext ctx, FieldState fieldState,
464: Object newValue) {
465: // get old related id
466: Object oldRelatedId = cmrField.getRelatedIdFromContext(ctx);
467:
468: // invoke down the cmrChain
469: if (originator != getNextLink()) {
470: getNextLink().doExecute(originator, ctx, fieldState,
471: newValue);
472: }
473:
474: // update field state
475: fieldState.setValue(newValue);
476:
477: // get new related id
478: Object newRelatedId = cmrField.getRelatedIdFromContext(ctx);
479:
480: // destroy old relationship
481: if (oldRelatedId != null)
482: destroyRelations(oldRelatedId, ctx);
483:
484: // establish new relationship
485: if (newRelatedId != null)
486: createRelations(newRelatedId, ctx);
487: }
488:
489: private void createRelations(Object newRelatedId,
490: EntityEnterpriseContext ctx) {
491: try {
492: if (cmrField.isForeignKeyValid(newRelatedId)) {
493: cmrField.createRelationLinks(ctx, newRelatedId,
494: false);
495: } else {
496: // set foreign key to a new value
497: cmrField.setForeignKey(ctx, newRelatedId);
498: // put calculated relatedId to the waiting list
499: if (ctx.getId() != null) {
500: JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge) cmrField
501: .getRelatedCMRField();
502: relatedCMRField.addRelatedPKWaitingForMyPK(
503: newRelatedId, ctx.getId());
504: }
505: }
506: } catch (Exception e) {
507: // no such object
508: }
509: }
510:
511: private void destroyRelations(Object oldRelatedId,
512: EntityEnterpriseContext ctx) {
513: JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge) cmrField
514: .getRelatedCMRField();
515: relatedCMRField.removeRelatedPKWaitingForMyPK(oldRelatedId,
516: ctx.getId());
517: try {
518: if (cmrField.isForeignKeyValid(oldRelatedId)) {
519: cmrField.destroyRelationLinks(ctx, oldRelatedId,
520: true, false);
521: }
522: } catch (Exception e) {
523: // no such object
524: }
525: }
526: }
527:
528: private static class DummyChainLink extends ChainLink {
529: public void doExecute(ChainLink originator,
530: EntityEnterpriseContext ctx, FieldState fieldState,
531: Object newValue) {
532: // invoke down the cmrChain
533: if (originator != getNextLink()) {
534: getNextLink().doExecute(originator, ctx, fieldState,
535: newValue);
536: }
537: // update field state
538: fieldState.setValue(newValue);
539: }
540: }
541: }
|