KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > methodhead > persistable > Persistable


1 /*
2  * Copyright (C) 2006 Methodhead Software LLC. All rights reserved.
3  *
4  * This file is part of TransferCM.
5  *
6  * TransferCM is free software; you can redistribute it and/or modify it under the
7  * terms of the GNU General Public License as published by the Free Software
8  * Foundation; either version 2 of the License, or (at your option) any later
9  * version.
10  *
11  * TransferCM is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * TransferCM; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
18  * Fifth Floor, Boston, MA 02110-1301 USA
19  */

20
21 package com.methodhead.persistable;
22
23 import java.sql.ResultSet JavaDoc;
24 import java.sql.SQLException JavaDoc;
25 import java.sql.Timestamp JavaDoc;
26
27 import java.util.Date JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.text.DateFormat JavaDoc;
31 import java.text.SimpleDateFormat JavaDoc;
32 import org.apache.commons.beanutils.BasicDynaBean;
33 import org.apache.commons.beanutils.DynaBean;
34 import org.apache.commons.beanutils.DynaClass;
35 import org.apache.commons.beanutils.DynaProperty;
36 import java.text.ParseException JavaDoc;
37 import com.methodhead.persistable.ConnectionSingleton;
38 import org.apache.commons.lang.exception.ExceptionUtils;
39 import com.methodhead.persistable.ConnectionSingleton;
40 import org.apache.log4j.Logger;
41 import com.methodhead.test.TestUtils;
42
43 /**
44 <p>
45   A base class for database-backed objects. Methods are
46   provided to simplify managing data in a database: {@link
47   #saveNew saveNew()}, {@link #save save()}, {@link #load
48   load()}, {@link #loadAll loadAll()}, and {@link #deleteAll
49   deleteAll()}. Methods are also provided to box and unbox
50   primitive values.
51 </p>
52 <p>
53   <tt>Persistable</tt> subclasses <tt>BasicDynaBean</tt>
54   from the BeanUtils component of <a
55   href="http://jakarta.apache.org/commons/">Jakarta
56   Commons</a>. This makes any <tt>Persistable</tt>-based
57   object compatible with libraries that operate on
58   <tt>DynaBean</tt>s, such as <a
59   href="http://jakarta.apache.org/struts/">Apache Struts</a>.
60 </p>
61 <p>
62   As it is a <tt>DynaBean</tt>, <tt>DynaClass</tt> plays an
63   important role in a persistable's operation. Make sure the
64   persistable's dynaclass is defined appropriately:
65 </p>
66 <ul>
67   <li>
68     <p>
69       The dynaclass's <strong>name should match the
70       table name</strong> in the database.
71     </p>
72   </li>
73   <li>
74     <p>
75       The dynaclass's <strong>dynaproperties should match the
76       corresponding column names and types</strong> in the table.
77     </p>
78   </li>
79   <li>
80     <p>
81       The dynaclass's <strong><tt>newInstance()</tt> should
82       return an appropriate object</strong>.
83     </p>
84   </li>
85 </ul>
86  */

