001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library 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 library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.persistence;
051:
052: import java.util.HashMap;
053: import java.util.Map;
054: import org.apache.log4j.Logger;
055: import org.jaffa.persistence.exceptions.*;
056: import org.jaffa.datatypes.ValidationException;
057: import org.jaffa.exceptions.ApplicationExceptions;
058: import org.jaffa.exceptions.DuplicateKeyException;
059: import org.jaffa.exceptions.FrameworkException;
060: import org.jaffa.persistence.util.PersistentHelper;
061: import org.jaffa.rules.RulesEngine;
062: import org.jaffa.util.MessageHelper;
063:
064: /** Base class for all persistent objects.
065: */
066: public abstract class Persistent implements IPersistent {
067:
068: private static final Logger log = Logger
069: .getLogger(Persistent.class);
070:
071: // The UOW has to be transient and cannot be serialized
072: // The other flags are closely related to the UOW and hence being made transient too.
073: private transient UOW m_uow = null;
074: private transient boolean m_modified = false;
075: private transient boolean m_databaseOccurence = false;
076: private transient int m_locking = Criteria.LOCKING_OPTIMISTIC;
077: private transient boolean m_locked = false;
078: private transient boolean m_queued = false;
079: private transient Map m_modifiedFields = null; //<String, Object>
080:
081: /** This returns the state of the object for diagnostic purposes.
082: * @return a String representation of the object.
083: */
084: public String toString() {
085: StringBuffer buf = new StringBuffer();
086: buf.append("<Persistent>");
087: buf.append("<uow>");
088: if (m_uow != null)
089: buf.append(m_uow);
090: buf.append("</uow>");
091: buf.append("<databaseOccurence>");
092: buf.append(m_databaseOccurence);
093: buf.append("</databaseOccurence>");
094: buf.append("<modified>");
095: buf.append(m_modified);
096: buf.append("</modified>");
097: buf.append("<locked>");
098: buf.append(m_locked);
099: buf.append("</locked>");
100: buf.append("<locking>");
101: buf.append(m_locking);
102: buf.append("</locking>");
103: buf.append("<queued>");
104: buf.append(m_queued);
105: buf.append("</queued>");
106: buf.append("</Persistent>");
107: return buf.toString();
108: }
109:
110: /** Returns a clone of the object.
111: * @throws CloneNotSupportedException if cloning is not supported. This should never happen.
112: * @return a clone of the object.
113: */
114: public Object clone() throws CloneNotSupportedException {
115: Persistent obj = (Persistent) super .clone();
116:
117: // Initialize the fields
118: obj.m_uow = null;
119: obj.m_modified = false;
120: obj.m_databaseOccurence = false;
121: obj.m_locking = Criteria.LOCKING_OPTIMISTIC;
122: obj.m_locked = false;
123: obj.m_queued = false;
124: obj.m_modifiedFields = null;
125: return obj;
126: }
127:
128: /** Returns the UOW to which this object is associated.
129: * @return The UOW
130: */
131: public UOW getUOW() {
132: return m_uow;
133: }
134:
135: /** Associates this object to a UOW.
136: * Note: This method is for internal use by the Persistence Engine only.
137: * @param uow The UOW.
138: */
139: public void setUOW(UOW uow) {
140: m_uow = uow;
141: }
142:
143: /** Returns a true value if the object had any of its fields updated.
144: * @return a true value if the object had any of its fields updated.
145: */
146: public boolean isModified() {
147: return m_modified;
148: }
149:
150: /** Set the modified status of this object.
151: * Note: This method is for internal use by the Persistence Engine only.
152: * @param modified the modified status.
153: */
154: public void setModified(boolean modified) {
155: m_modified = modified;
156:
157: // clear the ModifiedFields map
158: if (!modified && m_modifiedFields != null)
159: m_modifiedFields.clear();
160: }
161:
162: /** Returns a true value if the object was loaded from the database.
163: * @return a true value if the object was loaded from the database.
164: */
165: public boolean isDatabaseOccurence() {
166: return m_databaseOccurence;
167: }
168:
169: /** Set the database status of this object.
170: * Note: This method is for internal use by the Persistence Engine only.
171: * @param databaseOccurence the database status.
172: */
173: public void setDatabaseOccurence(boolean databaseOccurence) {
174: m_databaseOccurence = databaseOccurence;
175: }
176:
177: /** Returns the locking strategy for this persistent object.
178: * @return the locking strategy for this persistent object.
179: */
180: public int getLocking() {
181: return m_locking;
182: }
183:
184: /** Set the locking strategy for this persistent object.
185: * Note: This method is for internal use by the Persistence Engine only.
186: * @param locking the locking strategy.
187: */
188: public void setLocking(int locking) {
189: m_locking = locking;
190: }
191:
192: /** Returns a true value if the underlying database row is locked.
193: * @return a true value if the underlying database row is locked.
194: */
195: public boolean isLocked() {
196: return m_locked;
197: }
198:
199: /** Set the locked status of this object.
200: * Note: This method is for internal use by the Persistence Engine only.
201: * @param locked the locked status.
202: */
203: public void setLocked(boolean locked) {
204: m_locked = locked;
205: }
206:
207: /** Returns a true value if this object has been added/updated/deleted and not yet been committed.
208: * @return a true value if this object has been added/updated/deleted and not yet been committed.
209: */
210: public boolean isQueued() {
211: return m_queued;
212: }
213:
214: /** Set the queued status of this object.
215: * The Persistence Engine will set the queued status to true on an Add/Update/Delete. Thereafter, any updates on this object will throw a RuntimeException.
216: * This flag will be reset after the object actaully gets added/updated/deleted to the database.
217: * Note: This method is for internal use by the Persistence Engine only.
218: * @param queued the queued status.
219: */
220: public void setQueued(boolean queued) {
221: m_queued = queued;
222: }
223:
224: /** Returns a true value if the field has been updated.
225: * @param fieldName the field to check.
226: * @return a true value if the field has been updated.
227: */
228: public boolean isModified(String fieldName) {
229: return m_modifiedFields != null
230: && m_modifiedFields.containsKey(fieldName) ? true
231: : false;
232: }
233:
234: /** Returns the initial value for a field; i.e. before it was modified.
235: * A null will be returned if the field was never modified. Use the isModified() method to determine if the field was modified.
236: * A null will also be returned, if the field had a null value to begin with.
237: * @param fieldName the field.
238: * @return the initial value for a field; i.e. before it was modified.
239: */
240: public Object returnInitialValue(String fieldName) {
241: return m_modifiedFields != null ? m_modifiedFields
242: .get(fieldName) : null;
243: }
244:
245: /** This method is triggered by the UOW, before adding this object to the Add-Store, but after a UOW has been associated to the object.
246: * This will perform the following tasks:
247: * Will invoke the PersistentHelper.exists() to check against duplicate keys.
248: * Will invoke the performForeignKeyValidations() method to ensure no invalid foreign-keys are set.
249: * Will invoke PersistentHelper.checkMandatoryFields() to perform mandatory field checks.
250: * Will invoke the Rules Engine to perform mandatory field checks.
251: * @throws PreAddFailedException if any error occurs during the process.
252: */
253: public void preAdd() throws PreAddFailedException {
254: if (log.isDebugEnabled())
255: log
256: .debug("Invoking PersistentHelper.exists() to ensure unique-ness of the primary-key");
257: boolean keyExists = false;
258: try {
259: keyExists = PersistentHelper.exists(getUOW(), this );
260: } catch (NoSuchMethodException e) {
261: // It is possible that the Peristent class does not have the 'exists()' method, or the meta class does not have the 'getKeyFields()' method.
262: // In that case, we'll not perform the check, and leave it to the database to raise an error in case the key is a duplicate
263: if (log.isDebugEnabled())
264: log
265: .debug("The exists check could not be performed since either the exists() method is missing or the meta class does not have the getKeyFields method: "
266: + this .getClass());
267: } catch (Exception e) {
268: String str = "Exception thrown while checking the unique-ness of the primary-key: "
269: + this ;
270: log.error(str, e);
271: throw new PreAddFailedException(null, e);
272: }
273: if (keyExists) {
274: String str = "The primary-key is not unique: " + this ;
275: log.error(str);
276: String labelToken = null;
277: try {
278: labelToken = PersistentHelper.getLabelToken(this
279: .getClass().getName());
280: } catch (Exception e) {
281: // don't do anything.. just return the domainClassName
282: }
283: if (labelToken == null)
284: labelToken = MessageHelper.tokenize(this .getClass()
285: .getName());
286:
287: throw new PreAddFailedException(null,
288: new DuplicateKeyException(labelToken));
289: }
290:
291: if (log.isDebugEnabled())
292: log
293: .debug("Invoking performForeignKeyValidations() to ensure valid foreign-keys are used");
294: try {
295: performForeignKeyValidations();
296: } catch (Exception e) {
297: String str = "Exception thrown while validating the foreign-keys";
298: log.error(str, e);
299: throw new PreAddFailedException(null, e);
300: }
301:
302: if (log.isDebugEnabled())
303: log
304: .debug("Invoking checks for all the mandatory fields of the persistent object");
305: try {
306: PersistentHelper.checkMandatoryFields(this );
307: } catch (Exception e) {
308: if (e.getCause() != null
309: && e.getCause() instanceof NoSuchMethodException) {
310: // It is possible that the Peristent meta class does not have the 'getMandatoryFields()' method.
311: // In that case, we'll not perform the check, and leave it to the database to raise an error in case any mandatory field is missing.
312: if (log.isDebugEnabled())
313: log
314: .debug("The mandatory field check could not be performed since the meta class does not have the getMandatoryFields method: "
315: + this .getClass());
316: } else {
317: String str = "Exception thrown while checking the mandatory fields of the persistent object";
318: log.error(str, e);
319: throw new PreAddFailedException(null, e);
320: }
321: }
322:
323: if (log.isDebugEnabled())
324: log
325: .debug("Invoking the Dynamic Rules Engine to perform the mandatory rules on the Persistent object");
326: try {
327: RulesEngine.doMandatoryValidationsForDomainObject(this ,
328: this .getUOW());
329: } catch (Exception e) {
330: String str = "Exception thrown while invoking the Dynamic Rules Engine to perform the mandatory rules on the Persistent object";
331: log.error(str, e);
332: throw new PreAddFailedException(null, e);
333: }
334: }
335:
336: /** This method is triggered by the UOW, after adding this object to the Add-Store.
337: * A concrete persistent object should provide the implementation, if its necessary.
338: * @throws PostAddFailedException if any error occurs during the process.
339: */
340: public void postAdd() throws PostAddFailedException {
341: }
342:
343: /** This method is triggered by the UOW, before adding this object to the Update-Store.
344: * This will perform the following tasks:
345: * Will invoke the performForeignKeyValidations() method to ensure no invalid foreign-keys are set.
346: * Will invoke PersistentHelper.checkMandatoryFields() to perform mandatory field checks.
347: * Will invoke the Rules Engine to perform mandatory field checks.
348: * @throws PreUpdateFailedException if any error occurs during the process.
349: */
350: public void preUpdate() throws PreUpdateFailedException {
351: if (log.isDebugEnabled())
352: log
353: .debug("Invoking performForeignKeyValidations() to ensure valid foreign-keys are used");
354: try {
355: performForeignKeyValidations();
356: } catch (Exception e) {
357: String str = "Exception thrown while validating the foreign-keys";
358: log.error(str, e);
359: throw new PreUpdateFailedException(null, e);
360: }
361:
362: if (log.isDebugEnabled())
363: log
364: .debug("Invoking checks for all the mandatory fields of the persistent object");
365: try {
366: PersistentHelper.checkMandatoryFields(this );
367: } catch (Exception e) {
368: if (e.getCause() != null
369: && e.getCause() instanceof NoSuchMethodException) {
370: // It is possible that the Peristent meta class does not have the 'getMandatoryFields()' method.
371: // In that case, we'll not perform the check, and leave it to the database to raise an error in case any mandatory field is missing.
372: if (log.isDebugEnabled())
373: log
374: .debug("The mandatory field check could not be performed since the meta class does not have the getMandatoryFields method: "
375: + this .getClass());
376: } else {
377: String str = "Exception thrown while checking the mandatory fields of the persistent object";
378: log.error(str, e);
379: throw new PreUpdateFailedException(null, e);
380: }
381: }
382:
383: if (log.isDebugEnabled())
384: log
385: .debug("Invoking the Dynamic Rules Engine to perform the mandatory rules on the Persistent object");
386: try {
387: RulesEngine.doMandatoryValidationsForDomainObject(this ,
388: this .getUOW());
389: } catch (Exception e) {
390: String str = "Exception thrown while invoking the Dynamic Rules Engine to perform the mandatory rules on the Persistent object";
391: log.error(str, e);
392: throw new PreUpdateFailedException(null, e);
393: }
394: }
395:
396: /** This method is triggered by the UOW, after adding this object to the Update-Store.
397: * A concrete persistent object should provide the implementation, if its necessary.
398: * @throws PostUpdateFailedException if any error occurs during the process.
399: */
400: public void postUpdate() throws PostUpdateFailedException {
401: }
402:
403: /** This method is triggered by the UOW, before adding this object to the Delete-Store.
404: * This will perform the following tasks:
405: * Will invoke the performPreDeleteReferentialIntegrity() method.
406: * @throws PreDeleteFailedException if any error occurs during the process.
407: */
408: public void preDelete() throws PreDeleteFailedException {
409: performPreDeleteReferentialIntegrity();
410: }
411:
412: /** This method is triggered by the UOW, after adding this object to the Delete-Store.
413: * A concrete persistent object should provide the implementation, if its necessary.
414: * @throws PostDeleteFailedException if any error occurs during the process.
415: */
416: public void postDelete() throws PostDeleteFailedException {
417: }
418:
419: /** This method is triggered by the UOW after a query loads this object.
420: * A concrete persistent object should provide the implementation, if its necessary.
421: * @throws PostLoadFailedException if any error occurs during the process.
422: */
423: public void postLoad() throws PostLoadFailedException {
424: }
425:
426: /** This method ensures that the modified foreign-keys are valid.
427: * A concrete persistent object should provide the implementation, if its necessary.
428: * @throws ApplicationExceptions if an invalid foreign key is set.
429: * @throws FrameworkException Indicates some system error
430: */
431: public void performForeignKeyValidations()
432: throws ApplicationExceptions, FrameworkException {
433: }
434:
435: /** This method will perform referential integrity checks before this object is deleted.
436: * This will cascade delete all composite objects.
437: * This will raise an exception if any associated/aggregated objects exist.
438: * A concrete persistent object should provide the implementation, if its necessary.
439: * @throws PreDeleteFailedException if any error occurs during the process.
440: */
441: public void performPreDeleteReferentialIntegrity()
442: throws PreDeleteFailedException {
443: }
444:
445: /** This method should be invoked by every updateXxx() method of the persistent class, before setting the value.
446: * It ensures that a readonly object cannot be updated. It acquires a lock for the pessimistic locking strategy.
447: * Finally, it sets the modified flag.
448: * @throws ReadOnlyObjectException if the object has been marked as ReadOnly, and hence cannot be updated.
449: * @throws AlreadyLockedObjectException if the object has been locked by another process.
450: * @throws IllegalPersistentStateRuntimeException this RuntimeException will be thrown if the domain object has been submitted to the UOW for an Add/Update/Delete and commit hasnt yet been performed.
451: */
452: protected void update() throws ReadOnlyObjectException,
453: AlreadyLockedObjectException,
454: IllegalPersistentStateRuntimeException {
455: if (isQueued()) {
456: String str = "The domain object has already been submitted to the UOW for an Add/Update/Delete. No more updates can be performed until after a commit";
457: log.error(str);
458: throw new IllegalPersistentStateRuntimeException(str);
459: }
460:
461: if (!isModified()) {
462: if (getLocking() == Criteria.LOCKING_READ_ONLY) {
463: String str = "Cannot update the instance as it has been marked as ReadOnly";
464: log.error(str);
465: throw new ReadOnlyObjectException(
466: new Object[] { actualInstance() });
467: } else if (!isLocked()
468: && getLocking() != Criteria.LOCKING_OPTIMISTIC
469: && isDatabaseOccurence()) {
470: getUOW().acquireLock(actualInstance());
471: }
472: setModified(true);
473: }
474: }
475:
476: /** Adds an initial value for a field whenever it is modified.
477: * This method is typically invoked by the updateXyz() method of the extending class.
478: * Note: The value will not be added, if the field has already been modified.
479: * @param fieldName the field.
480: * @param initialValue the initial value.
481: */
482: protected void addInitialValue(String fieldName, Object initialValue) {
483: if (m_modifiedFields == null)
484: m_modifiedFields = new HashMap();
485:
486: if (!isModified(fieldName))
487: m_modifiedFields.put(fieldName, initialValue);
488: }
489:
490: /** This method simply returns the 'this' object.
491: * A proxy implementation will override this method and return the proxy instance.
492: * @return the persistent instance.
493: */
494: protected IPersistent actualInstance() {
495: return this;
496: }
497:
498: }
|