first cut of merge of STABLE-pre1_0 into HEAD. I won't even guarantee that it
[mir.git] / source / mir / storage / Database.java
1 /*
2  * put your module comment here
3  */
4 package mir.storage;
5
6 import  java.sql.*;
7 import  java.lang.*;
8 import  java.io.*;
9 import  java.util.*;
10 import  freemarker.template.*;
11 import  mir.storage.StorageObject;
12 import  mir.entity.*;
13 import  mir.misc.*;
14 import com.codestudio.sql.*;
15 import com.codestudio.util.*;
16
17
18 /**
19  * Diese Klasse implementiert die Zugriffsschicht auf die Datenbank.
20  * Alle Projektspezifischen Datenbankklassen erben von dieser Klasse.
21  * In den Unterklassen wird im Minimalfall nur die Tabelle angegeben.
22  * Im Konfigurationsfile findet sich eine Verweis auf den verwendeten
23  * Treiber, Host, User und Passwort, ueber den der Zugriff auf die
24  * Datenbank erfolgt.
25  *
26  * @author RK
27  * @version 16.7.1999
28  */
29 public class Database implements StorageObject {
30
31         protected String                    theTable;
32         protected String                    theCoreTable=null;
33         protected String                    thePKeyName="id";
34         protected int                       thePKeyType;
35         protected boolean                   evaluatedMetaData=false;
36         protected ArrayList                 metadataFields,metadataLabels,
37                                                                                                                                                         metadataNotNullFields;
38         protected int[]                     metadataTypes;
39         protected Class                     theEntityClass;
40         protected StorageObject             myselfDatabase;
41
42         /** @todo DatabaseCache will soon be replaced by
43          *  ObjectStore. Points to interweave Database with
44          *  ObjectStore are marked "todo: OS:" below */
45         protected DatabaseCache             cache;
46
47         protected SimpleList                popupCache=null;
48         protected boolean                   hasPopupCache = false;
49         protected SimpleHash                hashCache=null;
50         protected boolean                   hasTimestamp=true;
51         private       String                database_driver;
52         private       String                database_url;
53         private int                         defaultLimit;
54         protected DatabaseAdaptor           theAdaptor;
55         protected Logfile                   theLog;
56         protected static final SimpleHash   POPUP_EMTYLINE=new SimpleHash();
57
58         static {
59                 // always same object saves a little space
60                 POPUP_EMTYLINE.put("key", ""); POPUP_EMTYLINE.put("value", "--");
61
62                 /** @todo OS: init ObjectStore*/
63         }
64
65         /**
66          * Kontruktor bekommt den Filenamen des Konfigurationsfiles übergeben.
67          * Aus diesem file werden <code>Database.Logfile</code>,
68          * <code>Database.Username</code>,<code>Database.Password</code>,
69          * <code>Database.Host</code> und <code>Database.Adaptor</code>
70          * ausgelesen und ein Broker für die Verbindugen zur Datenbank
71          * erzeugt.
72          *
73          * @param   String confFilename Dateiname der Konfigurationsdatei
74          */
75         public Database() throws StorageObjectException {
76                 theLog = Logfile.getInstance(MirConfig.getProp("Home")+
77                                                                                                                                 MirConfig.getProp("Database.Logfile"));
78                 String theAdaptorName=MirConfig.getProp("Database.Adaptor");
79                 defaultLimit = Integer.parseInt(MirConfig.getProp("Database.Limit"));
80                 try {
81                         theEntityClass = Class.forName("mir.entity.GenericEntity");
82                         theAdaptor = (DatabaseAdaptor)Class.forName(theAdaptorName).newInstance();
83                 } catch (Exception e){
84                         theLog.printError("Error in Database() constructor with "+
85                                                                                                 theAdaptorName + " -- " +e.toString());
86                         throw new StorageObjectException("Error in Database() constructor with "
87                                                                                                                                                          +e.toString());
88                 }
89                 /*String database_username=MirConfig.getProp("Database.Username");
90                 String database_password=MirConfig.getProp("Database.Password");
91                 String database_host=MirConfig.getProp("Database.Host");
92                 try {
93                         database_driver=theAdaptor.getDriver();
94                         database_url=theAdaptor.getURL(database_username,database_password,
95                                                                                                                                                 database_host);
96                         theLog.printDebugInfo("adding Broker with: " +database_driver+":"+
97                                                                                                                 database_url  );
98                         MirConfig.addBroker(database_driver,database_url);
99                         //myBroker=MirConfig.getBroker();
100                 }*/
101         }
102
103         /**
104          * Liefert die Entity-Klasse zurück, in der eine Datenbankzeile gewrappt
105          * wird. Wird die Entity-Klasse durch die erbende Klasse nicht überschrieben,
106          * wird eine mir.entity.GenericEntity erzeugt.
107          *
108          * @return Class-Objekt der Entity
109          */
110         public java.lang.Class getEntityClass () {
111                 return  theEntityClass;
112         }
113
114         /**
115          * Liefert die Standardbeschränkung von select-Statements zurück, also
116          * wieviel Datensätze per Default selektiert werden.
117          *
118          * @return Standard-Anzahl der Datensätze
119          */
120         public int getLimit () {
121                 return  defaultLimit;
122         }
123
124         /**
125          * Liefert den Namen des Primary-Keys zurück. Wird die Variable nicht von
126          * der erbenden Klasse überschrieben, so ist der Wert <code>PKEY</code>
127          * @return Name des Primary-Keys
128          */
129         public String getIdName () {
130                 return  thePKeyName;
131         }
132
133         /**
134          * Liefert den Namen der Tabelle, auf das sich das Datenbankobjekt bezieht.
135          *
136          * @return Name der Tabelle
137          */
138         public String getTableName () {
139                 return  theTable;
140         }
141
142         /*
143          *   Dient dazu vererbte Tabellen bei objectrelationalen DBMS
144          *   zu speichern, wenn die id einer Tabelle in der parenttabelle verwaltet
145          *   wird.
146          *   @return liefert theCoreTabel als String zurueck, wenn gesetzt, sonst
147          *    the Table
148          */
149
150         public String getCoreTable(){
151                 if (theCoreTable!=null) return theCoreTable;
152                 else return theTable;
153         }
154
155         /**
156          * Liefert Feldtypen der Felder der Tabelle zurueck (s.a. java.sql.Types)
157          * @return int-Array mit den Typen der Felder
158          * @exception StorageObjectException
159          */
160         public int[] getTypes () throws StorageObjectException {
161                 if (metadataTypes == null)
162                         get_meta_data();
163                 return  metadataTypes;
164         }
165
166         /**
167          * Liefert eine Liste der Labels der Tabellenfelder
168          * @return ArrayListe mit Labeln
169          * @exception StorageObjectException
170          */
171         public ArrayList getLabels () throws StorageObjectException {
172                 if (metadataLabels == null)
173                         get_meta_data();
174                 return  metadataLabels;
175         }
176
177         /**
178          * Liefert eine Liste der Felder der Tabelle
179          * @return ArrayList mit Feldern
180          * @exception StorageObjectException
181          */
182         public ArrayList getFields () throws StorageObjectException {
183                 if (metadataFields == null)
184                         get_meta_data();
185                 return  metadataFields;
186         }
187
188
189         /*
190          *   Gets value out of ResultSet according to type and converts to String
191          *   @param inValue  Wert aus ResultSet.
192          *   @param aType  Datenbanktyp.
193          *   @return liefert den Wert als String zurueck. Wenn keine Umwandlung moeglich
194          *           dann /unsupported value/
195          */
196         private String getValueAsString (ResultSet rs, int valueIndex, int aType) throws StorageObjectException {
197                 String outValue = null;
198                 if (rs != null) {
199                         try {
200                                 switch (aType) {
201                                         case java.sql.Types.BIT:
202                                                 outValue = (rs.getBoolean(valueIndex) == true) ? "1" : "0";
203                                                 break;
204                                         case java.sql.Types.INTEGER:case java.sql.Types.SMALLINT:case java.sql.Types.TINYINT:case java.sql.Types.BIGINT:
205                                                 int out = rs.getInt(valueIndex);
206                                                 if (!rs.wasNull())
207                                                         outValue = new Integer(out).toString();
208                                                 break;
209                                         case java.sql.Types.NUMERIC:
210             /** @todo Numeric can be float or double depending upon
211              *  metadata.getScale() / especially with oracle */
212                                                 long outl = rs.getLong(valueIndex);
213                                                 if (!rs.wasNull())
214                                                         outValue = new Long(outl).toString();
215                                                 break;
216                                         case java.sql.Types.REAL:
217                                                 float tempf = rs.getFloat(valueIndex);
218                                                 if (!rs.wasNull()) {
219                                                         tempf *= 10;
220                                                         tempf += 0.5;
221                                                         int tempf_int = (int)tempf;
222                                                         tempf = (float)tempf_int;
223                                                         tempf /= 10;
224                                                         outValue = "" + tempf;
225                                                         outValue = outValue.replace('.', ',');
226                                                 }
227                                                 break;
228                                         case java.sql.Types.DOUBLE:
229                                                 double tempd = rs.getDouble(valueIndex);
230                                                 if (!rs.wasNull()) {
231                                                         tempd *= 10;
232                                                         tempd += 0.5;
233                                                         int tempd_int = (int)tempd;
234                                                         tempd = (double)tempd_int;
235                                                         tempd /= 10;
236                                                         outValue = "" + tempd;
237                                                         outValue = outValue.replace('.', ',');
238                                                 }
239                                                 break;
240                                         case java.sql.Types.CHAR:case java.sql.Types.VARCHAR:case java.sql.Types.LONGVARCHAR:
241                                                 outValue = rs.getString(valueIndex);
242                                                 //if (outValue != null)
243                                                         //outValue = StringUtil.encodeHtml(StringUtil.unquote(outValue));
244                                                 break;
245                                         case java.sql.Types.LONGVARBINARY:
246                                                 outValue = rs.getString(valueIndex);
247                                                 //if (outValue != null)
248                                                         //outValue = StringUtil.encodeHtml(StringUtil.unquote(outValue));
249                                                 break;
250                                         case java.sql.Types.TIMESTAMP:
251                                                 Timestamp timestamp = (rs.getTimestamp(valueIndex));
252                                                 if (!rs.wasNull()) {
253                                                         outValue = timestamp.toString();
254                                                 }
255                                                 break;
256                                         default:
257                                                 outValue = "<unsupported value>";
258                                                 theLog.printWarning("Unsupported Datatype: at " + valueIndex +
259                                                                 " (" + aType + ")");
260                                 }
261                         } catch (SQLException e) {
262                                 throw  new StorageObjectException("Could not get Value out of Resultset -- "
263                                                 + e.toString());
264                         }
265                 }
266                 return  outValue;
267         }
268
269         /*
270          *   select-Operator um einen Datensatz zu bekommen.
271          *   @param id Primaerschluessel des Datensatzes.
272          *   @return liefert EntityObject des gefundenen Datensatzes oder null.
273          */
274         public Entity selectById(String id)
275                 throws StorageObjectException {
276
277                 if (id==null||id.equals(""))
278                         throw new StorageObjectException("id war null");
279
280                 /** @todo OS: build StoreIdenfier and check ObjectStore for StoreIdentifier */
281                 if (cache != null && (cache.containsKey(id) > -1))
282                         return (Entity)cache.get(id);  // wenn cache gesetzt, evtl. kein roundtrip zur Datenbank
283
284                 Statement stmt=null;Connection con=getPooledCon();
285                 Entity returnEntity=null;
286                 try {
287                         ResultSet rs;
288                         /** @todo better prepared statement */
289                         String selectSql = "select * from " + theTable + " where " + thePKeyName + "=" + id;
290                         stmt = con.createStatement();
291                         rs = executeSql(stmt, selectSql);
292                         if (rs != null) {
293                                 if (evaluatedMetaData==false) evalMetaData(rs.getMetaData());
294                                 if (rs.next())
295                                         returnEntity = makeEntityFromResultSet(rs);
296                                 else theLog.printDebugInfo("Keine daten fuer id: " + id + "in Tabelle" + theTable);
297                                 rs.close();
298                         }
299                         else {
300                                 theLog.printDebugInfo("No Data for Id " + id + " in Table " + theTable);
301                         }
302                 }
303                 catch (SQLException sqe){
304                         throwSQLException(sqe,"selectById"); return null;
305                 }
306                 catch (NumberFormatException e) {
307                         theLog.printError("ID ist keine Zahl: " + id);
308                 }
309                 finally { freeConnection(con,stmt); }
310
311                 /** @todo OS: Entity should be saved in ostore */
312                 return returnEntity;
313         }
314
315
316         /**
317          *   select-Operator um Datensaetze zu bekommen, die key = value erfuellen.
318          *   @param key  Datenbankfeld der Bedingung.
319          *   @param value  Wert die der key anehmen muss.
320          *   @return EntityList mit den gematchten Entities
321          */
322         public EntityList selectByFieldValue(String aField, String aValue)
323                 throws StorageObjectException
324         {
325                 return selectByFieldValue(aField, aValue, 0);
326         }
327
328         /**
329          *   select-Operator um Datensaetze zu bekommen, die key = value erfuellen.
330          *   @param key  Datenbankfeld der Bedingung.
331          *   @param value  Wert die der key anehmen muss.
332          *   @param offset  Gibt an ab welchem Datensatz angezeigt werden soll.
333          *   @return EntityList mit den gematchten Entities
334          */
335         public EntityList selectByFieldValue(String aField, String aValue, int offset)
336                 throws StorageObjectException
337         {
338                 return selectByWhereClause(aField + "=" + aValue, offset);
339         }
340
341
342         /**
343          * select-Operator liefert eine EntityListe mit den gematchten Datensätzen zurück.
344          * Also offset wird der erste Datensatz genommen.
345          *
346          * @param wc where-Clause
347          * @return EntityList mit den gematchten Entities
348          * @exception StorageObjectException
349          */
350         public EntityList selectByWhereClause(String where)
351                 throws StorageObjectException
352         {
353                 return selectByWhereClause(where, 0);
354         }
355
356
357         /**
358          * select-Operator liefert eine EntityListe mit den gematchten Datensätzen zurück.
359          * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.
360          *
361          * @param wc where-Clause
362          * @param offset ab welchem Datensatz.
363          * @return EntityList mit den gematchten Entities
364          * @exception StorageObjectException
365          */
366         public EntityList selectByWhereClause(String whereClause, int offset)
367                 throws StorageObjectException
368         {
369                 return selectByWhereClause(whereClause, null, offset);
370         }
371
372
373         /**
374          * select-Operator liefert eine EntityListe mit den gematchten Datensätzen zurück.
375          * Also offset wird der erste Datensatz genommen.
376          * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.
377          *
378          * @param wc where-Clause
379          * @param ob orderBy-Clause
380          * @return EntityList mit den gematchten Entities
381          * @exception StorageObjectException
382          */
383
384         public EntityList selectByWhereClause(String where, String order)
385                 throws StorageObjectException {
386                 return selectByWhereClause(where, order, 0);
387         }
388
389
390         /**
391          * select-Operator liefert eine EntityListe mit den gematchten Datensätzen zurück.
392          * Als maximale Anzahl wird das Limit auf der Konfiguration genommen.
393          *
394          * @param wc where-Clause
395          * @param ob orderBy-Clause
396          * @param offset ab welchem Datensatz
397          * @return EntityList mit den gematchten Entities
398          * @exception StorageObjectException
399          */
400
401         public EntityList selectByWhereClause(String whereClause, String orderBy, int offset)
402                 throws StorageObjectException {
403                 return selectByWhereClause(whereClause, orderBy, offset, defaultLimit);
404         }
405
406
407         /**
408          * select-Operator liefert eine EntityListe mit den gematchten Datensätzen zurück.
409          * @param wc where-Clause
410          * @param ob orderBy-Clause
411          * @param offset ab welchem Datensatz
412          * @param limit wieviele Datensätze
413          * @return EntityList mit den gematchten Entities
414          * @exception StorageObjectException
415          */
416
417         public EntityList selectByWhereClause(String wc, String ob, int offset, int limit)
418                 throws StorageObjectException   {
419
420                 // local
421                 EntityList    theReturnList=null;
422                 Connection    con=null;
423                 Statement     stmt=null;
424                 ResultSet     rs;
425                 int           offsetCount = 0, count=0;
426
427                 // build sql-statement
428
429                 /** @todo count sql string should only be assembled if we really count
430                  *  see below at the end of method //rk */
431
432                 if (wc != null && wc.length() == 0) {
433                         wc = null;
434                 }
435                 StringBuffer countSql = new StringBuffer("select count(*) from ").append(theTable);
436                 StringBuffer selectSql = new StringBuffer("select * from ").append(theTable);
437                 if (wc != null) {
438                         selectSql.append(" where ").append(wc);
439                         countSql.append(" where ").append(wc);
440                 }
441                 if (ob != null && !(ob.length() == 0)) {
442                         selectSql.append(" order by ").append(ob);
443                 }
444                 if (theAdaptor.hasLimit()) {
445                         if (limit > -1 && offset > -1) {
446                                 selectSql.append(" limit ");
447                                 if (theAdaptor.reverseLimit()) {
448                                         selectSql.append(limit).append(",").append(offset);
449                                 }
450                                 else {
451                                         selectSql.append(offset).append(",").append(limit);
452                                 }
453                         }
454                 }
455
456                 /** @todo OS: make StoreIdentifier and ask Ostore*/
457
458                 // execute sql
459                 try {
460                         con = getPooledCon();
461                         stmt = con.createStatement();
462
463                         // selecting...
464                         rs = executeSql(stmt, selectSql.toString());
465                         if (rs != null) {
466                                 if (!evaluatedMetaData) evalMetaData(rs.getMetaData());
467
468                                 theReturnList = new EntityList();
469                                 Entity theResultEntity;
470                                 while (rs.next()) {
471                                         theResultEntity = makeEntityFromResultSet(rs);
472                                         theReturnList.add(theResultEntity);
473                                         offsetCount++;
474                                 }
475                                 rs.close();
476                         }
477
478                         // making entitylist infos
479                         if (!(theAdaptor.hasLimit())) count = offsetCount;
480
481                         if (theReturnList != null) {
482                                 // now we decide if we have to know an overall count...
483                                 count=offsetCount;
484                                 if (limit > -1 && offset > -1) {
485                                         if (offsetCount==limit) {
486                                                 /** @todo counting should be deffered to entitylist
487                                                  *  getSize() should be used */
488                                                 rs = executeSql(stmt, countSql.toString());
489                                                 if (rs != null) {
490                                                         if ( rs.next() ) count = rs.getInt(1);
491                                                         rs.close();
492                                                 }
493                                                 else theLog.printError("Could not count: " + countSql);
494                                         }
495                                 }
496                                 theReturnList.setCount(count);
497                                 theReturnList.setOffset(offset);
498                                 theReturnList.setWhere(wc);
499                                 theReturnList.setOrder(ob);
500                                 if (offset >= limit) {
501                                         theReturnList.setPrevBatch(offset - limit);
502                                 }
503                                 if (offset + offsetCount < count) {
504                                         theReturnList.setNextBatch(offset + limit);
505                                 }
506                         }
507                 }
508                 catch (SQLException sqe) {
509                         throwSQLException(sqe, "selectByWhereClause");
510                 }
511                 finally {
512                         freeConnection(con, stmt);
513                 }
514
515                 return  theReturnList;
516         }
517
518
519         /**
520          *  Bastelt aus einer Zeile der Datenbank ein EntityObjekt.
521          *
522          *  @param rs Das ResultSetObjekt.
523          *  @return Entity Die Entity.
524          */
525         private Entity makeEntityFromResultSet (ResultSet rs)
526                 throws StorageObjectException
527         {
528                 /** @todo OS: get Pkey from ResultSet and consult ObjectStore */
529                 HashMap theResultHash = new HashMap();
530                 String theResult = null;
531                 int theType;
532                 Entity returnEntity = null;
533                 try {
534                         int size = metadataFields.size();
535                         for (int i = 0; i < size; i++) {
536                                 // alle durchlaufen bis nix mehr da
537                                 theType = metadataTypes[i];
538                                 if (theType == java.sql.Types.LONGVARBINARY) {
539                                         InputStream us = rs.getAsciiStream(i + 1);
540                                         if (us != null) {
541                                                 InputStreamReader is = new InputStreamReader(us);
542                                                 char[] data = new char[32768];
543                                                 StringBuffer theResultString = new StringBuffer();
544                                                 int len;
545                                                 while ((len = is.read(data)) > 0) {
546                                                         theResultString.append(data, 0, len);
547                                                 }
548                                                 is.close();
549                                                 theResult = theResultString.toString();
550                                         }
551                                         else {
552                                                 theResult = null;
553                                         }
554                                 }
555                                 else {
556                                         theResult = getValueAsString(rs, (i + 1), theType);
557                                 }
558                                 if (theResult != null) {
559                                         theResultHash.put(metadataFields.get(i), theResult);
560                                 }
561                         }
562
563                         /** @todo why fetching all data (above) and then consulting the
564                          *  cache? we should fetch pkeyname first // rk  */
565                         if (cache != null && theResultHash.containsKey(thePKeyName) &&
566                                         (cache.containsKey((String)theResultHash.get(thePKeyName)) > -1)) {
567                                 returnEntity = (Entity)cache.get((String)theResultHash.get(thePKeyName));
568                         } else {
569                                 if (theEntityClass != null) {
570                                         returnEntity = (Entity)theEntityClass.newInstance();
571                                         returnEntity.setValues(theResultHash);
572                                         returnEntity.setStorage(myselfDatabase);
573                                         if (cache != null) {
574                                                 //theLog.printDebugInfo("CACHE: ( in) " + returnEntity.getId() + " :"+theTable);
575                                                 /** @todo put element into ObjectStore */
576                                                 cache.put(returnEntity.getId(), returnEntity);
577                                         }
578                                 } else {
579                                         throwStorageObjectException("Internal Error: theEntityClass not set!");
580                                 }
581                         }
582                 } catch (IllegalAccessException e) {
583                         throwStorageObjectException("Kein Zugriff! -- " + e.toString());
584                 } catch (IOException e) {
585                         throwStorageObjectException("IOException! -- " + e.toString());
586                 } catch (InstantiationException e) {
587                         throwStorageObjectException("Keine Instantiiierung! -- " + e.toString());
588                 } catch (SQLException sqe) {
589                         throwSQLException(sqe, "makeEntityFromResultSet");
590                         return  null;
591                 }
592                 return  returnEntity;
593         }
594
595         /**
596          * insert-Operator: fügt eine Entity in die Tabelle ein. Eine Spalte WEBDB_CREATE
597          * wird automatisch mit dem aktuellen Datum gefuellt.
598          *
599          * @param theEntity
600          * @return der Wert des Primary-keys der eingefügten Entity
601          */
602         public String insert (Entity theEntity) throws StorageObjectException {
603                 String returnId = null;
604                 Connection con = null;
605                 PreparedStatement pstmt = null;
606                 //cache
607                 invalidatePopupCache();
608                 /** @todo OS: if Entity is StorableObject, invalidate in ObjectStore
609                  *  careful: Entity has no id yet! */
610                 try {
611                         ArrayList streamedInput = theEntity.streamedInput();
612                         StringBuffer f = new StringBuffer();
613                         StringBuffer v = new StringBuffer();
614                         String aField, aValue;
615                         boolean firstField = true;
616                         // make sql-string
617                         for (int i = 0; i < getFields().size(); i++) {
618                                 aField = (String)getFields().get(i);
619                                 if (!aField.equals(thePKeyName)) {
620                                         aValue = null;
621                                         // sonderfaelle
622                                         if (aField.equals("webdb_create")) {
623                                                 aValue = "NOW()";
624                                         }
625                                         else {
626                                                 if (streamedInput != null && streamedInput.contains(aField)) {
627                                                         aValue = "?";
628                                                 }
629                                                 else {
630                                                         if (theEntity.hasValueForField(aField)) {
631                                                                 aValue = "'" + StringUtil.quote((String)theEntity.getValue(aField))
632                                                                                 + "'";
633                                                         }
634                                                 }
635                                         }
636                                         // wenn Wert gegeben, dann einbauen
637                                         if (aValue != null) {
638                                                 if (firstField == false) {
639                                                         f.append(",");
640                                                         v.append(",");
641                                                 }
642                                                 else {
643                                                         firstField = false;
644                                                 }
645                                                 f.append(aField);
646                                                 v.append(aValue);
647                                         }
648                                 }
649                         }         // end for
650                         // insert into db
651                         StringBuffer sqlBuf = new StringBuffer("insert into ").append(theTable).append("(").append(f).append(") values (").append(v).append(")");
652                         String sql = sqlBuf.toString();
653                         theLog.printInfo("INSERT: " + sql);
654                         con = getPooledCon();
655                         con.setAutoCommit(false);
656                         pstmt = con.prepareStatement(sql);
657                         if (streamedInput != null) {
658                                 for (int i = 0; i < streamedInput.size(); i++) {
659                                         String inputString = (String)theEntity.getValue((String)streamedInput.get(i));
660                                         pstmt.setBytes(i + 1, inputString.getBytes());
661                                 }
662                         }
663                         int ret = pstmt.executeUpdate();
664                         if(ret == 0){
665                                 //insert failed
666                                 return null;
667                         }
668                         pstmt = con.prepareStatement(theAdaptor.getLastInsertSQL((Database)myselfDatabase));
669                         ResultSet rs = pstmt.executeQuery();
670                         rs.next();
671                         returnId = rs.getString(1);
672                         theEntity.setId(returnId);
673                 } catch (SQLException sqe) {
674                         throwSQLException(sqe, "insert");
675                 } finally {
676                         try {
677                                 con.setAutoCommit(true);
678                         } catch (Exception e) {
679                                 ;
680                         }
681                         freeConnection(con, pstmt);
682                 }
683                 return  returnId;
684         }
685
686         /**
687          * update-Operator: aktualisiert eine Entity. Eine Spalte WEBDB_LASTCHANGE
688          * wird automatisch mit dem aktuellen Datum gefuellt.
689          *
690          * @param theEntity
691          */
692         public void update (Entity theEntity) throws StorageObjectException {
693                 Connection con = null;
694                 /** @todo this is stupid: why do we prepare statement, when we
695                  *  throw it away afterwards. should be regular statement
696                  *  update/insert could better be one routine called save()
697                  *  that chooses to either insert or update depending if we
698                  *  have a primary key in the entity. i don't know if we
699                  *  still need the streamed input fields. // rk  */
700                 /** @todo extension: check if Entity did change, otherwise we don't need */
701                 /** @todo OS: invalidate in Ostore if Entity is StorableObject */
702                 PreparedStatement pstmt = null;
703                 ArrayList streamedInput = theEntity.streamedInput();
704                 String id = theEntity.getId();
705                 String aField;
706                 StringBuffer fv = new StringBuffer();
707                 boolean firstField = true;
708                 //cache
709                 invalidatePopupCache();
710                 // build sql statement
711                 for (int i = 0; i < getFields().size(); i++) {
712                         aField = (String)metadataFields.get(i);
713                         // only normal cases
714                         if (!(aField.equals(thePKeyName) || aField.equals("webdb_create") ||
715                                         aField.equals("webdb_lastchange") || (streamedInput != null && streamedInput.contains(aField)))) {
716                                 if (theEntity.hasValueForField(aField)) {
717                                         if (firstField == false) {
718                                                 fv.append(", ");
719                                         }
720                                         else {
721                                                 firstField = false;
722                                         }
723                                         fv.append(aField).append("='").append(StringUtil.quote((String)theEntity.getValue(aField))).append("'");
724                                 }
725                         }
726                 }
727                 StringBuffer sql = new StringBuffer("update ").append(theTable).append(" set ").append(fv);
728                 // exceptions
729                 if (metadataFields.contains("webdb_lastchange")) {
730                         sql.append(",webdb_lastchange=NOW()");
731                 }
732                 if (streamedInput != null) {
733                         for (int i = 0; i < streamedInput.size(); i++) {
734                                 sql.append(",").append(streamedInput.get(i)).append("=?");
735                         }
736                 }
737                 sql.append(" where id=").append(id);
738                 theLog.printInfo("UPDATE: " + sql);
739                 // execute sql
740                 try {
741                         con = getPooledCon();
742                         con.setAutoCommit(false);
743                         pstmt = con.prepareStatement(sql.toString());
744                         if (streamedInput != null) {
745                                 for (int i = 0; i < streamedInput.size(); i++) {
746                                         String inputString = theEntity.getValue((String)streamedInput.get(i));
747                                         pstmt.setBytes(i + 1, inputString.getBytes());
748                                 }
749                         }
750                         pstmt.executeUpdate();
751                 } catch (SQLException sqe) {
752                         throwSQLException(sqe, "update");
753                 } finally {
754                         try {
755                                 con.setAutoCommit(true);
756                         } catch (Exception e) {
757                                 ;
758                         }
759                         freeConnection(con, pstmt);
760                 }
761         }
762
763         /*
764          *   delete-Operator
765          *   @param id des zu loeschenden Datensatzes
766          *   @return boolean liefert true zurueck, wenn loeschen erfolgreich war.
767          */
768         public boolean delete (String id) throws StorageObjectException {
769                 Statement stmt = null;
770                 Connection con = null;
771                 String sql;
772                 int res = 0;
773                 // loeschen des caches
774                 invalidatePopupCache();
775                 /** @todo OS: invalidate if StorableObject */
776                 /** @todo could be prepared Statement */
777                 sql = "delete from " + theTable + " where " + thePKeyName + "='" + id +
778                                 "'";
779                 theLog.printInfo("DELETE " + sql);
780                 try {
781                         con = getPooledCon();
782                         stmt = con.createStatement();
783                         res = stmt.executeUpdate(sql);
784                 } catch (SQLException sqe) {
785                         throwSQLException(sqe, "delete");
786                 } finally {
787                         freeConnection(con, stmt);
788                 }
789                 /** @todo should take place before we delete */
790                 if (cache != null) {
791                         theLog.printInfo("CACHE: deleted " + id);
792                         cache.remove(id);
793                 }
794                 return  (res > 0) ? true : false;
795         }
796
797         /* noch nicht implementiert.
798          * @return immer false
799          */
800         public boolean delete (EntityList theEntityList) {
801                 invalidatePopupCache();
802                 return  false;
803         }
804
805         /**
806          * Diese Methode sollte ueberschrieben werden, wenn fuer die abgeleitete Database-Klasse
807          * eine SimpleList mit Standard-Popupdaten erzeugt werden koennen soll.
808          * @return null
809          */
810         public SimpleList getPopupData () throws StorageObjectException {
811                 return  null;
812         }
813
814         /**
815          *  Holt Daten fuer Popups.
816          *  @param name  Name des Feldes.
817          *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.
818          *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.
819          */
820         public SimpleList getPopupData (String name, boolean hasNullValue)
821                 throws StorageObjectException {
822                 return  getPopupData(name, hasNullValue, null);
823         }
824
825         /**
826          *  Holt Daten fuer Popups.
827          *  @param name  Name des Feldes.
828          *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.
829          *  @param where  Schraenkt die Selektion der Datensaetze ein.
830          *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.
831          */
832         public SimpleList getPopupData (String name, boolean hasNullValue, String where) throws StorageObjectException {
833          return  getPopupData(name, hasNullValue, where, null);
834         }
835
836         /**
837          *  Holt Daten fuer Popups.
838          *  @param name  Name des Feldes.
839          *  @param hasNullValue  Wenn true wird eine leerer  Eintrag fuer die Popups erzeugt.
840          *  @param where  Schraenkt die Selektion der Datensaetze ein.
841          *  @param order  Gibt ein Feld als Sortierkriterium an.
842          *  @return SimpleList Gibt freemarker.template.SimpleList zurueck.
843          */
844         public SimpleList getPopupData (String name, boolean hasNullValue, String where, String order) throws StorageObjectException {
845                 // caching
846                 if (hasPopupCache && popupCache != null)
847                         return  popupCache;
848                 SimpleList simpleList = null;
849                 Connection con = null;
850                 Statement stmt = null;
851                 // build sql
852                 StringBuffer sql = new StringBuffer("select ").append(thePKeyName)
853                                                                                                                                                                 .append(",").append(name).append(" from ")
854                                                                                                                                                                 .append(theTable);
855                 if (where != null && !(where.length() == 0))
856                         sql.append(" where ").append(where);
857                 sql.append(" order by ");
858                 if (order != null && !(order.length() == 0))
859                         sql.append(order);
860                 else
861                         sql.append(name);
862                 // execute sql
863                 try {
864                         con = getPooledCon();
865                 } catch (Exception e) {
866                         throw new StorageObjectException(e.toString());
867                 }
868                 try {
869                         stmt = con.createStatement();
870                         ResultSet rs = executeSql(stmt, sql.toString());
871
872                         if (rs != null) {
873                                 if (!evaluatedMetaData) get_meta_data();
874                                 simpleList = new SimpleList();
875                                 // if popup has null-selector
876                                 if (hasNullValue) simpleList.add(POPUP_EMTYLINE);
877
878                                 SimpleHash popupDict;
879                                 while (rs.next()) {
880                                         popupDict = new SimpleHash();
881                                         popupDict.put("key", getValueAsString(rs, 1, thePKeyType));
882                                         popupDict.put("value", rs.getString(2));
883                                         simpleList.add(popupDict);
884                                 }
885                                 rs.close();
886                         }
887                 } catch (Exception e) {
888                         theLog.printError("getPopupData: "+e.toString());
889                         throw new StorageObjectException(e.toString());
890                 } finally {
891                         freeConnection(con, stmt);
892                 }
893
894                 if (hasPopupCache) popupCache = simpleList;
895                 return  simpleList;
896         }
897
898         /**
899          * Liefert alle Daten der Tabelle als SimpleHash zurueck. Dies wird verwandt,
900          * wenn in den Templates ein Lookup-Table benoetigt wird. Sollte nur bei kleinen
901          * Tabellen Verwendung finden.
902          * @return SimpleHash mit den Tabellezeilen.
903          */
904         public SimpleHash getHashData () {
905                 /** @todo dangerous! this should have a flag to be enabled, otherwise
906                  *  very big Hashes could be returned */
907                 if (hashCache == null) {
908                         try {
909                                 hashCache = HTMLTemplateProcessor.makeSimpleHash(selectByWhereClause("",
910                                                 -1));
911                         } catch (StorageObjectException e) {
912                                 theLog.printDebugInfo(e.toString());
913                         }
914                 }
915                 return  hashCache;
916         }
917
918         /* invalidates the popupCache
919          */
920         protected void invalidatePopupCache () {
921                 /** @todo  invalidates toooo much */
922                 popupCache = null;
923                 hashCache = null;
924         }
925
926         /**
927          * Diese Methode fuehrt den Sqlstring <i>sql</i> aus und timed im Logfile.
928          * @param stmt Statemnt
929          * @param sql Sql-String
930          * @return ResultSet
931          * @exception StorageObjectException
932          */
933         public ResultSet executeSql (Statement stmt, String sql)
934                 throws StorageObjectException, SQLException
935         {
936                 long startTime = System.currentTimeMillis();
937                 ResultSet rs;
938                 try {
939                         rs = stmt.executeQuery(sql);
940                         theLog.printInfo((System.currentTimeMillis() - startTime) + "ms. for: "
941                                 + sql);
942                 }
943                 catch (SQLException e)
944                 {
945                         theLog.printDebugInfo("Failed: " + (System.currentTimeMillis()
946                                                                                                                 - startTime) + "ms. for: "+ sql);
947                         throw e;
948                 }
949
950                 return  rs;
951         }
952
953         /**
954          * Fuehrt Statement stmt aus und liefert Resultset zurueck. Das SQL-Statment wird
955          * getimed und geloggt.
956          * @param stmt PreparedStatement mit der SQL-Anweisung
957          * @return Liefert ResultSet des Statements zurueck.
958          * @exception StorageObjectException, SQLException
959          */
960         public ResultSet executeSql (PreparedStatement stmt)
961                 throws StorageObjectException, SQLException {
962
963                 long startTime = (new java.util.Date()).getTime();
964                 ResultSet rs = stmt.executeQuery();
965                 theLog.printInfo((new java.util.Date().getTime() - startTime) + "ms.");
966                 return  rs;
967         }
968
969                 /**
970          * returns the number of rows in the table
971          */
972         public int getSize(String where)
973                 throws SQLException,StorageObjectException
974         {
975                 long  startTime = System.currentTimeMillis();
976                 String sql = "SELECT count(*) FROM "+ theTable + " where " + where;
977                 Connection con = null;
978                 Statement stmt = null;
979                 int result = 0;
980
981                 try {
982                         con = getPooledCon();
983                         stmt = con.createStatement();
984                         ResultSet rs = executeSql(stmt,sql);
985                         while(rs.next()){
986                                 result = rs.getInt(1);
987                         }
988                 } catch (SQLException e) {
989                         theLog.printError(e.toString());
990                 } finally {
991                         freeConnection(con,stmt);
992                 }
993                 //theLog.printInfo(theTable + " has "+ result +" rows where " + where);
994                 theLog.printInfo((System.currentTimeMillis() - startTime) + "ms. for: "
995                                                                                 + sql);
996                 return result;
997         }
998
999         public int executeUpdate(Statement stmt, String sql)
1000                 throws StorageObjectException, SQLException
1001         {
1002                 int rs;
1003                 long  startTime = (new java.util.Date()).getTime();
1004                 try
1005                 {
1006                         rs = stmt.executeUpdate(sql);
1007                         theLog.printInfo((new java.util.Date().getTime() - startTime) + "ms. for: "
1008                                                                                                 + sql);
1009                 }
1010                 catch (SQLException e)
1011                 {
1012                         theLog.printDebugInfo("Failed: " + (new java.util.Date().getTime()
1013                                                                                                                 - startTime) + "ms. for: "+ sql);
1014                         throw e;
1015                 }
1016                 return rs;
1017         }
1018
1019         public int executeUpdate(String sql)
1020                 throws StorageObjectException, SQLException
1021         {
1022                 int result=-1;
1023                 long  startTime = (new java.util.Date()).getTime();
1024                 Connection con=null;PreparedStatement pstmt=null;
1025                 try {
1026                         con=getPooledCon();
1027                         pstmt = con.prepareStatement(sql);
1028                         result = pstmt.executeUpdate();
1029                 }
1030                 catch (Exception e) {
1031                         theLog.printDebugInfo("settimage :: setImage gescheitert: "+e.toString());
1032                         throw new StorageObjectException("executeUpdate failed: "+e.toString());
1033                 }
1034                 finally { freeConnection(con,pstmt); }
1035                 theLog.printInfo((new java.util.Date().getTime() - startTime) + "ms. for: "
1036                                                                                 + sql);
1037                 return result;
1038         }
1039
1040         /**
1041          * Wertet ResultSetMetaData aus und setzt interne Daten entsprechend
1042          * @param md ResultSetMetaData
1043          * @exception StorageObjectException
1044          */
1045         private void evalMetaData (ResultSetMetaData md)
1046                 throws StorageObjectException {
1047
1048                 this.evaluatedMetaData = true;
1049                 this.metadataFields = new ArrayList();
1050                 this.metadataLabels = new ArrayList();
1051                 this.metadataNotNullFields = new ArrayList();
1052                 try {
1053                         int numFields = md.getColumnCount();
1054                         this.metadataTypes = new int[numFields];
1055                         String aField;
1056                         int aType;
1057                         for (int i = 1; i <= numFields; i++) {
1058                                 aField = md.getColumnName(i);
1059                                 metadataFields.add(aField);
1060                                 metadataLabels.add(md.getColumnLabel(i));
1061                                 aType = md.getColumnType(i);
1062                                 metadataTypes[i - 1] = aType;
1063                                 if (aField.equals(thePKeyName)) {
1064                                         thePKeyType = aType;
1065                                 }
1066                                 if (md.isNullable(i) == md.columnNullable) {
1067                                         metadataNotNullFields.add(aField);
1068                                 }
1069                         }
1070                 } catch (SQLException e) {
1071                         throwSQLException(e, "evalMetaData");
1072                 }
1073         }
1074
1075         /**
1076          *  Wertet die Metadaten eines Resultsets fuer eine Tabelle aus,
1077          *  um die alle Columns und Typen einer Tabelle zu ermitteln.
1078          */
1079         private void get_meta_data () throws StorageObjectException {
1080                 Connection con = null;
1081                 PreparedStatement pstmt = null;
1082                 String sql = "select * from " + theTable + " where 0=1";
1083                 try {
1084                         con = getPooledCon();
1085                         pstmt = con.prepareStatement(sql);
1086                         theLog.printInfo("METADATA: " + sql);
1087                         ResultSet rs = pstmt.executeQuery();
1088                         evalMetaData(rs.getMetaData());
1089                         rs.close();
1090                 } catch (SQLException e) {
1091                         throwSQLException(e, "get_meta_data");
1092                 } finally {
1093                         freeConnection(con, pstmt);
1094                 }
1095         }
1096
1097
1098         public Connection getPooledCon() throws StorageObjectException {
1099                 /* @todo , doublecheck but I'm pretty sure that this is unnecessary. -mh
1100                         try{
1101                         Class.forName("com.codestudio.sql.PoolMan").newInstance();
1102                 } catch (Exception e){
1103                         throw new StorageObjectException("Could not find the PoolMan Driver"
1104                                                                                                                                                                 +e.toString());
1105                 }*/
1106                 Connection con = null;
1107                 try{
1108                         con = SQLManager.getInstance().requestConnection();
1109                 } catch(SQLException e){
1110                         theLog.printError("could not connect to the database "+e.toString());
1111                         System.err.println("could not connect to the database "+e.toString());
1112                         throw new StorageObjectException("Could not connect to the database"+
1113                                                                                                                                                                 e.toString());
1114                 }
1115                 return con;
1116         }
1117
1118         public void freeConnection (Connection con, Statement stmt)
1119                 throws StorageObjectException {
1120                 SQLManager.getInstance().closeStatement(stmt);
1121                 SQLManager.getInstance().returnConnection(con);
1122         }
1123
1124         /**
1125          * Wertet SQLException aus und wirft dannach eine StorageObjectException
1126          * @param sqe SQLException
1127          * @param wo Funktonsname, in der die SQLException geworfen wurde
1128          * @exception StorageObjectException
1129          */
1130         protected void throwSQLException (SQLException sqe, String wo)
1131                 throws StorageObjectException {
1132                 String state = "";
1133                 String message = "";
1134                 int vendor = 0;
1135                 if (sqe != null) {
1136                         state = sqe.getSQLState();
1137                         message = sqe.getMessage();
1138                         vendor = sqe.getErrorCode();
1139                 }
1140                 theLog.printError(state + ": " + vendor + " : " + message + " Funktion: "
1141                                 + wo);
1142                 throw  new StorageObjectException((sqe == null) ? "undefined sql exception" :
1143                                 sqe.toString());
1144         }
1145
1146         protected void _throwStorageObjectException (Exception e, String wo)
1147                 throws StorageObjectException {
1148                 if (e != null) {
1149                                 theLog.printError(e.toString()+ wo);
1150                                 throw  new StorageObjectException(wo + e.toString());
1151                 } else {
1152                                 theLog.printError(wo);
1153                                 throw  new StorageObjectException(wo);
1154                 }
1155
1156         }
1157
1158         /**
1159          * Loggt Fehlermeldung mit dem Parameter Message und wirft dannach
1160          * eine StorageObjectException
1161          * @param message Nachricht mit dem Fehler
1162          * @exception StorageObjectException
1163          */
1164         void throwStorageObjectException (String message)
1165                 throws StorageObjectException {
1166                 _throwStorageObjectException(null, message);
1167         }
1168
1169 }
1170
1171
1172