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