87 public class Persistable
88 extends BasicDynaBean {
89
90   // constructors /////////////////////////////////////////////////////////////
91

92   public Persistable( DynaClass dynaClass ) {
93     super( dynaClass );
94   }
95
96   // constants ////////////////////////////////////////////////////////////////
97

98   // classes //////////////////////////////////////////////////////////////////
99

100   // methods //////////////////////////////////////////////////////////////////
101

102   /**
103    * Returns <tt>property</tt>, unboxing the value if necessary.
104    */

105   public String JavaDoc getString(
106     String JavaDoc property ) {
107
108     return ( String JavaDoc )get( property );
109   }
110
111   /**
112    * Sets <tt>property</tt> to <tt>value</tt>, boxing the value if necessary.
113    */

114   public void setString(
115     String JavaDoc property,
116     String JavaDoc value ) {
117
118     set( property, value );
119   }
120
121   /**
122    * Returns <tt>property</tt>, unboxing the value if necessary; returns 0 if
123    * the property has not been set.
124    */

125   public int getInt(
126     String JavaDoc property ) {
127
128     Integer JavaDoc i = ( Integer JavaDoc )get( property );
129
130     if ( i == null )
131       return 0;
132
133     return i.intValue();
134   }
135
136   /**
137    * Sets <tt>property</tt> to <tt>value</tt>, boxing the value if necessary.
138    */

139   public void setInt(
140     String JavaDoc property,
141     int value ) {
142
143     set( property, new Integer JavaDoc( value ) );
144   }
145
146   /**
147    * Returns <tt>property</tt>, unboxing the value if necessary; <tt>false</tt>
148    * is returned if the property has not been set.
149    */

150   public boolean getBoolean(
151     String JavaDoc property ) {
152
153     Boolean JavaDoc b = ( Boolean JavaDoc )get( property );
154
155     if ( b == null )
156       return false;
157
158     return b.booleanValue();
159   }
160
161   /**
162    * Sets <tt>property</tt> to <tt>value</tt>, boxing the value if necessary.
163    */

164   public void setBoolean(
165     String JavaDoc property,
166     boolean value ) {
167
168     set( property, new Boolean JavaDoc( value ) );
169   }
170
171   /**
172    * Returns <tt>property</tt>, unboxing the value if necessary; <tt>0.0</tt>
173    * is returned if the property has not been set.
174    */

175   public double getDouble(
176     String JavaDoc property ) {
177
178     Double JavaDoc d = ( Double JavaDoc )get( property );
179
180     if ( d == null )
181       return 0.0;
182
183     return d.doubleValue();
184   }
185
186   /**
187    * Sets <tt>property</tt> to <tt>value</tt>, boxing the value if necessary.
188    */

189   public void setDouble(
190     String JavaDoc property,
191     double value ) {
192
193     set( property, new Double JavaDoc( value ) );
194   }
195
196   /**
197    * Returns <tt>property</tt>, unboxing the value if necessary.
198    */

199   public Date JavaDoc getDate(
200     String JavaDoc property ) {
201
202     return ( Date JavaDoc )get( property );
203   }
204
205   /**
206    * Sets <tt>property</tt> to <tt>value</tt>, boxing the value if necessary.
207    */

208   public void setDate(
209     String JavaDoc property,
210     Date JavaDoc value ) {
211
212     set( property, value );
213   }
214
215   /**
216    * <p>
217    * Sets <tt>property</tt> to <tt>value</tt>, converting
218    * <tt>value</tt> from a string to the appropriate type in a
219    * sensible way.
220    * </p>
221    * <ul>
222    * <li>
223    * Strings: simply <tt>value</tt> or <tt>""</tt> if
224    * <tt>value</tt> is <tt>null</tt>.
225    * </li>
226    * <li>
227    * Integers: <tt>value</tt> if it can be successfully
228    * parsed; <tt>0</tt> if <tt>value</tt> is <tt>null</tt> or an
229    * empty string.
230    * </li>
231    * <li>
232    * Booleans: <tt>true</tt> if <tt>value</tt> is
233    * <tt>"true"</tt>, <tt>"yes"</tt>, or "on" (case insensitive),
234    * <tt>false</tt> otherwise.
235    * </li>
236    * <li>
237    * Doubles: <tt>value</tt> if it can be successfully
238    * parsed; <tt>0.0</tt> if <tt>value</tt> is <tt>null</tt> or
239    * an empty string.
240    * </li>
241    * <li>
242    * Dates: <tt>value</tt> if it can be successfully parsed
243    * by <tt>DateFormat.getDateInstance( DateFormat.SHORT )</tt>
244    * (e.g., "2/20/03") or <tt>DateFormat.getDateTimeInstance(
245    * DateFormat.SHORT, DateFormat.SHORT )</tt> (e.g., "2/20/03
246    * 8:20 PM"); the current date if <tt>value</tt> is
247    * <tt>null</tt> or an empty string.
248    * </li>
249    * </ul>
250    */

251   public void setAsString(
252     String JavaDoc property,
253     String JavaDoc value ) {
254
255     Class JavaDoc c = getDynaClass().getDynaProperty( property ).getType();
256
257     if ( c == String JavaDoc.class ) {
258       if ( value == null )
259         set( property, "" );
260       else
261         set( property, value );
262     }
263
264     else if ( c == Integer JavaDoc.class ) {
265       if ( ( value == null ) || value.equals( "" ) )
266         set( property, new Integer JavaDoc( 0 ) );
267       else
268         set( property, new Integer JavaDoc( value ) );
269     }
270
271     else if ( c == Boolean JavaDoc.class ) {
272
273       if ( value == null )
274         set( property, new Boolean JavaDoc( false ) );
275       else {
276         value = value.toLowerCase();
277
278         if ( value.equals( "true" ) ||
279              value.equals( "yes" ) ||
280              value.equals( "on" ) )
281           set( property, new Boolean JavaDoc( true ) );
282         else
283           set( property, new Boolean JavaDoc( false ) );
284       }
285     }
286
287     else if ( c == Double JavaDoc.class ) {
288       if ( ( value == null ) || value.equals( "" ) )
289         set( property, new Double JavaDoc( 0.0 ) );
290       else
291         set( property, new Double JavaDoc( value ) );
292     }
293
294     else if ( c == java.util.Date JavaDoc.class ) {
295       if ( ( value == null ) || value.equals( "" ) )
296         set( property, TestUtils.getCurrentDate() );
297       else {
298         Date JavaDoc d = null;
299         DateFormat JavaDoc fmt =
300           DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT );
301
302         try {
303           d = fmt.parse( value );
304         }
305         catch ( ParseException JavaDoc e ) {
306
307           fmt =DateFormat.getDateInstance( DateFormat.SHORT );
308           try {
309             d = fmt.parse( value );
310           }
311           catch ( ParseException JavaDoc e2 ) {
312             throw new PersistableException(
313               "Couldn't parse date from \"" + value + "\"" );
314           }
315         }
316
317         set( property, d );
318       }
319     }
320   }
321
322   /**
323    * Sets <tt>property</tt> by calling {@link #setAsString setAsString()} with
324    * <tt>value.toString()</tt>.
325    */

