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