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.Method;
019: import java.lang.reflect.Modifier;
020: import java.util.HashMap;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: import com.metaboss.enterprise.bo.BOException;
026: import com.metaboss.enterprise.bo.BOIllegalArgumentException;
027: import com.metaboss.enterprise.bo.BOIllegalObjectImplementationException;
028: import com.metaboss.enterprise.bo.BOInvalidOperationForDeletedObjectException;
029: import com.metaboss.enterprise.bo.BOInvalidOperationForNewlyCreatedObjectException;
030: import com.metaboss.enterprise.bo.BOInvalidOperationForObjectBeingEditedException;
031: import com.metaboss.enterprise.bo.BOInvalidOperationForObjectException;
032: import com.metaboss.enterprise.bo.BOInvalidOperationForReadOnlyObjectException;
033: import com.metaboss.enterprise.bo.BONewObjectRequiredException;
034: import com.metaboss.enterprise.bo.BOObject;
035: import com.metaboss.enterprise.bo.BOObjectNotInitialisedException;
036: import com.metaboss.enterprise.bo.BOObjectUnusableException;
037:
038: /* Base implementation class for all BOs */
039: public abstract class BOObjectImpl implements BOObject {
040: // Static logging instance
041: private static final Log sLogger = LogFactory
042: .getLog(BOObjectImpl.class);
043:
044: // The BO Object can be in one of the following states
045: // This state means that the object has become unuseable after some operations
046: // which have happend to it. Example is deleted object after successfull commit or
047: // newly created object after unsuccessfull commit or discardChanges operation
048: private static final int cStateUnusable = -1;
049: // The BO Object can be in one of the following states
050: // Unusable state. Just constructed object has it. Also rolled back new object has it
051: private static final int cStateNotSetUp = 0;
052: // Normal bo representing an existing entity. Bo object is read only
053: private static final int cStateReadOnly = 1;
054: // Normal bo representing an existing entity. Bo object is editable, meaning that
055: // when current transaction will commit - this bo will initiate an update to the database
056: // After successfull commit and unsuccessfull commit bo will revert to cStateReadOnly
057: private static final int cStateBeingEdited = 2;
058: // Normal bo representing an existing entity. Bo object is deleted, meaning that
059: // when current transaction will commit - this bo will initiate delete to the database
060: // After successfull commit bo will become unuseable, after unsuccessfull commit
061: // bo will revert to cStateReadOnly
062: private static final int cStateDeleted = 3;
063: // Bo representing newly proposed entity. Bo object is being editable, meaning that
064: // when current transaction will commit - this bo will initiate insert to the database
065: // After successfull commit bo will become cStateReadOnly, after unsuccessfull commit
066: // bo will become unusable
067: private static final int cStateNewlyCreated = 4;
068:
069: // The actual state variable
070: private int mState = cStateNotSetUp;
071: // The reference to the domain object this entity belongs to
072: private BOObjectDomainImpl mDomain;
073: // The entity type identifier - uniquely identifies the kind of entity
074: private String mEntityType;
075: // The entity instance identifier - uniquely identifies the instance of the entity
076: private String mEntityInstanceId;
077:
078: // Default constructor is disabled
079: private BOObjectImpl() throws BOException {
080: }
081:
082: // This constructs the new entity object
083: protected BOObjectImpl(BOObjectDomainImpl pDomain,
084: String pEntityType, String pEntityInstanceId)
085: throws BOException {
086: if (pDomain == null)
087: throw new BOIllegalArgumentException(
088: "null is not allowed for pDomain argument");
089: if (pEntityType == null)
090: throw new BOIllegalArgumentException(
091: "null is not allowed for pEntityType argument");
092: if (pEntityInstanceId == null)
093: throw new BOIllegalArgumentException(
094: "null is not allowed for pEntityInstanceId argument");
095: mDomain = pDomain;
096: mEntityType = pEntityType;
097: mEntityInstanceId = pEntityInstanceId;
098: mDomain.addEntity(this );
099: }
100:
101: // Getter for domain
102: protected BOObjectDomainImpl getObjectDomain() throws BOException {
103: return mDomain;
104: }
105:
106: // Getter for entity type
107: protected String getEntityType() throws BOException {
108: return mEntityType;
109: }
110:
111: // Getter for entity instance id
112: protected String getEntityInstanceId() throws BOException {
113: return mEntityInstanceId;
114: }
115:
116: // Getter for entity uri - globally unique object identifier. Basically
117: // combines entity type and instance identifier
118: protected String getEntityUri() throws BOException {
119: return "entity:/" + mEntityType + "/" + mEntityInstanceId;
120: }
121:
122: // This method should be overridden for entities which do have a natural primary key
123: protected Object[] getNaturalPrimaryKey() throws BOException {
124: throw new BOInvalidOperationForObjectException(
125: "Operation getNaturalPrimaryKey() is not valid for the entity. EntityType:"
126: + mEntityType);
127: }
128:
129: /* Returns true if object is read only representing an existing entity */
130: public boolean isReadOnly() throws BOException {
131: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
132: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
133: if (mState == cStateReadOnly)
134: return true;
135: checkValidState();
136: return false;
137: }
138:
139: /* Returns true if object is in transaction and representing an entity, which has been deleted */
140: public boolean isDeleted() throws BOException {
141: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
142: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
143: if (mState == cStateDeleted)
144: return true;
145: checkValidState();
146: return false;
147: }
148:
149: /* Returns true if object is in transaction and represents an existing entity which is being edited */
150: public boolean isBeingEdited() throws BOException {
151: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
152: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
153: if (mState == cStateBeingEdited)
154: return true;
155: checkValidState();
156: return false;
157: }
158:
159: /* Returns true if object is in transaction and represents proposed new entity which is being edited */
160: public boolean isNewlyCreated() throws BOException {
161: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
162: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
163: if (mState == cStateNewlyCreated)
164: return true;
165: checkValidState();
166: return false;
167: }
168:
169: /* Returns if object is allowed to execute getter and throws exception if it is not
170: * Getter is any operation which retrieves data from the object.
171: * Normally object would allow geter when it is in existing, new or being edited states */
172: public void assertGetter() throws BOException {
173: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
174: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
175: if (mState == cStateReadOnly || mState == cStateBeingEdited
176: || mState == cStateNewlyCreated)
177: return;
178: throw createInvalidOperationException();
179: }
180:
181: /* Returns if object is allowed to execute setter and throws exception if it is not
182: * Setter is any operation which sets data to the object.
183: * Normally object would allow setter when it is in new or being edited states */
184: public void assertSetter() 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 || mState == cStateNewlyCreated)
188: return;
189: throw createInvalidOperationException();
190: }
191:
192: /* Returns if object is allowed to have child objects created and throws exception if it is not
193: * Normally object would allow this in any nomal existance states such as NewlyCreated, ReadOnly or Being Edited */
194: public void assertChildCreator() 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 == cStateBeingEdited || mState == cStateReadOnly
198: || mState == cStateNewlyCreated)
199: return;
200: throw createInvalidOperationException();
201: }
202:
203: /* Returns if object is allowed to execute editor and throws exception if it is not
204: * Editor is any operation which changes data to the object.
205: * Normally object would allow editor when it is in being edited states */
206: public void assertEditor() throws BOException {
207: // This pattern of implementation does not waste time on unnecessary checks if object is in the right state
208: // That means that the application, which knows what it is doing will not run unnecessary tests all the time
209: if (mState == cStateBeingEdited)
210: return;
211: throw createInvalidOperationException();
212: }
213:
214: /** This method must be called immediately after BO construction in order
215: * to set it up as newly created object. Object is setup as NewlyCreate / BeingEdited
216: * in this case and therefore transaction is required */
217: public final void setupForNew() throws BOException {
218: if (sLogger.isDebugEnabled())
219: sLogger.debug("Created BO with InstanceId = '"
220: + mEntityInstanceId
221: + "'. It represents newly created entity.");
222:
223: // Check if we are in the right status
224: if (mState != cStateNotSetUp)
225: throw new BONewObjectRequiredException(); // Attempt to set up the object second time
226: mDomain.registerEntityForCreate(this );
227: // Call the state transition method
228: onCreateNew();
229: mState = cStateNewlyCreated;
230: }
231:
232: /* Overridable method. Called when new object being created */
233: protected void onCreateNew() throws BOException {
234: }
235:
236: /*
237: * This method must be called immediately after BO construction in order
238: * to set it up as existing object. Object is setup as Existing / ReadOnly
239: * in this case and therefore transaction is not required
240: */
241: public final void setupForExisting() throws BOException {
242: if (sLogger.isDebugEnabled())
243: sLogger.debug("Created BO with InstanceId = '"
244: + mEntityInstanceId
245: + "'. It represents existing entity.");
246:
247: // Check if we are in the right status
248: if (mState != cStateNotSetUp)
249: throw new BONewObjectRequiredException(); // Attempt to set up the object second time
250: mState = cStateReadOnly;
251: }
252:
253: /* Moves an Existing/ReadOnly BO to the Existing/BeingEdited stage */
254: public final void beginEdit() throws BOException {
255: if (sLogger.isDebugEnabled())
256: sLogger
257: .debug("Invoked beginEdit() on BO with InstanceId = '"
258: + mEntityInstanceId + "'.");
259:
260: // Allow multiple attempts to make an object editable
261: if (isBeingEdited()) {
262: if (sLogger.isDebugEnabled())
263: sLogger
264: .debug("BO with InstanceId = '"
265: + mEntityInstanceId
266: + "' is already being edited. beginEdit() has nothing to do.");
267: return;
268: }
269:
270: if (isNewlyCreated()) {
271: if (sLogger.isDebugEnabled())
272: sLogger
273: .debug("BO with InstanceId = '"
274: + mEntityInstanceId
275: + "' is newly created. beginEdit() has nothing to do.");
276: return;
277: }
278:
279: // You can only begin edit read only object
280: if (!isReadOnly())
281: throw createInvalidOperationException();
282:
283: // Call the state transition method while not yet in the transaction
284: onBeginEdit();
285: mDomain.registerEntityForUpdate(this );
286: mState = cStateBeingEdited;
287:
288: if (sLogger.isDebugEnabled())
289: sLogger
290: .debug("Completed beginEdit() on BO with InstanceId = '"
291: + mEntityInstanceId + "'.");
292: }
293:
294: /* Overridable method. Called when existing object enters being edited stage */
295: protected void onBeginEdit() throws BOException {
296: }
297:
298: /* Reloads the details of the object without changind its state. This
299: * discards a previously stored data, which may include unsaved changes */
300: public final void reload() throws BOException {
301: // We can only reload existing objects regardless whethere they are editable or not
302: if ((!isBeingEdited()) && (!isReadOnly()))
303: throw createInvalidOperationException();
304: // Call the worker method
305: onReload();
306: }
307:
308: /* Overridable method. Called when existing object needs to be reloaded. */
309: protected void onReload() throws BOException {
310: }
311:
312: /* Discards all the unsaved changes */
313: public final void discardChanges() throws BOException {
314: // Allow multiple deletes.
315: if (isDeleted()) {
316: // Unregister object for deletion and make it read only again
317: mDomain.unregisterEntityForDelete(this );
318: mState = cStateReadOnly;
319: } else if (isBeingEdited()) {
320: // Just undo all changes
321: onDiscardUpdate();
322: } else if (isNewlyCreated()) {
323: mDomain.unregisterEntityForCreate(this );
324: mState = cStateUnusable;
325: } else
326: throw createInvalidOperationException();
327: }
328:
329: /* Called after recent updates to the object have been discarded */
330: protected void onDiscardUpdate() throws BOException {
331: /* Do nothing by default */
332: }
333:
334: /*
335: * Marks this object for deletion of the underlying data base record. If object is
336: * already in transaction, and transaction supplied is the same one - than object
337: * is marked for deletion If object is not in transaction yet - attempt is made
338: * to beginEdit() this object and than mark it for deletion
339: */
340: public void delete() throws BOException {
341: if (sLogger.isDebugEnabled())
342: sLogger.debug("Invoked delete() on BO with InstanceId = '"
343: + mEntityInstanceId + "'.");
344:
345: // Allow multiple deletes.
346: if (isDeleted())
347: return;
348: // You can only delete read only object
349: if (!isReadOnly())
350: throw createInvalidOperationException();
351: mDomain.registerEntityForDelete(this );
352: mState = cStateDeleted;
353: }
354:
355: protected final void doSave() throws BOException {
356: // Run on save depending on internal state of the object without change to the state
357: switch (mState) {
358: case cStateNewlyCreated:
359: onSaveCreation();
360: break;
361: case cStateBeingEdited:
362: onSaveUpdate();
363: break;
364: case cStateDeleted:
365: onSaveDeletion();
366: break;
367: default:
368: throw createInvalidOperationException();
369: }
370: }
371:
372: /* Save action in this object if it is newly created */
373: protected void onSaveCreation() throws BOException {
374: /* Do nothing by default */
375: }
376:
377: /* Called after recent updates to the object have been saved */
378: protected void onSaveUpdate() throws BOException {
379: /* Do nothing by default */
380: }
381:
382: /* Save action in this object if it is being deleted */
383: protected void onSaveDeletion() throws BOException {
384: /* Do nothing by default */
385: }
386:
387: /* Encapsulates commit procedure */
388: protected final void doCommit() throws BOException {
389: /* Modify internal state of the object */
390: switch (mState) {
391: case cStateNewlyCreated:
392: onCommitCreation();
393: mState = cStateReadOnly;
394: break;
395: case cStateBeingEdited:
396: onCommitUpdate();
397: mState = cStateReadOnly;
398: break;
399: case cStateDeleted:
400: onCommitDeletion();
401: mState = cStateUnusable;
402: break;
403: default:
404: throw createInvalidOperationException();
405: }
406: }
407:
408: /* Encapsulates commit action by this object */
409: protected void onCommitCreation() throws BOException {
410: /* Do nothing by default */
411: }
412:
413: /* Encapsulates commit action by this object */
414: protected void onCommitUpdate() throws BOException {
415: /* Do nothing by default */
416: }
417:
418: /* Encapsulates commit action by this object */
419: protected void onCommitDeletion() throws BOException {
420: /* Do nothing by default */
421: }
422:
423: /* Encapsulates rollback procedure */
424: protected final void doRollback() throws BOException {
425: /* Modify internal state of the object */
426: switch (mState) {
427: case cStateNewlyCreated:
428: onRollbackCreation();
429: mState = cStateUnusable;
430: break;
431: case cStateBeingEdited:
432: onRollbackUpdate();
433: mState = cStateReadOnly;
434: break;
435: case cStateDeleted:
436: onRollbackDeletion();
437: mState = cStateReadOnly;
438: break;
439: default:
440: throw createInvalidOperationException();
441: }
442: }
443:
444: /* Encapsulates rollback action by this object */
445: protected void onRollbackCreation() throws BOException {
446: /* Do nothing by default */
447: }
448:
449: /* Encapsulates rollback action by this object */
450: protected void onRollbackUpdate() throws BOException {
451: /* Do nothing by default */
452: }
453:
454: /* Encapsulates rollback action by this object */
455: protected void onRollbackDeletion() throws BOException {
456: /* Do nothing by default */
457: }
458:
459: /* Optional preload of data */
460: public void preload() throws BOException {
461: checkValidState();
462: }
463:
464: // Helper. Checks if BO is setup and throws exception if it is not set
465: private void checkValidState() throws BOException {
466: if (mState == cStateNotSetUp)
467: throw new BOObjectNotInitialisedException();
468: if (mState == cStateUnusable)
469: throw new BOObjectUnusableException();
470: }
471:
472: // Helper. Creates the invalid operation exception based on the object's state
473: // Call it at the point when it is established that current state is not valid for the current operation
474: public BOException createInvalidOperationException() {
475: switch (mState) {
476: case cStateNotSetUp:
477: return new BOObjectNotInitialisedException();
478: case cStateReadOnly:
479: return new BOInvalidOperationForReadOnlyObjectException();
480: case cStateBeingEdited:
481: return new BOInvalidOperationForObjectBeingEditedException();
482: case cStateDeleted:
483: return new BOInvalidOperationForDeletedObjectException();
484: case cStateNewlyCreated:
485: return new BOInvalidOperationForNewlyCreatedObjectException();
486: default:
487: return new BOObjectUnusableException();
488: }
489: }
490:
491: static class BOImplMetaData {
492: public Method CreateMethod = null; // persistCreate() - insert object for the first time
493: public Method UpdateMethod = null; // persistUpdate() - save changes to object
494: public Method DeleteMethod = null; // persistDelete() - delete object
495: public Method UpdateChangesToReferences = null; // persistChangesToReferences() - save changes to many-to-many references
496: }
497:
498: private static HashMap cBOImplMetaDataMap = new HashMap();
499:
500: // Helper. Interrogates the implementation class and stores necessary method references once per JVM run
501: static BOImplMetaData getBOImplMetaData(BOObjectImpl pBOImpl)
502: throws BOException {
503: Class lImplClass = pBOImpl.getClass();
504: String lImplClassName = lImplClass.getName();
505: BOImplMetaData lBOImplMetaData = (BOImplMetaData) cBOImplMetaDataMap
506: .get(lImplClassName);
507: if (lBOImplMetaData == null) {
508: synchronized (cBOImplMetaDataMap) {
509: lBOImplMetaData = (BOImplMetaData) cBOImplMetaDataMap
510: .get(lImplClassName);
511: if (lBOImplMetaData == null) {
512: // Now we really have to register this class
513: Class lImplArrayClass = Array.newInstance(
514: lImplClass, 0).getClass();
515: lBOImplMetaData = new BOImplMetaData();
516: try {
517: lBOImplMetaData.DeleteMethod = lImplClass
518: .getMethod("persistDelete",
519: new Class[] { lImplArrayClass });
520: if (!Modifier
521: .isStatic(lBOImplMetaData.DeleteMethod
522: .getModifiers()))
523: throw new BOIllegalObjectImplementationException(
524: lImplClassName,
525: "persistDelete() method must be static");
526: } catch (NoSuchMethodException e) {
527: sLogger
528: .debug("persistDelete() method is not found in "
529: + lImplClassName
530: + ". The deletion of instances will not be supported");
531: }
532: try {
533: lBOImplMetaData.UpdateMethod = lImplClass
534: .getMethod("persistUpdate",
535: new Class[] { lImplArrayClass });
536: if (!Modifier
537: .isStatic(lBOImplMetaData.UpdateMethod
538: .getModifiers()))
539: throw new BOIllegalObjectImplementationException(
540: lImplClassName,
541: "persistUpdate() method must be static");
542: } catch (NoSuchMethodException e) {
543: sLogger
544: .debug("persistUpdate() method is not found in "
545: + lImplClassName
546: + ". The update of instances will not be supported");
547: }
548: try {
549: lBOImplMetaData.CreateMethod = lImplClass
550: .getMethod("persistCreate",
551: new Class[] { lImplArrayClass });
552: if (!Modifier
553: .isStatic(lBOImplMetaData.CreateMethod
554: .getModifiers()))
555: throw new BOIllegalObjectImplementationException(
556: lImplClassName,
557: "persistCreate() method must be static");
558: } catch (NoSuchMethodException e) {
559: sLogger
560: .debug("persistCreate() method is not found in "
561: + lImplClassName
562: + ". The creation of instances will not be supported");
563: }
564: try {
565: lBOImplMetaData.UpdateChangesToReferences = lImplClass
566: .getMethod(
567: "persistChangesToReferences",
568: new Class[] { lImplArrayClass });
569: if (!Modifier
570: .isStatic(lBOImplMetaData.UpdateChangesToReferences
571: .getModifiers()))
572: throw new BOIllegalObjectImplementationException(
573: lImplClassName,
574: "persistChangesToReferences() method must be static");
575: } catch (NoSuchMethodException e) {
576: sLogger
577: .debug("persistChangesToReferences() method is not found in "
578: + lImplClassName
579: + ". The update of many-to-many references will not be supported");
580: }
581: cBOImplMetaDataMap.put(lImplClassName,
582: lBOImplMetaData);
583: }
584: }
585: }
586: return lBOImplMetaData;
587: }
588: }
|