326   public void setAsObject(
327     String JavaDoc property,
328     Object JavaDoc value ) {
329
330     if ( value == null )
331       setAsString( property, null );
332     else
333       setAsString( property, value.toString() );
334   }
335
336   /**
337    * Returns a string literal suitable for use in a SQL statement by escaping
338    * single quotes and then wrapping the string in single quotes. For example,
339    * <tt>"it's good to escape"</tt> becomes <tt>"'it''s good to escape'"</tt>
340    */

341   public static String JavaDoc getSqlLiteral( String JavaDoc value ) {
342     return "'" + value.replaceAll( "'", "''" ) + "'";
343   }
344
345   /**
346    * Returns a string literal suitable for use in a SQL statement, using
347    * <tt>'1'</tt> for true and <tt>'0'</tt> for false.
348    */

349   public static String JavaDoc getSqlLiteral( Boolean JavaDoc value ) {
350     if ( value.booleanValue() )
351       return "'1'";
352     else
353       return "'0'";
354   }
355
356   /**
357    * Returns a date literal suitable for use in a SQL statement by wrapping a
358    * standard SQL date in single quotes.
359    */

360   public static String JavaDoc getSqlLiteral( Date JavaDoc value ) {
361     DateFormat JavaDoc format = new SimpleDateFormat JavaDoc( "yyyy-MM-dd HH:mm:ss" );
362     return "'" + format.format( value ) + "'";
363   }
364
365   /**
366    * Returns the value of <tt>dynaProperty</tt> in a form suitable for use in a
367    * SQL statement.
368    */

