fixes:
[mir.git] / source / mir / storage / Database.java
1 /*\r
2  * Copyright (C) 2001, 2002  The Mir-coders group\r
3  *\r
4  * This file is part of Mir.\r
5  *\r
6  * Mir is free software; you can redistribute it and/or modify\r
7  * it under the terms of the GNU General Public License as published by\r
8  * the Free Software Foundation; either version 2 of the License, or\r
9  * (at your option) any later version.\r
10  *\r
11  * Mir is distributed in the hope that it will be useful,\r
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14  * GNU General Public License for more details.\r
15  *\r
16  * You should have received a copy of the GNU General Public License\r
17  * along with Mir; if not, write to the Free Software\r
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19  *\r
20  * In addition, as a special exception, The Mir-coders gives permission to link\r
21  * the code of this program with the com.oreilly.servlet library, any library\r
22  * licensed under the Apache Software License, The Sun (tm) Java Advanced\r
23  * Imaging library (JAI), The Sun JIMI library (or with modified versions of\r
24  * the above that use the same license as the above), and distribute linked\r
25  * combinations including the two.  You must obey the GNU General Public\r
26  * License in all respects for all of the code used other than the above\r
27  * mentioned libraries.  If you modify this file, you may extend this exception\r
28  * to your version of the file, but you are not obligated to do so.  If you do\r
29  * not wish to do so, delete this exception statement from your version.\r
30  */\r
31 package mir.storage;\r
32 \r
33 import java.io.IOException;\r
34 import java.io.InputStreamReader;\r
35 \r
36 import java.sql.Connection;\r
37 import java.sql.PreparedStatement;\r
38 import java.sql.ResultSet;\r
39 import java.sql.ResultSetMetaData;\r
40 import java.sql.SQLException;\r
41 import java.sql.Statement;\r
42 import java.sql.Timestamp;\r
43 \r
44 import java.text.ParseException;\r
45 import java.text.SimpleDateFormat;\r
46 \r
47 import java.util.ArrayList;\r
48 import java.util.List;\r
49 import java.util.Calendar;\r
50 import java.util.GregorianCalendar;\r
51 import java.util.HashMap;\r
52 import java.util.Map;\r
53 \r
54 import com.codestudio.util.SQLManager;\r
55 \r
56 import freemarker.template.SimpleHash;\r
57 import freemarker.template.SimpleList;\r
58 \r
59 import mir.config.MirPropertiesConfiguration;\r
60 \r
61 import mir.config.MirPropertiesConfiguration.PropertiesConfigExc;\r
62 \r
63 import mir.entity.Entity;\r
64 import mir.entity.EntityList;\r
65 import mir.entity.StorableObjectEntity;\r
66 \r
67 import mir.log.LoggerWrapper;\r
68 \r
69 import mir.misc.HTMLTemplateProcessor;\r
70 import mir.misc.StringUtil;\r
71 \r
72 import mir.storage.store.ObjectStore;\r
73 import mir.storage.store.StorableObject;\r
74 import mir.storage.store.StoreContainerType;\r
75 import mir.storage.store.StoreIdentifier;\r
76 import mir.storage.store.StoreUtil;\r
77 \r
78 import mir.util.JDBCStringRoutines;\r
79 \r
80 \r
81 /**\r
82  * Diese Klasse implementiert die Zugriffsschicht auf die Datenbank.\r
83  * Alle Projektspezifischen Datenbankklassen erben von dieser Klasse.\r
84  * In den Unterklassen wird im Minimalfall nur die Tabelle angegeben.\r
85  * Im Konfigurationsfile findet sich eine Verweis auf den verwendeten\r
86  * Treiber, Host, User und Passwort, ueber den der Zugriff auf die\r
87  * Datenbank erfolgt.\r
88  *\r
89  * @version $Id: Database.java,v 1.35 2003/03/04 22:00:52 zapata Exp $\r
90  * @author rk\r
91  *\r
92  */\r
93 public class Database implements StorageObject {\r
94   private static Class GENERIC_ENTITY_CLASS = mir.entity.StorableObjectEntity.class;\r
95   private static Class STORABLE_OBJECT_ENTITY_CLASS = mir.entity.StorableObjectEntity.class;\r
96 \r
97 \r
98   private static SimpleHash POPUP_EMPTYLINE = new SimpleHash();\r
99   protected static final ObjectStore o_store = ObjectStore.getInstance();\r
100   private static final int _millisPerHour = 60 * 60 * 1000;\r
101   private static final int _millisPerMinute = 60 * 1000;\r
102 \r
103   static {\r
104     // always same object saves a little space\r
105     POPUP_EMPTYLINE.put("key", "");\r
106     POPUP_EMPTYLINE.put("value", "--");\r
107   }\r
108 \r
109   protected LoggerWrapper logger;\r
110   protected MirPropertiesConfiguration configuration;\r
111   protected String theTable;\r
112   protected String theCoreTable = null;\r
113   protected String thePKeyName = "id";\r
114   protected int thePKeyType;\r
115   protected int thePKeyIndex;\r
116   protected boolean evaluatedMetaData = false;\r
117   protected ArrayList metadataFields;\r
118   protected ArrayList metadataLabels;\r
119   protected ArrayList metadataNotNullFields;\r
120   protected int[] metadataTypes;\r
121   protected Class theEntityClass;\r
122   protected StorageObject myselfDatabase;\r
123   protected SimpleList popupCache = null;\r
124   protected boolean hasPopupCache = false;\r
125   protected SimpleHash hashCache = null;\r
126   protected boolean hasTimestamp = true;\r
127   private String database_driver;\r
128   private String database_url;\r
129   private int defaultLimit;\r
130   protected DatabaseAdaptor theAdaptor;\r
131   private SimpleDateFormat _dateFormatterOut =\r
132     new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");\r
133   private SimpleDateFormat _dateFormatterIn =\r
134     new SimpleDateFormat("yyyy-MM-dd HH:mm");\r
135   private Calendar _cal = new GregorianCalendar();\r
136 \r
137   /**\r
138    * Kontruktor bekommt den Filenamen des Konfigurationsfiles ?bergeben.\r
139    * Aus diesem file werden <code>Database.Logfile</code>,\r
140    * <code>Database.Username</code>,<code>Database.Password</code>,\r
141    * <code>Database.Host</code> und <code>Database.Adaptor</code>\r
142    * ausgelesen und ein Broker f?r die Verbindugen zur Datenbank\r
143    * erzeugt.\r
144    *\r
145    * @param   String confFilename Dateiname der Konfigurationsdatei\r
146    */\r
147   public Database() throws StorageObjectFailure {\r
148     try {\r
149       configuration = MirPropertiesConfiguration.instance();\r
150     }\r
151     catch (PropertiesConfigExc e) {\r
152       throw new StorageObjectFailure(e);\r
153     }\r
154     logger = new LoggerWrapper("Database");\r
155 \r
156     String theAdaptorName = configuration.getString("Database.Adaptor");\r
157     defaultLimit = Integer.parseInt(configuration.getString("Database.Limit"));\r
158 \r
159     try {\r
160       theEntityClass = GENERIC_ENTITY_CLASS;\r
161       theAdaptor = (DatabaseAdaptor) Class.forName(theAdaptorName).newInstance();\r
162     }\r
163     catch (Throwable e) {\r
164       logger.error("Error in Database() constructor with " + theAdaptorName + " -- " + e.getMessage());\r
165       throw new StorageObjectFailure("Error in Database() constructor.", e);\r
166     }\r
167   }\r
168 \r
169   /**\r
170    * Liefert die Entity-Klasse zur?ck, in der eine Datenbankzeile gewrappt\r
171    * wird. Wird die Entity-Klasse durch die erbende Klasse nicht ?berschrieben,\r
172    * wird eine mir.entity.GenericEntity erzeugt.\r
173    *\r
174    * @return Class-Objekt der Entity\r
175    */\r
176   public java.lang.Class getEntityClass() {\r
177     return theEntityClass;\r
178   }\r
179 \r
180   /**\r
181    * Liefert die Standardbeschr?nkung von select-Statements zur?ck, also\r
182    * wieviel Datens?tze per Default selektiert werden.\r
183    *\r
184    * @return Standard-Anzahl der Datens?tze\r
185    */\r
186   public int getLimit() {\r
187     return defaultLimit;\r
188   }\r
189 \r
190   /**\r
191    * Liefert den Namen des Primary-Keys zur?ck. Wird die Variable nicht von\r
192    * der erbenden Klasse ?berschrieben, so ist der Wert <code>PKEY</code>\r
193    * @return Name des Primary-Keys\r
194    */\r
195   public String getIdName() {\r
196     return thePKeyName;\r
197   }\r
198 \r
199   /**\r
200    * Liefert den Namen der Tabelle, auf das sich das Datenbankobjekt bezieht.\r
201    *\r
202    * @return Name der Tabelle\r
203    */\r
204   public String getTableName() {\r
205     return theTable;\r
206   }\r
207 \r
208   /*\r
209   *   Dient dazu vererbte Tabellen bei objectrelationalen DBMS\r
210   *   zu speichern, wenn die id einer Tabelle in der parenttabelle verwaltet\r
211   *   wird.\r
212   *   @return liefert theCoreTabel als String zurueck, wenn gesetzt, sonst\r
213   *    the Table\r
214    */\r
215   public String getCoreTable() {\r
216     if (theCoreTable != null) {\r
217       return theCoreTable;\r
218     } else {\r
219       return theTable;\r
220     }\r
221   }\r
222 \r
223   /**\r
224    * Liefert Feldtypen der Felder der Tabelle zurueck (s.a. java.sql.Types)\r
225    * @return int-Array mit den Typen der Felder\r
226    * @exception StorageObjectException\r
227    */\r
228   public int[] getTypes() throws StorageObjectFailure {\r
229     if (metadataTypes == null) {\r
230       get_meta_data();\r
231     }\r
232 \r
233     return metadataTypes;\r
234   }\r
235 \r
236   /**\r
237    * Liefert eine Liste der Labels der Tabellenfelder\r
238    * @return ArrayListe mit Labeln\r
239    * @exception StorageObjectException\r
240    */\r
241   public List getLabels() throws StorageObjectFailure {\r
242     if (metadataLabels == null) {\r
243       get_meta_data();\r
244     }\r
245 \r
246     return metadataLabels;\r
247   }\r
248 \r
249   /**\r
250    * Liefert eine Liste der Felder der Tabelle\r
251    * @return ArrayList mit Feldern\r
252    * @exception StorageObjectException\r
253    */\r
254   public List getFields() throws StorageObjectFailure {\r
255     if (metadataFields == null) {\r
256       get_meta_data();\r
257     }\r
258 \r
259     return metadataFields;\r
260   }\r
261 \r
262   /*\r
263   *   Gets value out of ResultSet according to type and converts to String\r
264   *   @param inValue  Wert aus ResultSet.\r
265   *   @param aType  Datenbanktyp.\r
266   *   @return liefert den Wert als String zurueck. Wenn keine Umwandlung moeglich\r
267   *           dann /unsupported value/\r
268    */\r
269   private String getValueAsString(ResultSet rs, int valueIndex, int aType)\r
270     throws StorageObjectFailure {\r
271     String outValue = null;\r
272 \r
273     if (rs != null) {\r
274       try {\r
275         switch (aType) {\r
276         case java.sql.Types.BIT:\r
277           outValue = (rs.getBoolean(valueIndex) == true) ? "1" : "0";\r
278 \r
279           break;\r
280 \r
281         case java.sql.Types.INTEGER:\r
282         case java.sql.Types.SMALLINT:\r
283         case java.sql.Types.TINYINT:\r
284         case java.sql.Types.BIGINT:\r
285 \r
286           int out = rs.getInt(valueIndex);\r
287 \r
288           if (!rs.wasNull()) {\r
289             outValue = new Integer(out).toString();\r
290           }\r
291 \r
292           break;\r
293 \r
294         case java.sql.Types.NUMERIC:\r
295 \r
296           /** @todo Numeric can be float or double depending upon\r
297            *  metadata.getScale() / especially with oracle */\r
298           long outl = rs.getLong(valueIndex);\r
299 \r
300           if (!rs.wasNull()) {\r
301             outValue = new Long(outl).toString();\r
302           }\r
303 \r
304           break;\r
305 \r
306         case java.sql.Types.REAL:\r
307 \r
308           float tempf = rs.getFloat(valueIndex);\r
309 \r
310           if (!rs.wasNull()) {\r
311             tempf *= 10;\r
312             tempf += 0.5;\r
313 \r
314             int tempf_int = (int) tempf;\r
315             tempf = (float) tempf_int;\r
316             tempf /= 10;\r
317             outValue = "" + tempf;\r
318             outValue = outValue.replace('.', ',');\r
319           }\r
320 \r
321           break;\r
322 \r
323         case java.sql.Types.DOUBLE:\r
324 \r
325           double tempd = rs.getDouble(valueIndex);\r
326 \r
327           if (!rs.wasNull()) {\r
328             tempd *= 10;\r
329             tempd += 0.5;\r
330 \r
331             int tempd_int = (int) tempd;\r
332             tempd = (double) tempd_int;\r
333             tempd /= 10;\r
334             outValue = "" + tempd;\r
335             outValue = outValue.replace('.', ',');\r
336           }\r
337 \r
338           break;\r
339 \r
340         case java.sql.Types.CHAR:\r
341         case java.sql.Types.VARCHAR:\r
342         case java.sql.Types.LONGVARCHAR:\r
343           outValue = rs.getString(valueIndex);\r
344 \r
345           break;\r
346 \r
347         case java.sql.Types.LONGVARBINARY:\r
348           outValue = rs.getString(valueIndex);\r
349 \r
350           break;\r
351 \r
352         case java.sql.Types.TIMESTAMP:\r
353 \r
354           // it's important to use Timestamp here as getting it\r
355           // as a string is undefined and is only there for debugging\r
356           // according to the API. we can make it a string through formatting.\r
357           // -mh\r
358           Timestamp timestamp = (rs.getTimestamp(valueIndex));\r
359 \r
360           if (!rs.wasNull()) {\r
361             java.util.Date date = new java.util.Date(timestamp.getTime());\r
362             outValue = _dateFormatterOut.format(date);\r
363             _cal.setTime(date);\r
364 \r
365             int offset =\r
366               _cal.get(Calendar.ZONE_OFFSET) + _cal.get(Calendar.DST_OFFSET);\r
367             String tzOffset =\r
368               StringUtil.zeroPaddingNumber(offset / _millisPerHour, 2, 2);\r
369             outValue = outValue + "+" + tzOffset;\r
370           }\r
371 \r
372           break;\r
373 \r
374         default:\r
375           outValue = "<unsupported value>";\r
376           logger.warn( "Unsupported Datatype: at " + valueIndex + " (" + aType + ")");\r
377         }\r
378       } catch (SQLException e) {\r
379         throw new StorageObjectFailure("Could not get Value out of Resultset -- ",\r
380           e);\r
381       }\r
382     }\r
383 \r
384     return outValue;\r
385   }\r
386 \r
387   /*\r
388   *   select-Operator um einen Datensatz zu bekommen.\r
389   *   @param id Primaerschluessel des Datensatzes.\r
390   *   @return liefert EntityObject des gefundenen Datensatzes oder null.\r
391    */\r
392   public Entity selectById(String id) throws StorageObjectExc {\r
393     if ((id == null) || id.equals("")) {\r
394       throw new StorageObjectExc("Database.selectById: Missing id");\r
395     }\r
396 \r
397     // ask object store for object\r
398     if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
399       String uniqueId = id;\r
400 \r
401       if (theEntityClass.equals(StorableObjectEntity.class)) {\r
402         uniqueId += ("@" + theTable);\r
403       }\r
404 \r
405       StoreIdentifier search_sid = new StoreIdentifier(theEntityClass, uniqueId);\r
406       logger.debug("CACHE: (dbg) looking for sid " + search_sid.toString());\r
407 \r
408       Entity hit = (Entity) o_store.use(search_sid);\r
409 \r
410       if (hit != null) {\r
411         return hit;\r
412       }\r
413     }\r
414 \r
415     Statement stmt = null;\r
416     Connection con = getPooledCon();\r
417     Entity returnEntity = null;\r
418 \r
419     try {\r
420       ResultSet rs;\r
421 \r
422       /** @todo better prepared statement */\r
423       String selectSql =\r
424         "select * from " + theTable + " where " + thePKeyName + "=" + id;\r
425       stmt = con.createStatement();\r
426       rs = executeSql(stmt, selectSql);\r
427 \r
428       if (rs != null) {\r
429         if (evaluatedMetaData == false) {\r
430           evalMetaData(rs.getMetaData());\r
431         }\r
432 \r
433         if (rs.next()) {\r
434           returnEntity = makeEntityFromResultSet(rs);\r
435         }\r
436         else {\r
437           logger.debug("No data for id: " + id + " in table " + theTable);\r
438         }\r
439 \r
440         rs.close();\r
441       }\r
442       else {\r
443         logger.debug("No Data for Id " + id + " in Table " + theTable);\r
444       }\r
445     }\r
446     catch (SQLException sqe) {\r
447       throwSQLException(sqe, "selectById");\r
448       return null;\r
449     }\r
450     catch (NumberFormatException e) {\r
451       logger.error("ID is no number: " + id);\r
452     }\r
453     finally {\r
454       freeConnection(con, stmt);\r
455     }\r
456 \r
457     return returnEntity;\r
458   }\r
459 \r
460   /**\r
461    *   select-Operator um Datensaetze zu bekommen, die key = value erfuellen.\r
462    *   @param key  Datenbankfeld der Bedingung.\r
463    *   @param value  Wert die der key anehmen muss.\r
464    *   @return EntityList mit den gematchten Entities\r
465    */\r
466   public EntityList selectByFieldValue(String aField, String aValue) throws StorageObjectFailure {\r
467     return selectByFieldValue(aField, aValue, 0);\r
468   }\r
469 \r
470   /**\r
471    *   select-Operator um Datensaetze zu bekommen, die key = value erfuellen.\r
472    *   @param key  Datenbankfeld der Bedingung.\r
473    *   @param value  Wert die der key anehmen muss.\r
474    *   @param offset  Gibt an ab welchem Datensatz angezeigt werden soll.\r
475    *   @return EntityList mit den gematchten Entities\r
476    */\r
477   public EntityList selectByFieldValue(String aField, String aValue, int offset) throws StorageObjectFailure {\r
478     return selectByWhereClause(aField + "=" + aValue, offset);\r
479   }\r
480 \r
481   /**\r
482    * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck.\r
483    * Also offset wird der erste Datensatz genommen.\r
484    *\r
485    * @param wc where-Clause\r
486    * @return EntityList mit den gematchten Entities\r
487    * @exception StorageObjectException\r
488    */\r
489   public EntityList selectByWhereClause(String where)\r
490     throws StorageObjectFailure {\r
491     return selectByWhereClause(where, 0);\r
492   }\r
493 \r
494   /**\r
495    * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck.\r
496    * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.\r
497    *\r
498    * @param wc where-Clause\r
499    * @param offset ab welchem Datensatz.\r
500    * @return EntityList mit den gematchten Entities\r
501    * @exception StorageObjectException\r
502    */\r
503   public EntityList selectByWhereClause(String whereClause, int offset)\r
504     throws StorageObjectFailure {\r
505     return selectByWhereClause(whereClause, null, offset);\r
506   }\r
507 \r
508   /**\r
509    * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck.\r
510    * Also offset wird der erste Datensatz genommen.\r
511    * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.\r
512    *\r
513    * @param wc where-Clause\r
514    * @param ob orderBy-Clause\r
515    * @return EntityList mit den gematchten Entities\r
516    * @exception StorageObjectException\r
517    */\r
518   public EntityList selectByWhereClause(String where, String order)\r
519     throws StorageObjectFailure {\r
520     return selectByWhereClause(where, order, 0);\r
521   }\r
522 \r
523   /**\r
524    * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck.\r
525    * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.\r
526    *\r
527    * @param wc where-Clause\r
528    * @param ob orderBy-Clause\r
529    * @param offset ab welchem Datensatz\r
530    * @return EntityList mit den gematchten Entities\r
531    * @exception StorageObjectException\r
532    */\r
533   public EntityList selectByWhereClause(String whereClause, String orderBy,\r
534     int offset) throws StorageObjectFailure {\r
535     return selectByWhereClause(whereClause, orderBy, offset, defaultLimit);\r
536   }\r
537 \r
538   /**\r
539    * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck.\r
540    * @param wc where-Clause\r
541    * @param ob orderBy-Clause\r
542    * @param offset ab welchem Datensatz\r
543    * @param limit wieviele Datens?tze\r
544    * @return EntityList mit den gematchten Entities\r
545    * @exception StorageObjectException\r
546    */\r
547   public EntityList selectByWhereClause(String wc, String ob, int offset,\r
548     int limit) throws StorageObjectFailure {\r
549     // check o_store for entitylist\r
550     if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
551       StoreIdentifier search_sid =\r
552         new StoreIdentifier(theEntityClass,\r
553           StoreContainerType.STOC_TYPE_ENTITYLIST,\r
554           StoreUtil.getEntityListUniqueIdentifierFor(theTable, wc, ob, offset,\r
555             limit));\r
556       EntityList hit = (EntityList) o_store.use(search_sid);\r
557 \r
558       if (hit != null) {\r
559         logger.debug("CACHE (hit): " + search_sid.toString());\r
560 \r
561         return hit;\r
562       }\r
563     }\r
564 \r
565     // local\r
566     EntityList theReturnList = null;\r
567     Connection con = null;\r
568     Statement stmt = null;\r
569     ResultSet rs;\r
570     int offsetCount = 0;\r
571     int count = 0;\r
572 \r
573     // build sql-statement\r
574 \r
575     /** @todo count sql string should only be assembled if we really count\r
576      *  see below at the end of method //rk */\r
577     if ((wc != null) && (wc.trim().length() == 0)) {\r
578       wc = null;\r
579     }\r
580 \r
581     StringBuffer countSql =\r
582       new StringBuffer("select count(*) from ").append(theTable);\r
583     StringBuffer selectSql =\r
584       new StringBuffer("select * from ").append(theTable);\r
585 \r
586     if (wc != null) {\r
587       selectSql.append(" where ").append(wc);\r
588       countSql.append(" where ").append(wc);\r
589     }\r
590 \r
591     if ((ob != null) && !(ob.trim().length() == 0)) {\r
592       selectSql.append(" order by ").append(ob);\r
593     }\r
594 \r
595     if (theAdaptor.hasLimit()) {\r
596       if ((limit > -1) && (offset > -1)) {\r
597         selectSql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset);\r
598       }\r
599     }\r
600 \r
601     // execute sql\r
602     try {\r
603       con = getPooledCon();\r
604       stmt = con.createStatement();\r
605 \r
606       // selecting...\r
607       rs = executeSql(stmt, selectSql.toString());\r
608 \r
609       if (rs != null) {\r
610         if (!evaluatedMetaData) {\r
611           evalMetaData(rs.getMetaData());\r
612         }\r
613 \r
614         theReturnList = new EntityList();\r
615 \r
616         Entity theResultEntity;\r
617 \r
618         while (rs.next()) {\r
619           theResultEntity = makeEntityFromResultSet(rs);\r
620           theReturnList.add(theResultEntity);\r
621           offsetCount++;\r
622         }\r
623 \r
624         rs.close();\r
625       }\r
626 \r
627       // making entitylist infos\r
628       if (!(theAdaptor.hasLimit())) {\r
629         count = offsetCount;\r
630       }\r
631 \r
632       if (theReturnList != null) {\r
633         // now we decide if we have to know an overall count...\r
634         count = offsetCount;\r
635 \r
636         if ((limit > -1) && (offset > -1)) {\r
637           if (offsetCount == limit) {\r
638             /** @todo counting should be deffered to entitylist\r
639              *  getSize() should be used */\r
640             rs = executeSql(stmt, countSql.toString());\r
641 \r
642             if (rs != null) {\r
643               if (rs.next()) {\r
644                 count = rs.getInt(1);\r
645               }\r
646 \r
647               rs.close();\r
648             }\r
649             else {\r
650               logger.error("Could not count: " + countSql);\r
651             }\r
652           }\r
653         }\r
654 \r
655         theReturnList.setCount(count);\r
656         theReturnList.setOffset(offset);\r
657         theReturnList.setWhere(wc);\r
658         theReturnList.setOrder(ob);\r
659         theReturnList.setStorage(this);\r
660         theReturnList.setLimit(limit);\r
661 \r
662         if (offset >= limit) {\r
663           theReturnList.setPrevBatch(offset - limit);\r
664         }\r
665 \r
666         if ((offset + offsetCount) < count) {\r
667           theReturnList.setNextBatch(offset + limit);\r
668         }\r
669 \r
670         if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
671           StoreIdentifier sid = theReturnList.getStoreIdentifier();\r
672           logger.debug("CACHE (add): " + sid.toString());\r
673           o_store.add(sid);\r
674         }\r
675       }\r
676     } catch (SQLException sqe) {\r
677       throwSQLException(sqe, "selectByWhereClause");\r
678     } finally {\r
679       try {\r
680         if (con != null) {\r
681           freeConnection(con, stmt);\r
682         }\r
683       } catch (Throwable t) {\r
684       }\r
685     }\r
686 \r
687     return theReturnList;\r
688   }\r
689 \r
690   /**\r
691    *  Bastelt aus einer Zeile der Datenbank ein EntityObjekt.\r
692    *\r
693    *  @param rs Das ResultSetObjekt.\r
694    *  @return Entity Die Entity.\r
695    */\r
696   private Entity makeEntityFromResultSet(ResultSet rs)\r
697     throws StorageObjectFailure {\r
698     /** @todo OS: get Pkey from ResultSet and consult ObjectStore */\r
699     Map theResultHash = new HashMap();\r
700     String theResult = null;\r
701     int theType;\r
702     Entity returnEntity = null;\r
703 \r
704     try {\r
705       int size = metadataFields.size();\r
706 \r
707       for (int i = 0; i < size; i++) {\r
708         // alle durchlaufen bis nix mehr da\r
709         theType = metadataTypes[i];\r
710 \r
711         if (theType == java.sql.Types.LONGVARBINARY) {\r
712           InputStreamReader is =\r
713             (InputStreamReader) rs.getCharacterStream(i + 1);\r
714 \r
715           if (is != null) {\r
716             char[] data = new char[32768];\r
717             StringBuffer theResultString = new StringBuffer();\r
718             int len;\r
719 \r
720             while ((len = is.read(data)) > 0) {\r
721               theResultString.append(data, 0, len);\r
722             }\r
723 \r
724             is.close();\r
725             theResult = theResultString.toString();\r
726           } else {\r
727             theResult = null;\r
728           }\r
729         } else {\r
730           theResult = getValueAsString(rs, (i + 1), theType);\r
731         }\r
732 \r
733         if (theResult != null) {\r
734           theResultHash.put(metadataFields.get(i), theResult);\r
735         }\r
736       }\r
737 \r
738       if (theEntityClass != null) {\r
739         returnEntity = (Entity) theEntityClass.newInstance();\r
740         returnEntity.setValues(theResultHash);\r
741         returnEntity.setStorage(myselfDatabase);\r
742 \r
743         if (returnEntity instanceof StorableObject) {\r
744           logger.debug("CACHE: ( in) " + returnEntity.getId() + " :" + theTable);\r
745           o_store.add(((StorableObject) returnEntity).getStoreIdentifier());\r
746         }\r
747       } else {\r
748         throwStorageObjectException("Internal Error: theEntityClass not set!");\r
749       }\r
750     } catch (IllegalAccessException e) {\r
751       throwStorageObjectException("No access! -- " + e.getMessage());\r
752     } catch (IOException e) {\r
753       throwStorageObjectException("IOException! -- " + e.getMessage());\r
754     } catch (InstantiationException e) {\r
755       throwStorageObjectException("No Instatiation! -- " + e.getMessage());\r
756     } catch (SQLException sqe) {\r
757       throwSQLException(sqe, "makeEntityFromResultSet");\r
758 \r
759       return null;\r
760     }\r
761 \r
762     return returnEntity;\r
763   }\r
764 \r
765   /**\r
766    * insert-Operator: f?gt eine Entity in die Tabelle ein. Eine Spalte WEBDB_CREATE\r
767    * wird automatisch mit dem aktuellen Datum gefuellt.\r
768    *\r
769    * @param theEntity\r
770    * @return der Wert des Primary-keys der eingef?gten Entity\r
771    */\r
772   public String insert(Entity theEntity) throws StorageObjectFailure {\r
773     //cache\r
774     invalidatePopupCache();\r
775 \r
776     // invalidating all EntityLists corresponding with theEntityClass\r
777     if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
778       StoreContainerType stoc_type =\r
779         StoreContainerType.valueOf(theEntityClass,\r
780           StoreContainerType.STOC_TYPE_ENTITYLIST);\r
781       o_store.invalidate(stoc_type);\r
782     }\r
783 \r
784     String returnId = null;\r
785     Connection con = null;\r
786     PreparedStatement pstmt = null;\r
787 \r
788     try {\r
789       List streamedInput = theEntity.streamedInput();\r
790       StringBuffer f = new StringBuffer();\r
791       StringBuffer v = new StringBuffer();\r
792       String aField;\r
793       String aValue;\r
794       boolean firstField = true;\r
795 \r
796       // make sql-string\r
797       for (int i = 0; i < getFields().size(); i++) {\r
798         aField = (String) getFields().get(i);\r
799 \r
800         if (!aField.equals(thePKeyName)) {\r
801           aValue = null;\r
802 \r
803           // exceptions\r
804           if (aField.equals("webdb_create") ||\r
805               aField.equals("webdb_lastchange")) {\r
806             aValue = "NOW()";\r
807           } else {\r
808             if ((streamedInput != null) && streamedInput.contains(aField)) {\r
809               aValue = "?";\r
810             } else {\r
811               if (theEntity.hasValueForField(aField)) {\r
812                 aValue =\r
813                   "'" +\r
814                   JDBCStringRoutines.escapeStringLiteral((String) theEntity.getValue(\r
815                       aField)) + "'";\r
816               }\r
817             }\r
818           }\r
819 \r
820           // wenn Wert gegeben, dann einbauen\r
821           if (aValue != null) {\r
822             if (firstField == false) {\r
823               f.append(",");\r
824               v.append(",");\r
825             }\r
826             else {\r
827               firstField = false;\r
828             }\r
829 \r
830             f.append(aField);\r
831             v.append(aValue);\r
832           }\r
833         }\r
834       }\r
835        // end for\r
836 \r
837       // insert into db\r
838       StringBuffer sqlBuf =\r
839         new StringBuffer("insert into ").append(theTable).append("(").append(f)\r
840                                         .append(") values (").append(v).append(")");\r
841       String sql = sqlBuf.toString();\r
842 \r
843       //theLog.printInfo("INSERT: " + sql);\r
844       con = getPooledCon();\r
845       con.setAutoCommit(false);\r
846       pstmt = con.prepareStatement(sql);\r
847 \r
848       if (streamedInput != null) {\r
849         for (int i = 0; i < streamedInput.size(); i++) {\r
850           String inputString =\r
851             (String) theEntity.getValue((String) streamedInput.get(i));\r
852           pstmt.setBytes(i + 1, inputString.getBytes());\r
853         }\r
854       }\r
855 \r
856       int ret = pstmt.executeUpdate();\r
857 \r
858       if (ret == 0) {\r
859         //insert failed\r
860         return null;\r
861       }\r
862 \r
863       pstmt =\r
864         con.prepareStatement(theAdaptor.getLastInsertSQL(\r
865             (Database) myselfDatabase));\r
866 \r
867       ResultSet rs = pstmt.executeQuery();\r
868       rs.next();\r
869       returnId = rs.getString(1);\r
870       theEntity.setId(returnId);\r
871     }\r
872     catch (SQLException sqe) {\r
873       throwSQLException(sqe, "insert");\r
874     }\r
875     finally {\r
876       try {\r
877         con.setAutoCommit(true);\r
878       }\r
879       catch (Exception e) {\r
880       }\r
881 \r
882       freeConnection(con, pstmt);\r
883     }\r
884 \r
885     /** @todo store entity in o_store */\r
886     return returnId;\r
887   }\r
888 \r
889   /**\r
890    * update-Operator: aktualisiert eine Entity. Eine Spalte WEBDB_LASTCHANGE\r
891    * wird automatisch mit dem aktuellen Datum gefuellt.\r
892    *\r
893    * @param theEntity\r
894    */\r
895   public void update(Entity theEntity) throws StorageObjectFailure {\r
896     Connection con = null;\r
897     PreparedStatement pstmt = null;\r
898 \r
899     /** @todo this is stupid: why do we prepare statement, when we\r
900      *  throw it away afterwards. should be regular statement\r
901      *  update/insert could better be one routine called save()\r
902      *  that chooses to either insert or update depending if we\r
903      *  have a primary key in the entity. i don't know if we\r
904      *  still need the streamed input fields. // rk  */\r
905     /** @todo extension: check if Entity did change, otherwise we don't need\r
906      *  the roundtrip to the database */\r
907     /** invalidating corresponding entitylists in o_store*/\r
908     if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
909       StoreContainerType stoc_type =\r
910         StoreContainerType.valueOf(theEntityClass,\r
911           StoreContainerType.STOC_TYPE_ENTITYLIST);\r
912       o_store.invalidate(stoc_type);\r
913     }\r
914 \r
915     List streamedInput = theEntity.streamedInput();\r
916     String id = theEntity.getId();\r
917     String aField;\r
918     StringBuffer fv = new StringBuffer();\r
919     boolean firstField = true;\r
920 \r
921     //cache\r
922     invalidatePopupCache();\r
923 \r
924     // build sql statement\r
925     for (int i = 0; i < getFields().size(); i++) {\r
926       aField = (String) metadataFields.get(i);\r
927 \r
928       // only normal cases\r
929       if (  !(aField.equals(thePKeyName) ||\r
930             aField.equals("webdb_create") ||\r
931             aField.equals("webdb_lastchange") ||\r
932             ((streamedInput != null) && streamedInput.contains(aField)))) {\r
933         if (theEntity.hasValueForField(aField)) {\r
934           if (firstField == false) {\r
935             fv.append(", ");\r
936           }\r
937           else {\r
938             firstField = false;\r
939           }\r
940 \r
941           fv.append(aField).append("='").append(JDBCStringRoutines.escapeStringLiteral((String) theEntity.getValue(aField))).append("'");\r
942 \r
943           //              fv.append(aField).append("='").append(StringUtil.quote((String)theEntity.getValue(aField))).append("'");\r
944         }\r
945       }\r
946     }\r
947 \r
948     StringBuffer sql =\r
949       new StringBuffer("update ").append(theTable).append(" set ").append(fv);\r
950 \r
951     // exceptions\r
952     if (metadataFields.contains("webdb_lastchange")) {\r
953       sql.append(",webdb_lastchange=NOW()");\r
954     }\r
955 \r
956     // special case: the webdb_create requires the field in yyyy-mm-dd HH:mm\r
957     // format so anything extra will be ignored. -mh\r
958     if (metadataFields.contains("webdb_create") &&\r
959         theEntity.hasValueForField("webdb_create")) {\r
960       // minimum of 10 (yyyy-mm-dd)...\r
961       if (theEntity.getValue("webdb_create").length() >= 10) {\r
962         String dateString = theEntity.getValue("webdb_create");\r
963 \r
964         // if only 10, then add 00:00 so it doesn't throw a ParseException\r
965         if (dateString.length() == 10) {\r
966           dateString = dateString + " 00:00";\r
967         }\r
968 \r
969         // TimeStamp stuff\r
970         try {\r
971           java.util.Date d = _dateFormatterIn.parse(dateString);\r
972           Timestamp tStamp = new Timestamp(d.getTime());\r
973           sql.append(",webdb_create='" + tStamp.toString() + "'");\r
974         } catch (ParseException e) {\r
975           throw new StorageObjectFailure(e);\r
976         }\r
977       }\r
978     }\r
979 \r
980     if (streamedInput != null) {\r
981       for (int i = 0; i < streamedInput.size(); i++) {\r
982         sql.append(",").append(streamedInput.get(i)).append("=?");\r
983       }\r
984     }\r
985 \r
986     sql.append(" where id=").append(id);\r
987 \r
988     //theLog.printInfo("UPDATE: " + sql);\r
989     // execute sql\r
990     try {\r
991       con = getPooledCon();\r
992       con.setAutoCommit(false);\r
993       pstmt = con.prepareStatement(sql.toString());\r
994 \r
995       if (streamedInput != null) {\r
996         for (int i = 0; i < streamedInput.size(); i++) {\r
997           String inputString =\r
998             theEntity.getValue((String) streamedInput.get(i));\r
999           pstmt.setBytes(i + 1, inputString.getBytes());\r
1000         }\r
1001       }\r
1002 \r
1003       pstmt.executeUpdate();\r
1004     } catch (SQLException sqe) {\r
1005       throwSQLException(sqe, "update");\r
1006     } finally {\r
1007       try {\r
1008         con.setAutoCommit(true);\r
1009       } catch (Exception e) {\r
1010         ;\r
1011       }\r
1012 \r
1013       freeConnection(con, pstmt);\r
1014     }\r
1015   }\r
1016 \r
1017   /*\r
1018   *   delete-Operator\r
1019   *   @param id des zu loeschenden Datensatzes\r
1020   *   @return boolean liefert true zurueck, wenn loeschen erfolgreich war.\r
1021    */\r
1022   public boolean delete(String id) throws StorageObjectFailure {\r
1023     invalidatePopupCache();\r
1024 \r
1025     // ostore send notification\r
1026     if (StoreUtil.implementsStorableObject(theEntityClass)) {\r
1027       String uniqueId = id;\r
1028 \r
1029       if (theEntityClass.equals(StorableObjectEntity.class)) {\r
1030         uniqueId += ("@" + theTable);\r
1031       }\r
1032 \r
1033       logger.debug("CACHE: (del) " + id);\r
1034 \r
1035       StoreIdentifier search_sid =\r
1036         new StoreIdentifier(theEntityClass,\r
1037           StoreContainerType.STOC_TYPE_ENTITY, uniqueId);\r
1038       o_store.invalidate(search_sid);\r
1039     }\r
1040 \r
1041     /** @todo could be prepared Statement */\r
1042     Statement stmt = null;\r
1043     Connection con = null;\r
1044     int res = 0;\r
1045     String sql =\r
1046       "delete from " + theTable + " where " + thePKeyName + "='" + id + "'";\r
1047 \r
1048     //theLog.printInfo("DELETE " + sql);\r
1049     try {\r
1050       con = getPooledCon();\r
1051       stmt = con.createStatement();\r
1052       res = stmt.executeUpdate(sql);\r
1053     } catch (SQLException sqe) {\r
1054       throwSQLException(sqe, "delete");\r
1055     } finally {\r
1056       freeConnection(con, stmt);\r
1057     }\r
1058 \r
1059     return (res > 0) ? true : false;\r
1060   }\r
1061 \r
1062   /* noch nicht implementiert.\r
1063   * @return immer false\r
1064    */\r
1065   public boolean delete(EntityList theEntityList) {\r
1066     invalidatePopupCache();\r
1067 \r
1068     return false;\r
1069   }\r
1070 \r
1071   /**\r
1072    * Diese Methode sollte ueberschrieben werden, wenn fuer die abgeleitete Database-Klasse\r
1073    * eine SimpleList mit Standard-Popupdaten erzeugt werden koennen soll.\r
1074    * @return null\r
1075    */\r
1076   public SimpleList getPopupData() throws StorageObjectFailure {\r
1077     return null;\r
1078   }\r
1079 \r
1080   /**\r
1081    *  Holt Daten fuer Popups.\r
1082    *  @param name  Name des Feldes.\r
1083    *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.\r
1084    *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.\r
1085    */\r
1086   public SimpleList getPopupData(String name, boolean hasNullValue)\r
1087     throws StorageObjectFailure {\r
1088     return getPopupData(name, hasNullValue, null);\r
1089   }\r
1090 \r
1091   /**\r
1092    *  Holt Daten fuer Popups.\r
1093    *  @param name  Name des Feldes.\r
1094    *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.\r
1095    *  @param where  Schraenkt die Selektion der Datensaetze ein.\r
1096    *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.\r
1097    */\r
1098   public SimpleList getPopupData(String name, boolean hasNullValue, String where)\r
1099     throws StorageObjectFailure {\r
1100     return getPopupData(name, hasNullValue, where, null);\r
1101   }\r
1102 \r
1103   /**\r
1104    *  Holt Daten fuer Popups.\r
1105    *  @param name  Name des Feldes.\r
1106    *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.\r
1107    *  @param where  Schraenkt die Selektion der Datensaetze ein.\r
1108    *  @param order  Gibt ein Feld als Sortierkriterium an.\r
1109    *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.\r
1110    */\r
1111   public SimpleList getPopupData(String name, boolean hasNullValue,\r
1112     String where, String order) throws StorageObjectFailure {\r
1113     // caching\r
1114     if (hasPopupCache && (popupCache != null)) {\r
1115       return popupCache;\r
1116     }\r
1117 \r
1118     SimpleList simpleList = null;\r
1119     Connection con = null;\r
1120     Statement stmt = null;\r
1121 \r
1122     // build sql\r
1123     StringBuffer sql =\r
1124       new StringBuffer("select ").append(thePKeyName).append(",").append(name)\r
1125                                  .append(" from ").append(theTable);\r
1126 \r
1127     if ((where != null) && !(where.length() == 0)) {\r
1128       sql.append(" where ").append(where);\r
1129     }\r
1130 \r
1131     sql.append(" order by ");\r
1132 \r
1133     if ((order != null) && !(order.length() == 0)) {\r
1134       sql.append(order);\r
1135     } else {\r
1136       sql.append(name);\r
1137     }\r
1138 \r
1139     // execute sql\r
1140     try {\r
1141       con = getPooledCon();\r
1142     } catch (Exception e) {\r
1143       throw new StorageObjectFailure(e);\r
1144     }\r
1145 \r
1146     try {\r
1147       stmt = con.createStatement();\r
1148 \r
1149       ResultSet rs = executeSql(stmt, sql.toString());\r
1150 \r
1151       if (rs != null) {\r
1152         if (!evaluatedMetaData) {\r
1153           get_meta_data();\r
1154         }\r
1155 \r
1156         simpleList = new SimpleList();\r
1157 \r
1158         // if popup has null-selector\r
1159         if (hasNullValue) {\r
1160           simpleList.add(POPUP_EMPTYLINE);\r
1161         }\r
1162 \r
1163         SimpleHash popupDict;\r
1164 \r
1165         while (rs.next()) {\r
1166           popupDict = new SimpleHash();\r
1167           popupDict.put("key", getValueAsString(rs, 1, thePKeyType));\r
1168           popupDict.put("value", rs.getString(2));\r
1169           simpleList.add(popupDict);\r
1170         }\r
1171 \r
1172         rs.close();\r
1173       }\r
1174     }\r
1175     catch (Exception e) {\r
1176       logger.error("getPopupData: " + e.getMessage());\r
1177       throw new StorageObjectFailure(e);\r
1178     } finally {\r
1179       freeConnection(con, stmt);\r
1180     }\r
1181 \r
1182     if (hasPopupCache) {\r
1183       popupCache = simpleList;\r
1184     }\r
1185 \r
1186     return simpleList;\r
1187   }\r
1188 \r
1189   /**\r
1190    * Liefert alle Daten der Tabelle als SimpleHash zurueck. Dies wird verwandt,\r
1191    * wenn in den Templates ein Lookup-Table benoetigt wird. Sollte nur bei kleinen\r
1192    * Tabellen Verwendung finden.\r
1193    * @return SimpleHash mit den Tabellezeilen.\r
1194    */\r
1195   public SimpleHash getHashData() {\r
1196     /** @todo dangerous! this should have a flag to be enabled, otherwise\r
1197      *  very big Hashes could be returned */\r
1198     if (hashCache == null) {\r
1199       try {\r
1200         hashCache =\r
1201           HTMLTemplateProcessor.makeSimpleHash(selectByWhereClause("", -1));\r
1202       }\r
1203       catch (StorageObjectFailure e) {\r
1204         logger.debug(e.getMessage());\r
1205       }\r
1206     }\r
1207 \r
1208     return hashCache;\r
1209   }\r
1210 \r
1211   /* invalidates the popupCache\r
1212    */\r
1213   protected void invalidatePopupCache() {\r
1214     /** @todo  invalidates toooo much */\r
1215     popupCache = null;\r
1216     hashCache = null;\r
1217   }\r
1218 \r
1219   /**\r
1220    * Diese Methode fuehrt den Sqlstring <i>sql</i> aus und timed im Logfile.\r
1221    * @param stmt Statemnt\r
1222    * @param sql Sql-String\r
1223    * @return ResultSet\r
1224    * @exception StorageObjectException\r
1225    */\r
1226   public ResultSet executeSql(Statement stmt, String sql)\r
1227                             throws StorageObjectFailure, SQLException {\r
1228     ResultSet rs;\r
1229     long startTime = System.currentTimeMillis();\r
1230 \r
1231     try {\r
1232       rs = stmt.executeQuery(sql);\r
1233 \r
1234       logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1235     }\r
1236     catch (SQLException e) {\r
1237       logger.error(e.getMessage() +"\n" + (System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1238       throw e;\r
1239     }\r
1240 \r
1241     return rs;\r
1242   }\r
1243 \r
1244   /**\r
1245    * returns the number of rows in the table\r
1246    */\r
1247   public int getSize(String where) throws SQLException, StorageObjectFailure {\r
1248     long startTime = System.currentTimeMillis();\r
1249     String sql = "SELECT Count(*) FROM " + theTable;\r
1250 \r
1251     if ((where != null) && !(where.length() == 0)) {\r
1252       sql = sql + " where " + where;\r
1253     }\r
1254 \r
1255     Connection con = null;\r
1256     Statement stmt = null;\r
1257     int result = 0;\r
1258 \r
1259     try {\r
1260       con = getPooledCon();\r
1261       stmt = con.createStatement();\r
1262 \r
1263       ResultSet rs = executeSql(stmt, sql);\r
1264 \r
1265       while (rs.next()) {\r
1266         result = rs.getInt(1);\r
1267       }\r
1268     }\r
1269     catch (SQLException e) {\r
1270       logger.error("Database.getSize: " + e.getMessage());\r
1271     }\r
1272     finally {\r
1273       freeConnection(con, stmt);\r
1274     }\r
1275 \r
1276     //theLog.printInfo(theTable + " has "+ result +" rows where " + where);\r
1277     logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1278 \r
1279     return result;\r
1280   }\r
1281 \r
1282   public int executeUpdate(Statement stmt, String sql)\r
1283     throws StorageObjectFailure, SQLException {\r
1284     int rs;\r
1285     long startTime = System.currentTimeMillis();\r
1286 \r
1287     try {\r
1288       rs = stmt.executeUpdate(sql);\r
1289 \r
1290       logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1291     }\r
1292     catch (SQLException e) {\r
1293       logger.debug("Failed: " + (System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1294       throw e;\r
1295     }\r
1296 \r
1297     return rs;\r
1298   }\r
1299 \r
1300   public int executeUpdate(String sql)\r
1301     throws StorageObjectFailure, SQLException {\r
1302     int result = -1;\r
1303     long startTime = System.currentTimeMillis();\r
1304     Connection con = null;\r
1305     PreparedStatement pstmt = null;\r
1306 \r
1307     try {\r
1308       con = getPooledCon();\r
1309       pstmt = con.prepareStatement(sql);\r
1310       result = pstmt.executeUpdate();\r
1311     }\r
1312     catch (Throwable e) {\r
1313       logger.error("Database.executeUpdate(" + sql + "): " + e.getMessage());\r
1314       throw new StorageObjectFailure("Database.executeUpdate(" + sql + "): " + e.getMessage(), e);\r
1315     }\r
1316     finally {\r
1317       freeConnection(con, pstmt);\r
1318     }\r
1319 \r
1320     logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql);\r
1321     return result;\r
1322   }\r
1323 \r
1324   /**\r
1325    * Wertet ResultSetMetaData aus und setzt interne Daten entsprechend\r
1326    * @param md ResultSetMetaData\r
1327    * @exception StorageObjectException\r
1328    */\r
1329   private void evalMetaData(ResultSetMetaData md) throws StorageObjectFailure {\r
1330     this.evaluatedMetaData = true;\r
1331     this.metadataFields = new ArrayList();\r
1332     this.metadataLabels = new ArrayList();\r
1333     this.metadataNotNullFields = new ArrayList();\r
1334 \r
1335     try {\r
1336       int numFields = md.getColumnCount();\r
1337       this.metadataTypes = new int[numFields];\r
1338 \r
1339       String aField;\r
1340       int aType;\r
1341 \r
1342       for (int i = 1; i <= numFields; i++) {\r
1343         aField = md.getColumnName(i);\r
1344         metadataFields.add(aField);\r
1345         metadataLabels.add(md.getColumnLabel(i));\r
1346         aType = md.getColumnType(i);\r
1347         metadataTypes[i - 1] = aType;\r
1348 \r
1349         if (aField.equals(thePKeyName)) {\r
1350           thePKeyType = aType;\r
1351           thePKeyIndex = i;\r
1352         }\r
1353 \r
1354         if (md.isNullable(i) == ResultSetMetaData.columnNullable) {\r
1355           metadataNotNullFields.add(aField);\r
1356         }\r
1357       }\r
1358     }\r
1359     catch (SQLException e) {\r
1360       throwSQLException(e, "evalMetaData");\r
1361     }\r
1362   }\r
1363 \r
1364   /**\r
1365    *  Wertet die Metadaten eines Resultsets fuer eine Tabelle aus,\r
1366    *  um die alle Columns und Typen einer Tabelle zu ermitteln.\r
1367    */\r
1368   private void get_meta_data() throws StorageObjectFailure {\r
1369     Connection con = null;\r
1370     PreparedStatement pstmt = null;\r
1371     String sql = "select * from " + theTable + " where 0=1";\r
1372 \r
1373     try {\r
1374       con = getPooledCon();\r
1375       pstmt = con.prepareStatement(sql);\r
1376 \r
1377       logger.debug("METADATA: " + sql);\r
1378       ResultSet rs = pstmt.executeQuery();\r
1379       evalMetaData(rs.getMetaData());\r
1380       rs.close();\r
1381     }\r
1382     catch (SQLException e) {\r
1383       throwSQLException(e, "get_meta_data");\r
1384     }\r
1385     finally {\r
1386       freeConnection(con, pstmt);\r
1387     }\r
1388   }\r
1389 \r
1390   public Connection getPooledCon() throws StorageObjectFailure {\r
1391     Connection con = null;\r
1392 \r
1393     try {\r
1394       con = SQLManager.getInstance().requestConnection();\r
1395     }\r
1396     catch (SQLException e) {\r
1397       logger.error("could not connect to the database " + e.getMessage());\r
1398 \r
1399       throw new StorageObjectFailure("Could not connect to the database", e);\r
1400     }\r
1401 \r
1402     return con;\r
1403   }\r
1404 \r
1405   public void freeConnection(Connection con, Statement stmt)\r
1406     throws StorageObjectFailure {\r
1407     SQLManager.closeStatement(stmt);\r
1408     SQLManager.getInstance().returnConnection(con);\r
1409   }\r
1410 \r
1411   /**\r
1412    * Wertet SQLException aus und wirft dannach eine StorageObjectException\r
1413    * @param sqe SQLException\r
1414    * @param wo Funktonsname, in der die SQLException geworfen wurde\r
1415    * @exception StorageObjectException\r
1416    */\r
1417   protected void throwSQLException(SQLException sqe, String aFunction)\r
1418     throws StorageObjectFailure {\r
1419     String state = "";\r
1420     String message = "";\r
1421     int vendor = 0;\r
1422 \r
1423     if (sqe != null) {\r
1424       state = sqe.getSQLState();\r
1425       message = sqe.getMessage();\r
1426       vendor = sqe.getErrorCode();\r
1427     }\r
1428 \r
1429     String information =\r
1430         "SQL Error: " +\r
1431         "state= " + state +\r
1432         ", vendor= " + vendor +\r
1433         ", message=" + message +\r
1434         ", function= " + aFunction;\r
1435 \r
1436     logger.error(information);\r
1437 \r
1438     throw new StorageObjectFailure(information, sqe);\r
1439   }\r
1440 \r
1441   protected void _throwStorageObjectException(Exception e, String aFunction)\r
1442     throws StorageObjectFailure {\r
1443 \r
1444     if (e != null) {\r
1445       logger.error(e.getMessage() + aFunction);\r
1446       throw new StorageObjectFailure(aFunction, e);\r
1447     }\r
1448   }\r
1449 \r
1450   /**\r
1451    * Loggt Fehlermeldung mit dem Parameter Message und wirft dannach\r
1452    * eine StorageObjectException\r
1453    * @param message Nachricht mit dem Fehler\r
1454    * @exception StorageObjectException\r
1455    */\r
1456   void throwStorageObjectException(String aMessage) throws StorageObjectFailure {\r
1457     logger.error(aMessage);\r
1458     throw new StorageObjectFailure(aMessage, null);\r
1459   }\r
1460 }\r