View Javadoc

1   // 
2   //Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J. 
3   //All rights reserved. 
4   //   
5   //Redistribution and use in source and binary forms, with or without modification,  
6   //are permitted provided that the following conditions are met: 
7   //   
8   //* Redistributions of source code must retain the above copyright notice,  
9   //this list of conditions and the following disclaimer. 
10  //* Redistributions in binary form must reproduce the above copyright notice,  
11  //this list of conditions and the following disclaimer in the documentation  
12  //and/or other materials provided with the distribution. 
13  //* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.  
14  //nor the names of its contributors may be used to endorse or promote products  
15  //derived from this software without specific prior written permission. 
16  // 
17  //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  
18  //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
19  //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
20  //IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,  
21  //INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  
22  //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
23  //OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
24  //WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  
25  //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  
26  //POSSIBILITY OF SUCH DAMAGE. 
27  //
28  
29  package org.objectledge.database.persistence;
30  
31  import java.math.BigDecimal;
32  import java.net.URL;
33  import java.sql.Array;
34  import java.sql.Blob;
35  import java.sql.Clob;
36  import java.sql.Connection;
37  import java.sql.PreparedStatement;
38  import java.sql.Ref;
39  import java.sql.SQLException;
40  import java.sql.Time;
41  import java.sql.Timestamp;
42  import java.util.Arrays;
43  import java.util.Date;
44  import java.util.HashMap;
45  import java.util.HashSet;
46  import java.util.Iterator;
47  import java.util.Set;
48  
49  /**
50   * An implementation of {@link DefaultOutputRecord}.
51   *
52   * TODO get rid of sun.misc.Base64Encoder
53   */
54  public class DefaultOutputRecord implements OutputRecord
55  {
56      /*** The persistent object. */
57      private Persistent object;
58      
59      /*** The fields. */
60      private HashMap<String,Object> fields;
61          
62      /***
63       * Constructs an <code>OutputRecordImpl</code>.
64       * 
65       * @param object a Presistent object.
66       */
67      public DefaultOutputRecord(Persistent object)
68      {
69          fields = new HashMap<String,Object>();
70          this.object = object;
71      }
72          
73      /***
74       * Sets a <code>boolean</code> field value.
75       *
76       * @param field the name of the field.
77       * @param value the value of the field.
78       * @throws PersistenceException if the field could not be set to the 
79       *         specified value.
80       */
81      public void setBoolean(String field, boolean value)
82          throws PersistenceException
83      {
84          fields.put(field, value ? Boolean.TRUE : Boolean.FALSE);
85      }
86  
87      /***
88       * Sets a <code>byte</code> field value.
89       *
90       * @param field the name of the field.
91       * @param value the value of the field.
92       * @throws PersistenceException if the field could not be set to the 
93       * specified value.
94       */
95      public void setByte(String field, byte value)
96          throws PersistenceException
97      {
98          fields.put(field, new Byte(value));
99      }
100 
101     /***
102      * Sets a <code>short</code> field value.
103      *
104      * @param field the name of the field.
105      * @param value the value of the field.
106      * @throws PersistenceException if the field could not be set to the 
107      *         specified value.
108      */
109     public void setShort(String field, short value)
110         throws PersistenceException
111     {
112         fields.put(field, new Short(value));
113     }
114 
115     /***
116      * Sets an <code>int</code> field value.
117      *
118      * @param field the name of the field.
119      * @param value the value of the field.
120      * @throws PersistenceException if the field could not be set to the 
121      *         specified value.
122      */
123     public void setInteger(String field, int value)
124         throws PersistenceException
125     {
126         fields.put(field, new Integer(value));
127     }
128 
129     /***
130      * Sets a <code>long</code> field value.
131      *
132      * @param field the name of the field.
133      * @param value the value of the field.
134      * @throws PersistenceException if the field could not be set to the 
135      *         specified value.
136      */
137     public void setLong(String field, long value)
138         throws PersistenceException
139     {
140         fields.put(field, new Long(value));
141     }        
142 
143     /***
144      * Sets a <code>BigDecimal</code> field value.
145      *
146      * @param field the name of the field.
147      * @param value the value of the field.
148      * @throws PersistenceException if the field could not be set to the 
149      *         specified value.
150      */
151     public void setBigDecimal(String field, BigDecimal value)
152         throws PersistenceException
153     {
154         if(value == null)
155         {
156             setNull(field);
157             return;
158         }
159         fields.put(field, value);
160     }
161 
162     /***
163      * Sets a <code>float</code> field value.
164      *
165      * @param field the name of the field.
166      * @param value the value of the field.
167      * @throws PersistenceException if the field could not be set to the 
168      *         specified value.
169      */
170     public void setFloat(String field, float value)
171         throws PersistenceException
172     {
173         fields.put(field, new Float(value));
174     }
175 
176     /***
177      * Sets a <code>double</code> field value.
178      *
179      * @param field the name of the field.
180      * @param value the value of the field.
181      * @throws PersistenceException if the field could not be set to the 
182      *         specified value.
183      */
184     public void setDouble(String field, double value)
185         throws PersistenceException
186     {
187         fields.put(field, new Double(value));
188     }
189 
190     /***
191      * Sets a <code>String</code> field value.
192      *
193      * @param field the name of the field.
194      * @param value the value of the field.
195      * @throws PersistenceException if the field could not be set to the
196      *         specified value. 
197      */
198     public void setString(String field, String value)
199         throws PersistenceException
200     {
201         if(value == null)
202         {
203             setNull(field);
204             return;
205         }        
206         fields.put(field, value);
207     }
208 
209     /***
210      * Sets a <code>byte</code> array field value.
211      *
212      * <p>String value read from the database will be BASE64 decoded to obtain
213      * byte array.</p>
214      *
215      * @param field the name of the field.
216      * @param value the value of the field.
217      * @throws PersistenceException if the field could not be set to the
218      *         specified value. 
219      */
220     public void setBytes(String field, byte[] value)
221         throws PersistenceException
222     {
223         try
224         {
225             sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
226             String encoded = encoder.encodeBuffer(value);
227             fields.put(field, encoded);
228         }
229         catch(Exception e)
230         {
231             throw new PersistenceException("Failed to encode field "+field, e);
232         }
233     }
234 
235     /***
236      * Sets a <code>Date</code> field value.
237      *
238      * @param field the name of the field.
239      * @param value the value of the field.
240      * @throws PersistenceException if the field could not be set to the
241      *         specified value. 
242      */
243     public void setDate(String field, Date value)
244         throws PersistenceException
245     {
246         if(value == null)
247         {
248             setNull(field);
249             return;
250         }        
251         fields.put(field, new java.sql.Date(value.getTime()));
252     }
253     
254     /***
255      * Sets a <code>Time</code> field value.
256      * 
257      * @param field the name of the field.
258      * @param value the value of the filed.
259      * @throws PersistenceException if the field could not be set to the
260      *         specified value. 
261      */
262     public void setTime(String field, Date value)
263         throws PersistenceException
264     {
265         if(value == null)
266         {
267             setNull(field);
268             return;
269         }        
270         fields.put(field, new Time(value.getTime()));
271     }
272 
273     /***
274      * Sets a <code>Timestamp</code> field value.
275      * 
276      * @param field the name of the field.
277      * @param value the value of the filed.
278      * @throws PersistenceException if the field could not be set to the
279      *         specified value. 
280      */
281     public void setTimestamp(String field, Date value)
282         throws PersistenceException
283     {
284         if(value == null)
285         {
286             setNull(field);
287             return;
288         }        
289         fields.put(field, new Timestamp(value.getTime()));
290     }
291 
292     /***
293      * Sets a <code>Array</code> field value.
294      * 
295      * @param field the name of the field.
296      * @param value the value of the filed.
297      * @throws PersistenceException if the field could not be set to the
298      *         specified value. 
299      */
300     public void setArray(String field, Array value)
301         throws PersistenceException
302     {
303         fields.put(field, value);
304     }
305 
306     /***
307      * Sets a <code>Blob</code> field value.
308      * 
309      * @param field the name of the field.
310      * @param value the value of the filed.
311      * @throws PersistenceException if the field could not be set to the
312      *         specified value. 
313      */
314     public void setBlob(String field, Blob value)
315         throws PersistenceException
316     {
317         fields.put(field, value);
318     }
319 
320     /***
321      * Sets a <code>Clob</code> field value.
322      * 
323      * @param field the name of the field.
324      * @param value the value of the filed.
325      * @throws PersistenceException if the field could not be set to the
326      *         specified value. 
327      */
328     public void setClob(String field, Clob value)
329         throws PersistenceException
330     {
331         fields.put(field, value);
332     }
333 
334     /***
335      * Sets a <code>Ref</code> field value.
336      * 
337      * @param field the name of the field.
338      * @param value the value of the filed.
339      * @throws PersistenceException if the field could not be set to the
340      *         specified value. 
341      */
342     public void setRef(String field, Ref value)
343         throws PersistenceException
344     {
345         fields.put(field, value);
346     }
347 
348     /***
349      * Sets a <code>URL</code> field value.
350      * 
351      * @param field the name of the field.
352      * @param value the value of the filed.
353      * @throws PersistenceException if the field could not be set to the
354      *         specified value. 
355      */
356     public void setURL(String field, URL value)
357         throws PersistenceException
358     {
359         fields.put(field, value);
360     }
361 
362     /***
363      * Sets a <code>Object</code> field value.
364      * 
365      * @param field the name of the field.
366      * @param value the value of the filed.
367      * @throws PersistenceException if the field could not be set to the
368      *         specified value. 
369      */
370     public void setObject(String field, Object value)
371         throws PersistenceException
372     {
373         fields.put(field, value);
374     }
375 
376     /***
377      * Sets a field to <code>SQL NULL</code> value.
378      *
379      * @param field the name of the field.
380      * @throws PersistenceException if the field could not be set to the
381      *         specified value. 
382      */
383     public void setNull(String field)
384         throws PersistenceException
385     {
386         fields.put(field, null);
387     }
388 
389     // Implementation specific ///////////////////////////////////////////
390 
391     /***
392      * Builds <code>WHERE</code> clause with contained data.
393      *
394      * @return the where clause.
395      * @throws PersistenceException if the clause could not be built.
396      */
397     public String getWhereClause()
398         throws PersistenceException
399     {
400         Set keyFields = getKeyFields();
401         StringBuilder buff = new StringBuilder();
402         for(Iterator i = fields.keySet().iterator(); i.hasNext();)
403         {
404             String field = (String)i.next();
405             if(keyFields.contains(field))
406             {
407                 buff.append(field);
408                 Object value = fields.get(field);
409                 buff.append(value == null ? " IS " : " = ");
410                 appendValueString(buff, value);
411                 buff.append(" AND ");
412             }
413         }
414         // remove trailing " AND "
415         buff.setLength(buff.length() - 5);
416         return buff.toString();
417     }
418 
419     /***
420      * Builds an insert statement with contained data.
421      *
422      * @param conn database connection.
423      * @return the statement.
424      * @throws PersistenceException if the statement could not be built.
425      * @throws SQLException if the statement could not be created.
426      */
427     public PreparedStatement getInsertStatement(Connection conn)
428         throws PersistenceException, SQLException
429     {
430         StringBuilder buff = new StringBuilder();
431         StringBuilder buff2 = new StringBuilder();
432         buff.append("INSERT INTO ");
433         buff.append(object.getTable());
434         buff.append(" (");
435         for(Iterator i = fields.keySet().iterator(); i.hasNext();)
436         {
437             String field = (String)i.next();
438             buff.append(field);
439             Object value = fields.get(field);
440             appendValueString(buff2, value);
441             if(i.hasNext())
442             {
443                 buff.append(", ");
444                 buff2.append(", ");
445             }
446         }
447         buff.append(") VALUES (");
448         buff.append(buff2.toString());
449         buff.append(")");
450         PreparedStatement stmt = conn.prepareStatement(buff.toString());
451         setValues(stmt, true, true);
452         return stmt;
453     }
454     
455     /***
456      * Builds an update statement with contained data.
457      *
458      * @param conn database connection.
459      * @return the statement.
460      * @throws PersistenceException if the statement could not be built.
461      * @throws SQLException if the statement could not be created.
462      */
463     public PreparedStatement getUpdateStatement(Connection conn)
464         throws PersistenceException, SQLException
465     {
466         Set keyFields = getKeyFields();
467         StringBuilder buff = new StringBuilder();
468         buff.append("UPDATE ");
469         buff.append(object.getTable());
470         buff.append(" SET ");
471         Iterator i = fields.keySet().iterator();
472         while(i.hasNext())
473         {
474             String field = (String)i.next();
475             if(!keyFields.contains(field))
476             {
477                 buff.append(field);
478                 buff.append(" = ");
479                 Object value = fields.get(field);
480                 appendValueString(buff, value);
481                 buff.append(", ");
482             }
483         }
484         // remove superfluous ", "
485         buff.setLength(buff.length() - 2);
486         buff.append(" WHERE ");
487         buff.append(getWhereClause());
488         PreparedStatement stmt = conn.prepareStatement(buff.toString());
489         // set non-key values first
490         setValues(stmt, false, true);
491         // set key values
492         setValues(stmt, true, false);
493         return stmt;
494     }
495 
496     /***
497      * @return set of key field names.
498      */
499     private Set getKeyFields()
500     {
501         return new HashSet<String>(Arrays.asList(object.getKeyColumns()));
502     }
503     
504     /***
505      * Appends string token apropriate for the value in the statement body to a given buffer.
506      *
507      * @param buff the buffer to append to.
508      * @param object the object.
509      */
510     private void appendValueString(StringBuilder buff, Object object)
511     {
512         if(object == null)
513         {
514             buff.append("NULL");
515         }
516         else
517         {
518             if(object instanceof Number)
519             {
520                 buff.append(object.toString());
521             }
522             else
523             {
524                 buff.append('?');
525             }
526         }        
527     }
528 
529     /***
530      * Builds a <code>DELETE</code> statement with contained data.
531      *
532      *
533      * @param conn database connection.
534      * @return the statement.
535      * @throws PersistenceException if the statement could not be built.
536      * @throws SQLException if the statement could not be created.
537      */
538     public PreparedStatement getDeleteStatement(Connection conn)
539         throws PersistenceException, SQLException
540     {
541         StringBuilder buff = new StringBuilder();
542         buff.append("DELETE FROM ");
543         buff.append(object.getTable());
544         buff.append(" WHERE ");
545         buff.append(getWhereClause());
546         PreparedStatement stmt = conn.prepareStatement(buff.toString());
547         setValues(stmt, true, false);
548         return stmt;
549     }
550 
551     /***
552      * Returns a value of a field.
553      *
554      * <p>Note! String and Date values will be returned enclosed in single
555      * quotes, byte array values will be returned BASE64 encoded and enclosed
556      * in single quotes.</p>
557      *
558      * @param name the name of the field
559      * @return stringied and possibly quoted value of the field, or
560      *         <code>null</code> if unset.
561      */
562     public String getField(String name)
563     {
564         return (String)fields.get(name);
565     }
566     
567     /***
568      * Sets prepared statement's positional parameters to non-string field values.
569      * 
570      * @param stmt the statement.
571      * @param includeKeys <code>true</code> to set key values.
572      * @param includeNonKeys <code>true</code> to set non-key values.
573      * @throws SQLException if a value couldn't be set.
574      */
575     public void setValues(PreparedStatement stmt, boolean includeKeys, boolean includeNonKeys)
576         throws SQLException
577     {
578         Set keyFields = getKeyFields();
579         int pos = 1;
580         for(Iterator i = fields.keySet().iterator(); i.hasNext();)
581         {
582             Object field = i.next();
583             boolean isKey = keyFields.contains(field);
584             if((isKey && includeKeys) || (!isKey && includeNonKeys))
585             {
586                 Object value = fields.get(field);
587                 if(value != null)
588                 {
589                     if(!(value instanceof Number))
590                     {
591                         setValue(pos++, value, stmt);
592                     }
593                 }
594             }
595         }
596     }
597     
598     /***
599      * Sets prepared statement's positional parameters to non-string field values.
600      * 
601      * @param pos parameter position (1 based).
602      * @param value parameter value.
603      * @param stmt the statement.
604      */
605     private void setValue(int pos, Object value, PreparedStatement stmt)
606         throws SQLException
607     {
608         if(value instanceof Array)
609         {
610             stmt.setArray(pos, (Array)value);
611         }
612         else if(value instanceof Blob)
613         {
614             stmt.setBlob(pos, (Blob)value);
615         }
616         else if(value instanceof Clob)
617         {
618             stmt.setClob(pos, (Clob)value);
619         }
620         else if(value instanceof java.sql.Date)
621         {
622             stmt.setDate(pos, (java.sql.Date)value);
623         }
624         else if(value instanceof Time)
625         {
626             stmt.setTime(pos, (Time)value);        
627         }
628         else if(value instanceof Timestamp)
629         {
630             stmt.setTimestamp(pos, (Timestamp)value);        
631         }
632         else if(value instanceof Ref)
633         {
634             stmt.setRef(pos, (Ref)value);
635         }
636         else if(value instanceof URL)
637         {
638             stmt.setURL(pos, (URL)value);
639         }
640         else if(value instanceof Boolean)
641         {
642             stmt.setInt(pos, ((Boolean)value).booleanValue() ? 1 : 0);
643         }
644         else if(value instanceof String)
645         {
646             stmt.setString(pos, (String)value);
647         }
648         else
649         {
650             stmt.setObject(pos, object);
651         }
652     }
653 }