369   protected String JavaDoc getSqlLiteral(
370     DynaProperty dynaProperty ) {
371
372     if ( get( dynaProperty.getName() ) == null )
373       return "NULL";
374
375     String JavaDoc property = dynaProperty.getName();
376
377     if ( dynaProperty.getType() == String JavaDoc.class )
378       return getSqlLiteral( get( property ).toString() );
379     else if ( dynaProperty.getType() == Integer JavaDoc.class )
380       return get( property ).toString();
381     else if ( dynaProperty.getType() == Boolean JavaDoc.class )
382       return getSqlLiteral( ( Boolean JavaDoc )get( property ) );
383     else if ( dynaProperty.getType() == Double JavaDoc.class )
384       return get( property ).toString();
385     else if ( dynaProperty.getType() == java.util.Date JavaDoc.class )
386       return getSqlLiteral( ( Date JavaDoc )get( property ) );
387
388     return null;
389   }
390
391   /**
392    * Convenience method to set the <tt>dynaProperty</tt> of
393    * <tt>persistable</tt> with the corresponding value from <tt>rs</tt>.
394    */

395   protected static void setProperty(
396     DynaBean persistable,
397     DynaProperty dynaProperty,
398     ResultSet JavaDoc rs) {
399
400     try {
401     if ( dynaProperty.getType() == String JavaDoc.class )
402       persistable.set(
403         dynaProperty.getName(),
404         rs.getString( dynaProperty.getName() ) );
405     else if ( dynaProperty.getType() == Integer JavaDoc.class )
406       persistable.set(
407         dynaProperty.getName(),
408         new Integer JavaDoc( rs.getInt( dynaProperty.getName() ) ) );
409     else if ( dynaProperty.getType() == Boolean JavaDoc.class )
410       persistable.set(
411         dynaProperty.getName(),
412         new Boolean JavaDoc( rs.getBoolean( dynaProperty.getName() ) ) );
413     else if ( dynaProperty.getType() == Double JavaDoc.class )
414       persistable.set(
415         dynaProperty.getName(),
416         new Double JavaDoc( rs.getDouble( dynaProperty.getName() ) ) );
417     else if ( dynaProperty.getType() == java.util.Date JavaDoc.class )
418       persistable.set(
419         dynaProperty.getName(),
420         rs.getTimestamp( dynaProperty.getName() ) );
421     }
422     catch ( SQLException JavaDoc e ) {
423       throw new PersistableException(
424         "Unexpected SQLException while setting property \"" +
425         dynaProperty.getName() + "\":\n" + e.getMessage() );
426     }
427   }
428
429   /**
430    * Saves the persistable by inserting a new row.
431    */

432   public void saveNew() {
433
434     DynaClass dynaClass = getDynaClass();
435
436     //
437
// start statement
438
//
439
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
440
441     buf.append( "INSERT INTO " );
442     buf.append( dynaClass.getName() );
443     buf.append( " (" );
444
445     //
446
// append field names
447
//
448
DynaProperty[] dyanProperties = dynaClass.getDynaProperties();
449     for ( int i = 0; i < dyanProperties.length; i++ ) {
450       buf.append( dyanProperties[ i ].getName() );
451
452       if ( i < dyanProperties.length - 1 )
453         buf.append( "," );
454     }
455
456     //
457
// append values
458
//
459
buf.append( ") VALUES (" );
460
461     for ( int i = 0; i < dyanProperties.length; i++ ) {
462       buf.append( getSqlLiteral( dyanProperties[ i ] ) );
463
464       if ( i < dyanProperties.length - 1 )
465         buf.append( "," );
466     }
467
468     buf.append( ")" );
469
470     //
471
// execute the statement
472
//
473
try {
474       ConnectionSingleton.runUpdate( buf.toString() );
475     }
476     catch ( SQLException JavaDoc e ) {
477       throw new PersistableException(
478         "Unexpected SQLException: " + e.getMessage() );
479     }
480   }
481
482   /**
483    * Saves the persistable, by updating any rows to which the specified
484    * <tt>whereClause</tt> clause applies. <tt>whereClause</tt> should not
485    * include the <tt>WHERE</tt> keyword; if <tt>whereClause</tt> is
486    * <tt>null</tt>, all rows in the table are updated.
487    */

