001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
012: // POSSIBILITY OF SUCH DAMAGE.
013: //
014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
015: package com.metaboss.enterprise.bo.impl;
016:
017: import java.lang.reflect.Array;
018: import java.lang.reflect.InvocationTargetException;
019: import java.lang.reflect.Method;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import javax.naming.NamingException;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import com.metaboss.enterprise.bo.BOException;
033: import com.metaboss.enterprise.bo.BOInvalidOperationForObjectDomainBeingEditedException;
034: import com.metaboss.enterprise.bo.BOInvalidOperationForObjectException;
035: import com.metaboss.enterprise.bo.BOInvalidOperationForReadOnlyObjectDomainException;
036: import com.metaboss.enterprise.bo.BONamingAndDirectoryServiceInvocationException;
037: import com.metaboss.enterprise.bo.BONewObjectRequiredException;
038: import com.metaboss.enterprise.bo.BOObjectAlreadyInTransactionException;
039: import com.metaboss.enterprise.bo.BOObjectDomain;
040: import com.metaboss.enterprise.bo.BOObjectDomainNotInTransactionException;
041: import com.metaboss.enterprise.bo.BOObjectNotInTransactionException;
042: import com.metaboss.enterprise.bo.BOObjectNotInitialisedException;
043: import com.metaboss.enterprise.bo.BOObjectUnusableException;
044: import com.metaboss.enterprise.bo.BOPersistenceServiceInvocationException;
045: import com.metaboss.enterprise.bo.BOUnexpectedProgramConditionException;
046: import com.metaboss.enterprise.ps.PSException;
047:
048: /* Base implementation class for all BOs */
049: public abstract class BOObjectDomainImpl implements BOObjectDomain {
050: // Static logging instance
051: private static final Log sLogger = LogFactory
052: .getLog(BOObjectDomainImpl.class);
053:
054: // The domain can be in one of the following states
055: // Unusable state. Just constructed object has it.
056: private static final int cStateNotSetUp = 0;
057: // Read only domain.
058: private static final int cStateReadOnly = 1;
059: // Editable domain
060: // After successfull commit and unsuccessfull commit bo will revert to cStateReadOnly
061: private static final int cStateBeingEdited = 2;
062:
063: // The actual state variable
064: private int mState = cStateNotSetUp;
065: // The actual domain url variable
066: private String mDomainType;
067: // The store for the entities loaded in this domain
068: private HashMap mEntities = new HashMap();
069:
070: // Default constructor is disabled
071: private BOObjectDomainImpl() {
072: }
073:
074: // Domain Url is a unique id of the domain.
075: protected BOObjectDomainImpl(String pDomainType) throws BOException {
076: mDomainType = pDomainType;
077: }
078:
079: // Getter for the domain type
080: protected String getDomainType() throws BOException {
081: return mDomainType;
082: }
083:
084: // Domain Url is globally unique identifier of the domain
085: protected String getDomainUrl() throws BOException {
086: return "domain:/" + mDomainType;
087: }
088:
089: // Registers newly loaded entity or throws exception if this entity is already loaded
090: public void addEntity(BOObjectImpl pEntity) throws BOException {
091: HashMap lTypeInstances = (HashMap) mEntities.get(pEntity
092: .getEntityType());
093: if (lTypeInstances == null)
094: mEntities.put(pEntity.getEntityType(),
095: lTypeInstances = new HashMap());
096: if (lTypeInstances.containsKey(pEntity.getEntityInstanceId()))
097: throw new BOUnexpectedProgramConditionException(
098: "BOObject representing same record has already been instantiated in this domain.");
099: lTypeInstances.put(pEntity.getEntityInstanceId(), pEntity);
100: }
101:
102: // Returns registered instance of the entity or null if nothing is registered
103: public BOObjectImpl getEntity(String pEntityType,
104: String pEntityInstanceId) throws BOException {
105: HashMap lTypeInstances = (HashMap) mEntities.get(pEntityType);
106: if (lTypeInstances == null)
107: return null;
108: return (BOObjectImpl) lTypeInstances.get(pEntityInstanceId);
109: }
110:
111: // Returns collection of all entities of the given types.
112: // The collection is empty if no entities are stored
113: // Supplied array of entity types allows to cater for the hierarchy of subclass entities
114: public Collection getAllEntities(String[] pEntityTypes)
115: throws BOException {
116: List lResultList = new ArrayList();
117: for (int i = 0; i < pEntityTypes.length; i++) {
118: HashMap lTypeInstances = (HashMap) mEntities
119: .get(pEntityTypes[i]);
120: if (lTypeInstances != null)
121: lResultList.addAll(lTypeInstances.values());
122: }
123: return lResultList;
124: }
125:
126: // Returns registered instance of the entity with given natural primary key
127: // Supplied array of entity types allows to cater for the hierarchy of subclass entities
128: public BOObjectImpl getEntityWithNaturalPrimaryKey(
129: String[] pEntityTypes, Object[] pNaturalPrimaryKey)
130: throws BOException {
131: for (int i = 0; i < pEntityTypes.length; i++) {
132: HashMap lTypeInstances = (HashMap) mEntities
133: .get(pEntityTypes[i]);
134: if (lTypeInstances != null) {
135: for (Iterator lTypeIterator = lTypeInstances.values()
136: .iterator(); lTypeIterator.hasNext();) {
137: BOObjectImpl lEntityObject = (BOObjectImpl) lTypeIterator
138: .next();
139: Object[] lNaturalPrimaryKey = lEntityObject
140: .getNaturalPrimaryKey();
141: if (lNaturalPrimaryKey == null
142: || lNaturalPrimaryKey.length == 0)
143: throw new BOUnexpectedProgramConditionException(
144: "Natural primary key is not expected to be empty");
145: boolean lMatch = true;
146: for (int j = 0; j < lNaturalPrimaryKey.length
147: && lMatch == true; j++)
148: lMatch = pNaturalPrimaryKey[j]
149: .equals(lNaturalPrimaryKey[j]);
150: if (lMatch)
151: return lEntityObject;
152: }
153: }
154: }
155: return null;
156: }
157:
158: // Returns registered instance of the entity or null if nothing is registered
159: public boolean containsEntity(String pEntityType,
160: String pEntityInstanceId) throws BOException {
161: return getEntity(pEntityType, pEntityInstanceId) != null;
162: }
163:
164: // Returns registered instance of the entity or null if nothing is registered
165: // Supplied array of entity types allows to cater for the hierarchy of subclass entities
166: public boolean containsEntityWithNaturalPrimaryKey(
167: String[] pEntityTypes, Object[] pNaturalPrimaryKey)
168: throws BOException {
169: return getEntityWithNaturalPrimaryKey(pEntityTypes,
170: pNaturalPrimaryKey) != null;
171: }
172:
173: /* Returns true if object is read only representing an existing entity */
174: public boolean isReadOnly() throws BOException {
175: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
176: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
177: if (mState == cStateReadOnly)
178: return true;
179: checkValidState();
180: return false;
181: }
182:
183: /* Returns true if object is in transaction and represents an existing entity which is being edited */
184: public boolean isBeingEdited() throws BOException {
185: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
186: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
187: if (mState == cStateBeingEdited)
188: return true;
189: checkValidState();
190: return false;
191: }
192:
193: /* Returns if object is allowed to execute getter and throws exception if it is not */
194: public void assertGetter() throws BOException {
195: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
196: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
197: if (mState == cStateReadOnly || mState == cStateBeingEdited)
198: return;
199: throw createInvalidOperationException();
200: }
201:
202: /* Returns if object is allowed to execute getter and throws exception if it is not */
203: public void assertSetter() throws BOException {
204: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
205: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
206: if (mState == cStateBeingEdited)
207: return;
208: throw createInvalidOperationException();
209: }
210:
211: /*
212: * This method must be called immediately after domain construction in order
213: * to set it up as existing object. Object is setup as Existing / ReadOnly
214: * in this case and therefore transaction is not required
215: */
216: public final void setupForExisting() throws BOException {
217: // Check if we are in the right status
218: if (mState != cStateNotSetUp)
219: throw new BONewObjectRequiredException(); // Attempt to set up the object second time
220: mState = cStateReadOnly;
221: }
222:
223: /* Marks domain as editable and registers it with transaction */
224: public final void beginEdit() throws BOException {
225: // Allow multiple begin edits.
226: if (isBeingEdited())
227: return;
228: // You can only begin edit read only object
229: if (!isReadOnly())
230: throw createInvalidOperationException();
231: // Call the state transition method while not yet in the transaction
232: onBeginEdit();
233: // Get existing or create new internal transaction controller
234: InternalTransactionController lTxController = InternalTransactionController
235: .getInstance();
236: lTxController.registerDomain(this );
237: mState = cStateBeingEdited;
238: }
239:
240: /** Saves changes if this domain object is editable. This method is forgiving in a sence that
241: * if the deomain object is not editable it just returns silently. Useful in cases when
242: * domain object is passed around the code (ie. processing code does not know what sate domain is in)
243: * and there is a need to save changes */
244: public final void saveChangesIfNecessary() throws BOException {
245: // Just call the other method if we are editable
246: if (isBeingEdited())
247: saveChanges();
248: }
249:
250: /* Saves changes collected from the most recent call to saveChanges() or beginEdit(),
251: * whichever is most recent. The domain has no outstanding changes in memory after completion of
252: * this call. Note that calling this method explicitly is not essential -
253: * it will be called automatically during transaction commit. Still, there may be
254: * valid reasons to call this method explicitly:
255: * <UL>
256: * <LI>Caller wants to explicitly make sure that collected changes do not violate any database storage constraints</LI>
257: * <LI>Caller wants to save all changes before executing database search</LI>
258: * </UL>
259: */
260: public final void saveChanges() throws BOException {
261: // We can only saveChanges on editable domain.
262: if (!isBeingEdited())
263: throw createInvalidOperationException();
264: sLogger
265: .debug("Commencing saving changes to all entities in the domain");
266: sLogger.debug("Found " + mMainInstructionsList.size()
267: + " persistence instructions to execute.");
268: try {
269: // Iterate through main list of PersistenceInstructions and execute them one by one
270: for (Iterator lInstructionsIterator = mMainInstructionsList
271: .iterator(); lInstructionsIterator.hasNext();) {
272: PersistenceInstruction lInstruction = (PersistenceInstruction) lInstructionsIterator
273: .next();
274: Object[] lBOImplArray = lInstruction.InstancesList
275: .toArray((Object[]) Array.newInstance(
276: lInstruction.ImplClass,
277: lInstruction.InstancesList.size()));
278: lInstruction.PersistenceMethod.invoke(null,
279: new Object[] { lBOImplArray });
280: }
281: // Iterate through deferred list of PersistenceInstructions and execute them one by one
282: for (Iterator lInstructionsIterator = mDeferredInstructionsList
283: .iterator(); lInstructionsIterator.hasNext();) {
284: PersistenceInstruction lInstruction = (PersistenceInstruction) lInstructionsIterator
285: .next();
286: Object[] lBOImplArray = lInstruction.InstancesList
287: .toArray((Object[]) Array.newInstance(
288: lInstruction.ImplClass,
289: lInstruction.InstancesList.size()));
290: lInstruction.PersistenceMethod.invoke(null,
291: new Object[] { lBOImplArray });
292: }
293: // Do save on all involved entities first
294: for (Iterator lInstructionsIterator = mMainInstructionsList
295: .iterator(); lInstructionsIterator.hasNext();) {
296: PersistenceInstruction lInstruction = (PersistenceInstruction) lInstructionsIterator
297: .next();
298: for (Iterator lBOImplsIterator = lInstruction.InstancesList
299: .iterator(); lBOImplsIterator.hasNext();) {
300: BOObjectImpl lBOImpl = (BOObjectImpl) lBOImplsIterator
301: .next();
302: lBOImpl.doSave();
303: }
304: }
305: // Modify persistence instructions list to reflect the latest changes
306: // All entities with create instructions - populate with update instructions
307: // all entities with delete instructions - delete from the instructions list
308: mMainInstructionsList.doAfterSave();
309: mDeferredInstructionsList.doAfterSave();
310: } catch (Throwable t) {
311: // Unpack invocation target exception
312: if (t instanceof InvocationTargetException)
313: t = ((InvocationTargetException) t)
314: .getTargetException();
315: // BOException can be rethrown as is
316: if (t instanceof BOException)
317: throw (BOException) t;
318: if (t instanceof PSException)
319: throw new BOPersistenceServiceInvocationException(
320: "Caught exception while saving pending changes to domain entities",
321: (PSException) t);
322: if (t instanceof NamingException)
323: throw new BONamingAndDirectoryServiceInvocationException(
324: "Caught exception while saving pending changes to domain entities",
325: (NamingException) t);
326: throw new BOException(
327: "Caught exception while saving pending changes to domain entities",
328: t);
329: }
330: }
331:
332: /* Overridable method. Called when existing object enters being edited stage */
333: protected void onBeginEdit() throws BOException {
334: }
335:
336: /* Encapsulates commit procedure */
337: protected final void doCommit() throws BOException {
338: try {
339: // Do commit on all entities first
340: for (Iterator lBOImplsIterator = mEntitiesInTransaction
341: .values().iterator(); lBOImplsIterator.hasNext();) {
342: BOObjectImpl lBOImpl = (BOObjectImpl) lBOImplsIterator
343: .next();
344: lBOImpl.doCommit();
345: }
346: // Work on domain object itself
347: switch (mState) {
348: case cStateBeingEdited:
349: onCommitUpdate();
350: mState = cStateReadOnly;
351: break;
352: default:
353: throw createInvalidOperationException();
354: }
355: } finally {
356: // This method is final call in the transaction
357: cleanUpTransactionData();
358: }
359: }
360:
361: /* Encapsulates commit action by this object */
362: protected void onCommitUpdate() throws BOException {
363: /* Do nothing by default */
364: }
365:
366: /* Encapsulates rollback procedure */
367: protected final void doRollback() throws BOException {
368: try {
369: // Do rollback on all entities first
370: for (Iterator lBOImplsIterator = mEntitiesInTransaction
371: .values().iterator(); lBOImplsIterator.hasNext();) {
372: BOObjectImpl lBOImpl = (BOObjectImpl) lBOImplsIterator
373: .next();
374: lBOImpl.doRollback();
375: }
376: // Work on domain object itself
377: switch (mState) {
378: case cStateBeingEdited:
379: onRollbackUpdate();
380: mState = cStateReadOnly;
381: break;
382: default:
383: throw createInvalidOperationException();
384: }
385: } finally {
386: // This method is final call in the transaction
387: cleanUpTransactionData();
388: }
389: }
390:
391: /* Encapsulates rollback action by this object */
392: protected void onRollbackUpdate() throws BOException {
393: /* Do nothing by default */
394: }
395:
396: // Helper. Checks if BO is setup and throws exception if it is not set
397: private void checkValidState() throws BOException {
398: if (mState == cStateNotSetUp)
399: throw new BOObjectNotInitialisedException();
400: }
401:
402: // Helper. Creates the invalid operation exception based on the object's state
403: // Call it at the point when it is established that current state is not valid for the current operation
404: public BOException createInvalidOperationException() {
405: switch (mState) {
406: case cStateNotSetUp:
407: return new BOObjectNotInitialisedException();
408: case cStateReadOnly:
409: return new BOInvalidOperationForReadOnlyObjectDomainException();
410: case cStateBeingEdited:
411: return new BOInvalidOperationForObjectDomainBeingEditedException();
412: default:
413: return new BOObjectUnusableException();
414: }
415: }
416:
417: // This object keeps details of outstanding persistence instruction
418: // Persistence instfruction is one of create, update or delete entity
419: // During transaction we keep accumulating these persistence instructions instead of
420: // executing them on the spot. This gives us an opportunity to run batch operations prior to
421: // transaction commit.
422: private class PersistenceInstruction {
423: public Class ImplClass = null;
424: BOObjectImpl.BOImplMetaData ImplMetaData = null;
425: public Method PersistenceMethod = null;
426: public ArrayList InstancesList = new ArrayList();
427: }
428:
429: // This object keeps a list of outstanding persistence instructions
430: // Note that each instruction may have a number of actual entities attached to it
431: private class PersistenceInstructionList {
432: private ArrayList mInstructionList = new ArrayList();
433: private PersistenceInstruction mLastInstruction = null;
434:
435: // Adds persistence instructon to the list. Collects things in batches
436: public void addInstruction(Method pPersistenceMethod,
437: BOObjectImpl.BOImplMetaData pImplMetaData,
438: BOObjectImpl pBOImpl) {
439: if (mLastInstruction == null
440: || mLastInstruction.PersistenceMethod != pPersistenceMethod
441: || mLastInstruction.ImplClass != pBOImpl.getClass()) {
442: mInstructionList
443: .add(mLastInstruction = new PersistenceInstruction());
444: mLastInstruction.PersistenceMethod = pPersistenceMethod;
445: mLastInstruction.ImplClass = pBOImpl.getClass();
446: mLastInstruction.ImplMetaData = pImplMetaData;
447: }
448: mLastInstruction.InstancesList.add(pBOImpl);
449: }
450:
451: // Removes persistence instructon from the the list.
452: public void removeInstruction(Method pPersistenceMethod,
453: BOObjectImpl.BOImplMetaData pImplMetaData,
454: BOObjectImpl pBOImpl) {
455: List lInstructionsToBeDeleted = null;
456: // Iterate through the instruction list and find this object
457: for (Iterator lInstructionIter = mInstructionList
458: .iterator(); lInstructionIter.hasNext();) {
459: PersistenceInstruction lInstruction = (PersistenceInstruction) lInstructionIter
460: .next();
461: if (lInstruction.PersistenceMethod
462: .equals(pPersistenceMethod)
463: && lInstruction.ImplClass.equals(pBOImpl
464: .getClass())
465: && lInstruction.ImplMetaData
466: .equals(pImplMetaData)
467: && lInstruction.InstancesList.remove(pBOImpl) == true) {
468: // We have in fact removed the istruction for this Bo
469: // If this instruction batch is empty - we need to remove the batch as well
470: if (mLastInstruction.InstancesList.isEmpty()) {
471: if (lInstructionsToBeDeleted == null)
472: lInstructionsToBeDeleted = new ArrayList();
473: lInstructionsToBeDeleted.add(lInstruction);
474: }
475: }
476: }
477: // We may have some empty batches which should be deleted
478: if (lInstructionsToBeDeleted != null) {
479: // Remove all instructions that need to be removed
480: for (Iterator lInstructionIter = lInstructionsToBeDeleted
481: .iterator(); lInstructionIter.hasNext();) {
482: PersistenceInstruction lInstructionToBeDeleted = (PersistenceInstruction) lInstructionIter
483: .next();
484: mInstructionList.remove(lInstructionToBeDeleted);
485: }
486: // We also may need to fix the last instruction reference (because we may have deleted it)
487: if (lInstructionsToBeDeleted.contains(mLastInstruction)) {
488: int lInstructionListSize = mInstructionList.size();
489: mLastInstruction = (PersistenceInstruction) (lInstructionListSize > 0 ? mInstructionList
490: .get(lInstructionListSize - 1)
491: : null);
492: }
493: }
494: }
495:
496: public int size() {
497: return mInstructionList.size();
498: }
499:
500: public void clear() {
501: mInstructionList.clear();
502: mLastInstruction = null;
503: }
504:
505: // This method will modify the list of persistence instructions after each save
506: // it will remove delete instructions and change create instructions to update ones
507: public void doAfterSave() {
508: // This will remove all delete instructions and change the create instructions to update ones
509: PersistenceInstruction[] lAllInstructions = (PersistenceInstruction[]) mInstructionList
510: .toArray(new PersistenceInstruction[mInstructionList
511: .size()]);
512: mInstructionList.clear();
513: mLastInstruction = null;
514: for (int i = 0; i < lAllInstructions.length; i++) {
515: PersistenceInstruction lInstruction = lAllInstructions[i];
516: if (lInstruction.PersistenceMethod == lInstruction.ImplMetaData.DeleteMethod)
517: continue; // Deleted objects will not be saved again
518: if (lInstruction.PersistenceMethod == lInstruction.ImplMetaData.UpdateMethod) {
519: // Basically we need to copy as is. But we will attempt to see if we can increase the batch
520: if (mLastInstruction != null
521: && mLastInstruction.PersistenceMethod == lInstruction.PersistenceMethod
522: && mLastInstruction.ImplClass == lInstruction.ImplClass) {
523: // Possibly because of change new - to existing we have now found that
524: // two batches can be amalgamated. Do it
525: mLastInstruction.InstancesList
526: .addAll(lInstruction.InstancesList);
527: } else {
528: // Make a copy and mark this instruction as last
529: mInstructionList.add(lInstruction);
530: mLastInstruction = lInstruction;
531: }
532: } else if (lInstruction.PersistenceMethod == lInstruction.ImplMetaData.UpdateChangesToReferences) {
533: // This method just copied straight to the target
534: mInstructionList.add(lInstruction);
535: mLastInstruction = lInstruction;
536: } else if (lInstruction.PersistenceMethod == lInstruction.ImplMetaData.CreateMethod) {
537: // This method becomes update method if there is one in the bo
538: // If there is no update method - this method is removed
539: if (lInstruction.ImplMetaData.UpdateMethod == null)
540: continue; // Nothing to add
541: lInstruction.PersistenceMethod = lInstruction.ImplMetaData.UpdateMethod; // Change create method to to update method
542:
543: // Basically we need to copy as is. But we will attempt to see if we can increase the batch
544: if (mLastInstruction != null
545: && mLastInstruction.PersistenceMethod == lInstruction.PersistenceMethod
546: && mLastInstruction.ImplClass == lInstruction.ImplClass) {
547: // Possibly because of change new - to existing we have now found that
548: // two batches can be amalgamated. Do it
549: mLastInstruction.InstancesList
550: .addAll(lInstruction.InstancesList);
551: } else {
552: // Make a copy and mark this instruction as last
553: mInstructionList.add(lInstruction);
554: mLastInstruction = lInstruction;
555: }
556: }
557: }
558: }
559:
560: public Iterator iterator() {
561: return mInstructionList.iterator();
562: }
563: }
564:
565: // This object keeps details of the change to association between entities
566: // Change may be one of the following:
567: // 1. Association creation. In this case Old values are nulls and new values are populated with instance ids
568: // 2. Association removal. In this case Old values are populated with instance ids and new values are nulls
569: // 3. Association edit. In this case Old values are populated with instance ids and new values are populated with instance ids
570: private class EntityAssociationChangeDetails {
571: // The unique identifier of the association
572: public String AssociationTypeId;
573: // The unique identifier of the aggregator entity instance as it was before this action
574: // Equals null if association was not established before (i.e. this record is the record of creation of an association)
575: public String OldAggregatorEntityInstance;
576: // The unique identifier of the aggregatee entity instance as it was before this action
577: // Equals null if association was not established before (i.e. this record is the record of creation of an association)
578: public String OldAggregateeEntityInstance;
579: // The unique identifier of the aggregator entity instance as it will be after this action
580: // Equals null if association will not exist after (i.e. this record is the record of deletion of an association)
581: public String NewAggregatorEntityInstance;
582: // The unique identifier of the aggregatee entity instance as it will be after this action
583: // Equals null if association will not exist after (i.e. this record is the record of deletion of an association)
584: public String NewAggregateeEntityInstance;
585: }
586:
587: // This object keeps the list of changes to associations
588: private class EntityAssociationChangesList {
589: // Details mapped by the association type id. The key is AssociationTypeId, the value is the list of actual association details
590: private Map mDetailsByAssociationId = new HashMap();
591:
592: // Details mapped by the entity. The key is EntityUri, the value is the list of actual association details
593: private Map mDetailsByEntityInstanceId = new HashMap();
594:
595: // Adds details about association in the list
596: public void addAssociation(String pAssociationTypeId,
597: String pNewAggregatorEntityInstanceId,
598: String pNewAggregateeEntityInstanceId) {
599: EntityAssociationChangeDetails lEntityAssociationChangeDetails = new EntityAssociationChangeDetails();
600: lEntityAssociationChangeDetails.AssociationTypeId = pAssociationTypeId;
601: lEntityAssociationChangeDetails.NewAggregatorEntityInstance = pNewAggregatorEntityInstanceId;
602: lEntityAssociationChangeDetails.NewAggregateeEntityInstance = pNewAggregateeEntityInstanceId;
603: // Add to the list changes to of association
604: {
605: List lAssociationChanges = (List) mDetailsByAssociationId
606: .get(pAssociationTypeId);
607: if (lAssociationChanges == null)
608: mDetailsByAssociationId.put(pAssociationTypeId,
609: lAssociationChanges = new ArrayList());
610: lAssociationChanges
611: .add(lEntityAssociationChangeDetails);
612: }
613: // Add to the list changes to the aggregator entity
614: {
615: List lEntityChanges = (List) mDetailsByEntityInstanceId
616: .get(pNewAggregatorEntityInstanceId);
617: if (lEntityChanges == null)
618: mDetailsByEntityInstanceId.put(
619: pNewAggregatorEntityInstanceId,
620: lEntityChanges = new ArrayList());
621: lEntityChanges.add(lEntityAssociationChangeDetails);
622: }
623: // Add to the list changes to the aggregatee entity
624: {
625: List lEntityChanges = (List) mDetailsByEntityInstanceId
626: .get(pNewAggregateeEntityInstanceId);
627: if (lEntityChanges == null)
628: mDetailsByEntityInstanceId.put(
629: pNewAggregateeEntityInstanceId,
630: lEntityChanges = new ArrayList());
631: lEntityChanges.add(lEntityAssociationChangeDetails);
632: }
633: }
634:
635: // Removes details about association from the list
636: public void removeAssociationDetails(String pAssociationTypeId,
637: String pOldAggregatorEntityInstanceId,
638: String pOldAggregateeEntityInstanceId) {
639: EntityAssociationChangeDetails lEntityAssociationChangeDetails = new EntityAssociationChangeDetails();
640: lEntityAssociationChangeDetails.AssociationTypeId = pAssociationTypeId;
641: lEntityAssociationChangeDetails.OldAggregatorEntityInstance = pOldAggregatorEntityInstanceId;
642: lEntityAssociationChangeDetails.OldAggregateeEntityInstance = pOldAggregateeEntityInstanceId;
643: // Add to the list changes to of association
644: {
645: List lAssociationChanges = (List) mDetailsByAssociationId
646: .get(pAssociationTypeId);
647: if (lAssociationChanges == null)
648: mDetailsByAssociationId.put(pAssociationTypeId,
649: lAssociationChanges = new ArrayList());
650: lAssociationChanges
651: .add(lEntityAssociationChangeDetails);
652: }
653: // Add to the list changes to the aggregator entity
654: {
655: List lEntityChanges = (List) mDetailsByEntityInstanceId
656: .get(pOldAggregatorEntityInstanceId);
657: if (lEntityChanges == null)
658: mDetailsByEntityInstanceId.put(
659: pOldAggregatorEntityInstanceId,
660: lEntityChanges = new ArrayList());
661: lEntityChanges.add(lEntityAssociationChangeDetails);
662: }
663: // Add to the list changes to the aggregatee entity
664: {
665: List lEntityChanges = (List) mDetailsByEntityInstanceId
666: .get(pOldAggregateeEntityInstanceId);
667: if (lEntityChanges == null)
668: mDetailsByEntityInstanceId.put(
669: pOldAggregateeEntityInstanceId,
670: lEntityChanges = new ArrayList());
671: lEntityChanges.add(lEntityAssociationChangeDetails);
672: }
673: }
674:
675: // Cleans up all contents of the list
676: public void clear() {
677: mDetailsByAssociationId.clear();
678: mDetailsByEntityInstanceId.clear();
679: }
680: }
681:
682: // This list contains details of all associations which have been changed in the current transaction
683: private EntityAssociationChangesList mEntityAssociationInTransactionChangesList = new EntityAssociationChangesList();
684:
685: // Set of entities in transaction. Used to keep track of entities
686: // in need of roolback or commit processing
687: private Map mEntitiesInTransaction = new HashMap();
688: // Main list of transaction instructions - executed when commit command is issued
689: private PersistenceInstructionList mMainInstructionsList = new PersistenceInstructionList();
690: // Secondary list of transaction instructions - executed after main list
691: private PersistenceInstructionList mDeferredInstructionsList = new PersistenceInstructionList();
692:
693: // Registers entity for the creation before commit
694: void registerEntityForCreate(BOObjectImpl pBOImpl)
695: throws BOException {
696: // First obtain implementation's metadata
697: BOObjectImpl.BOImplMetaData lBOImplMetaData = BOObjectImpl
698: .getBOImplMetaData(pBOImpl);
699: if (lBOImplMetaData.CreateMethod == null)
700: throw new BOInvalidOperationForObjectException(
701: "Operation registerForCreate() is not valid for the entity. EntityType:"
702: + pBOImpl.getEntityType());
703: // Make sure that this domain is in transaction
704: if (!isBeingEdited())
705: throw new BOObjectDomainNotInTransactionException();
706: // Guard against duplicate modification instructions in the same transaction
707: if (mEntitiesInTransaction.put(pBOImpl.getEntityUri(), pBOImpl) != null)
708: throw new BOObjectAlreadyInTransactionException();
709: // Now add appropriate persistence instruction
710: mMainInstructionsList.addInstruction(
711: lBOImplMetaData.CreateMethod, lBOImplMetaData, pBOImpl);
712: // Add update many-to-many if this entity has one
713: if (lBOImplMetaData.UpdateChangesToReferences != null)
714: mDeferredInstructionsList.addInstruction(
715: lBOImplMetaData.UpdateChangesToReferences,
716: lBOImplMetaData, pBOImpl);
717: if (sLogger.isDebugEnabled())
718: sLogger
719: .debug("Creation of BO with InstanceId = '"
720: + pBOImpl.getEntityInstanceId()
721: + "' is added to deferred persistence instructions list. Current count of deferred persistence instructions is "
722: + mMainInstructionsList.size());
723: }
724:
725: // Registers entity for the creation before commit
726: void unregisterEntityForCreate(BOObjectImpl pBOImpl)
727: throws BOException {
728: // First obtain implementation's metadata
729: BOObjectImpl.BOImplMetaData lBOImplMetaData = BOObjectImpl
730: .getBOImplMetaData(pBOImpl);
731: if (lBOImplMetaData.CreateMethod == null)
732: throw new BOInvalidOperationForObjectException(
733: "Operation unregisterForCreate() is not valid for the entity. EntityType:"
734: + pBOImpl.getEntityType());
735: // Make sure that this domain is in transaction
736: if (!isBeingEdited())
737: throw new BOObjectDomainNotInTransactionException();
738: // Guard against object which is not in transaction
739: if (!mEntitiesInTransaction.containsKey(pBOImpl.getEntityUri()))
740: throw new BOObjectNotInTransactionException();
741: // Now remove appropriate persistence instruction
742: mMainInstructionsList.removeInstruction(
743: lBOImplMetaData.CreateMethod, lBOImplMetaData, pBOImpl);
744: // Add update many-to-many if this entity has one
745: if (lBOImplMetaData.UpdateChangesToReferences != null)
746: mDeferredInstructionsList.removeInstruction(
747: lBOImplMetaData.UpdateChangesToReferences,
748: lBOImplMetaData, pBOImpl);
749: if (sLogger.isDebugEnabled())
750: sLogger
751: .debug("Creation of BO with InstanceId = '"
752: + pBOImpl.getEntityInstanceId()
753: + "' is removed from deferred persistence instructions list. Current count of deferred persistence instructions is "
754: + mMainInstructionsList.size());
755: }
756:
757: // Registers object for the update before commit
758: void registerEntityForUpdate(BOObjectImpl pBOImpl)
759: throws BOException {
760: // First obtain implementation's metadata
761: BOObjectImpl.BOImplMetaData lBOImplMetaData = BOObjectImpl
762: .getBOImplMetaData(pBOImpl);
763: if (lBOImplMetaData.UpdateMethod == null)
764: throw new BOInvalidOperationForObjectException(
765: "Operation registerForUpdate() is not valid for the entity. EntityType:"
766: + pBOImpl.getEntityType());
767: // Make sure that this domain is in transaction
768: if (!isBeingEdited())
769: throw new BOObjectDomainNotInTransactionException();
770: // Guard against duplicate modification instructions in the same transaction
771: if (mEntitiesInTransaction.put(pBOImpl.getEntityUri(), pBOImpl) != null)
772: throw new BOObjectAlreadyInTransactionException();
773: // Now add appropriate persistence instruction
774: mMainInstructionsList.addInstruction(
775: lBOImplMetaData.UpdateMethod, lBOImplMetaData, pBOImpl);
776: // Add update many-to-many if this entity has one
777: if (lBOImplMetaData.UpdateChangesToReferences != null)
778: mDeferredInstructionsList.addInstruction(
779: lBOImplMetaData.UpdateChangesToReferences,
780: lBOImplMetaData, pBOImpl);
781: if (sLogger.isDebugEnabled())
782: sLogger
783: .debug("Update of BO with InstanceId = '"
784: + pBOImpl.getEntityInstanceId()
785: + "' is added to deferred persistence instructions list. Current count of deferred persistence instructions is "
786: + mMainInstructionsList.size());
787: }
788:
789: // Registers object for the delete before commit
790: void registerEntityForDelete(BOObjectImpl pBOImpl)
791: throws BOException {
792: // First obtain implementation's metadata
793: BOObjectImpl.BOImplMetaData lBOImplMetaData = BOObjectImpl
794: .getBOImplMetaData(pBOImpl);
795: if (lBOImplMetaData.DeleteMethod == null)
796: throw new BOInvalidOperationForObjectException(
797: "Operation registerForDelete() is not valid for the entity. EntityType:"
798: + pBOImpl.getEntityType());
799: // Make sure that this domain is in transaction
800: if (!isBeingEdited())
801: throw new BOObjectDomainNotInTransactionException();
802: // Guard against duplicate modification instructions in the same transaction
803: if (mEntitiesInTransaction.put(pBOImpl.getEntityUri(), pBOImpl) != null)
804: throw new BOObjectAlreadyInTransactionException();
805: // Now add appropriate persistence instruction
806: mMainInstructionsList.addInstruction(
807: lBOImplMetaData.DeleteMethod, lBOImplMetaData, pBOImpl);
808: if (sLogger.isDebugEnabled())
809: sLogger
810: .debug("Deletion of BO with InstanceId = '"
811: + pBOImpl.getEntityInstanceId()
812: + "' is added to deferred persistence instructions list. Current count of deferred persistence instructions is "
813: + mMainInstructionsList.size());
814: }
815:
816: // Unregisters object for the delete before commit
817: void unregisterEntityForDelete(BOObjectImpl pBOImpl)
818: throws BOException {
819: // First obtain implementation's metadata
820: BOObjectImpl.BOImplMetaData lBOImplMetaData = BOObjectImpl
821: .getBOImplMetaData(pBOImpl);
822: if (lBOImplMetaData.DeleteMethod == null)
823: throw new BOInvalidOperationForObjectException(
824: "Operation unregisterForDelete() is not valid for the entity. EntityType:"
825: + pBOImpl.getEntityType());
826: // Make sure that this domain is in transaction
827: if (!isBeingEdited())
828: throw new BOObjectDomainNotInTransactionException();
829: // Guard against object which is not in transaction
830: if (!mEntitiesInTransaction.containsKey(pBOImpl.getEntityUri()))
831: throw new BOObjectNotInTransactionException();
832: // Now add appropriate persistence instruction
833: mMainInstructionsList.removeInstruction(
834: lBOImplMetaData.DeleteMethod, lBOImplMetaData, pBOImpl);
835: if (sLogger.isDebugEnabled())
836: sLogger
837: .debug("Deletion of BO with InstanceId = '"
838: + pBOImpl.getEntityInstanceId()
839: + "' is removed from deferred persistence instructions list. Current count of deferred persistence instructions is "
840: + mMainInstructionsList.size());
841: }
842:
843: // This helper cleans up the data about transacton
844: private void cleanUpTransactionData() {
845: mEntitiesInTransaction.clear();
846: mMainInstructionsList.clear();
847: mDeferredInstructionsList.clear();
848: mEntityAssociationInTransactionChangesList.clear();
849: if (sLogger.isDebugEnabled())
850: sLogger
851: .debug("Deferred persistence instructions list is cleared.");
852: }
853: }
|