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