488   public void save(
489     String JavaDoc whereClause )
490   throws
491     PersistableException {
492
493     //
494
// start statement
495
//
496
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
497
498     buf.append( "UPDATE " );
499     buf.append( getDynaClass().getName() );
500     buf.append( " SET " );
501
502     DynaProperty[] dynaProperties = getDynaClass().getDynaProperties();
503
504     //
505
// append field names
506
//
507
for ( int i = 0; i < dynaProperties.length; i++ ) {
508       buf.append( dynaProperties[ i ].getName() );
509       buf.append( "=" );
510       buf.append( getSqlLiteral( dynaProperties[ i ] ) );
511
512       if ( i < dynaProperties.length - 1 )
513         buf.append( "," );
514     }
515
516     //
517
// append where clause if it was provided
518
//
519
if ( whereClause != null )
520       buf.append( " WHERE " + whereClause );
521
522     //
523
// execute the statement
524
//
525
try {
526       ConnectionSingleton.runUpdate( buf.toString() );
527     }
528     catch ( SQLException JavaDoc e ) {
529       throw new PersistableException(
530         "Unexpected SQLException: " + e.getMessage() + " while executing \"" +
531         buf.toString() + "\"" );
532     }
533   }
534
535   /**
536    * Loads the persistable according to the specified <tt>whereClause</tt>. If
537    * no records match <tt>whereClause</tt>, an exception is thrown. If more
538    * than one record matches, only the first is used.
539    */

540   public void load(
541     String JavaDoc whereClause ) {
542
543     DynaProperty[] dynaProperties = getDynaClass().getDynaProperties();
544
545     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
546     buf.append( "SELECT " );
547
548     for ( int i = 0; i < dynaProperties.length; i++ ) {
549       buf.append( dynaProperties[ i ].getName() );
550       if ( i < dynaProperties.length - 1 )
551         buf.append( "," );
552     }
553
554     buf.append( " FROM " );
555     buf.append( getDynaClass().getName() );
556     buf.append( " WHERE " );
557     buf.append( whereClause );
558
559     ResultSet JavaDoc rs = null;
560     try {
561       rs = ConnectionSingleton.runQuery( buf.toString() );
562
563       if ( rs == null )
564         throw new PersistableException(
565           "Couldn't execute statement: " + buf.toString() );
566
567       if ( !rs.next() ) {
568         throw new PersistableException(
569           "No records for where clause \"" + whereClause + "\"." );
570       }
571
572       for ( int i = 0; i < dynaProperties.length; i++ )
573         setProperty( this, dynaProperties[ i ], rs );
574     }
575     catch ( SQLException JavaDoc e ) {
576       String JavaDoc msg = "Loading for whereClause \"" + whereClause + "\". " + ExceptionUtils.getStackTrace( e );
577       logger_.error( msg );
578       throw new RuntimeException JavaDoc( msg );
579     }
580     finally {
581       ConnectionSingleton.close( rs );
582     }
583   }
584
585   /**
586    * Returns a list containing all persistables for the specified
587    * <tt>WHERE</tt> and <tt>ORDER BY</tt> clauses. <tt>whereClause</tt> should
588    * not include the <tt>WHERE</tt> keyword; if <tt>whereClause</tt> is
589    * <tt>null</tt> all persistables in the table are loaded. <tt>orderBy</tt>
590    * should not contain the <tt>ORDER BY</tt> keywords; if
591    * <tt>orderByClause</tt> is <tt>null</tt> persistables are sorted as
592    * returned from the database.
593    */

