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