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