594   public static List JavaDoc loadAll(
595     DynaClass dynaClass,
596     String JavaDoc whereClause,
597     String JavaDoc orderByClause ) {
598
599     DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
600
601     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
602     buf.append( "SELECT " );
603
604     for ( int i = 0; i < dynaProperties.length; i++ ) {
605       buf.append( dynaProperties[ i ].getName() );
606       if ( i < dynaProperties.length - 1 )
607         buf.append( "," );
608     }
609
610     buf.append( " FROM " );
611     buf.append( dynaClass.getName() );
612
613     if ( whereClause != null ) {
614       buf.append( " WHERE " );
615       buf.append( whereClause );
616     }
617
618     if ( orderByClause != null ) {
619       buf.append( " ORDER BY " );
620       buf.append( orderByClause );
621     }
622
623     ResultSet JavaDoc rs = null;
624     try {
625       rs = ConnectionSingleton.runQuery( buf.toString() );
626
627       if ( rs == null )
628         throw new PersistableException(
629           "Couldn't execute statement: " + buf.toString() );
630
631       List JavaDoc persistables = new ArrayList JavaDoc();
632
633       while ( rs.next() ) {
634         DynaBean persistable = ( DynaBean )dynaClass.newInstance();
635
636         for ( int i = 0; i < dynaProperties.length; i++ )
637           setProperty( persistable, dynaProperties[ i ], rs );
638
639         persistables.add( persistable );
640       }
641
642       ConnectionSingleton.close( rs );
643
644       return persistables;
645     }
646     catch ( SQLException JavaDoc e ) {
647       if ( rs != null )
648         ConnectionSingleton.close( rs );
649       throw new PersistableException(
650         "Unexpected SQLException: " + e.getMessage() );
651     }
652     catch ( IllegalAccessException JavaDoc e ) {
653       throw new PersistableException(
654         "Unexpected IllegalAccessException: " + e.getMessage() );
655     }
656     catch ( InstantiationException JavaDoc e ) {
657       throw new PersistableException(
658         "Unexpected InstantiationException: " + e.getMessage() );
659     }
660   }
661
662   /**
663    * Returns a list containing all persistables for the specified
664    * <tt>WHERE</tt> and <tt>ORDER BY</tt> clauses. <tt>whereClause</tt> should
665    * not include the <tt>WHERE</tt> keyword; if <tt>whereClause</tt> is
666    * <tt>null</tt> all persistables in the table are loaded. <tt>orderBy</tt>
667    * should not contain the <tt>ORDER BY</tt> keywords; if
668    * <tt>orderByClause</tt> is <tt>null</tt> persistables are sorted as
669    * returned from the database.
670    */

671   public List JavaDoc loadAll(
672     String JavaDoc whereClause,
673     String JavaDoc orderByClause ) {
674
675     return loadAll( getDynaClass(), whereClause, orderByClause );
676   }
677
678   /**
679    * Deletes all persistables according to the specified <tt>whereClause</tt>.
680    * <tt>whereClause</tt> should not include the <tt>WHERE</tt> keyword; if
681    * <tt>whereClause</tt> is <tt>null</tt>, all persistables in the table are
682    * deleted.
683    */

684   public static void deleteAll(
685     DynaClass dynaClass,
686     String JavaDoc whereClause )
687   throws
688     PersistableException {
689
690     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
691     buf.append( "DELETE FROM " );
692     buf.append( dynaClass.getName() );
693
694     if ( whereClause != null ) {
695       buf.append( " WHERE " );
696       buf.append( whereClause );
697     }
698
699     try {
700       ConnectionSingleton.runUpdate( buf.toString() );
701     }
702     catch ( SQLException JavaDoc e ) {
703       throw new PersistableException(
704         "Unexpected SQLException: " + e.getMessage() );
705     }
706   }
707
708   /**
709    * Deletes all persistables according to the specified <tt>whereClause</tt>.
710    * <tt>whereClause</tt> should not include the <tt>WHERE</tt> keyword; if
711    * <tt>whereClause</tt> is <tt>null</tt>, all persistables in the table are
712    * deleted.
713    */

714   public void deleteAll(
715     String JavaDoc whereClause )
716   throws
717     PersistableException {
718
719     deleteAll( getDynaClass(), whereClause );
720   }
721
722   // properties ///////////////////////////////////////////////////////////////
723

724   // attributes ///////////////////////////////////////////////////////////////
725

726   private static Logger logger_ = Logger.getLogger( Persistable.class );
727 }
728
Popular Tags