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