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