merge of localization branch into HEAD. mh and zap
[mir.git] / source / mir / storage / Database.java
index 257c1c7..d82264c 100755 (executable)
@@ -7,12 +7,16 @@ import  java.sql.*;
 import  java.lang.*;
 import  java.io.*;
 import  java.util.*;
+import  java.text.SimpleDateFormat;
+import  java.text.ParseException;
 import  freemarker.template.*;
+import  com.codestudio.sql.*;
+import  com.codestudio.util.*;
+
 import  mir.storage.StorageObject;
+import  mir.storage.store.*;
 import  mir.entity.*;
 import  mir.misc.*;
-import com.codestudio.sql.*;
-import com.codestudio.util.*;
 
 
 /**
@@ -23,44 +27,72 @@ import com.codestudio.util.*;
  * Treiber, Host, User und Passwort, ueber den der Zugriff auf die
  * Datenbank erfolgt.
  *
- * @author RK
- * @version 16.7.1999
+ * @version $Revision: 1.22 $ $Date: 2002/08/25 19:00:09 $
+ * @author $Author: mh $
+ *
+ * $Log: Database.java,v $
+ * Revision 1.22  2002/08/25 19:00:09  mh
+ * merge of localization branch into HEAD. mh and zap
+ *
+ * Revision 1.21  2002/08/04 23:38:22  mh
+ * fix up the webdb_create update stuff
+ *
+ * Revision 1.20  2002/07/21 22:32:25  mh
+ * on insert, the "webdb_lastchange" field should get a value
+ *
+ * Revision 1.19  2002/06/29 15:44:46  mh
+ * make the webdb_create update be called webdb_create_update. it breaks things otherwise. a fixme case I know..
+ *
+ * Revision 1.18  2002/06/28 20:42:13  mh
+ * added necessary bits in templates and Database.java to make webdb_create modifiable. make the conversion from sql/Timestamp to String more robust
+ *
+ *
  */
 public class Database implements StorageObject {
 
        protected String                    theTable;
        protected String                    theCoreTable=null;
        protected String                    thePKeyName="id";
-       protected int                       thePKeyType;
+       protected int                       thePKeyType, thePKeyIndex;
        protected boolean                   evaluatedMetaData=false;
        protected ArrayList                 metadataFields,metadataLabels,
                                                                                                                                                        metadataNotNullFields;
        protected int[]                     metadataTypes;
        protected Class                     theEntityClass;
        protected StorageObject             myselfDatabase;
-
-       /** @todo DatabaseCache will soon be replaced by
-        *  ObjectStore. Points to interweave Database with
-        *  ObjectStore are marked "todo: OS:" below */
-       protected DatabaseCache             cache;
-
        protected SimpleList                popupCache=null;
        protected boolean                   hasPopupCache = false;
        protected SimpleHash                hashCache=null;
        protected boolean                   hasTimestamp=true;
-       private       String                database_driver;
-       private       String                database_url;
+       private String                      database_driver, database_url;
        private int                         defaultLimit;
        protected DatabaseAdaptor           theAdaptor;
        protected Logfile                   theLog;
-       protected static final SimpleHash   POPUP_EMTYLINE=new SimpleHash();
+       private static Class                GENERIC_ENTITY_CLASS=null,
+                                      STORABLE_OBJECT_ENTITY_CLASS=null;
+  private static SimpleHash           POPUP_EMTYLINE=new SimpleHash();
+  protected static final ObjectStore  o_store=ObjectStore.getInstance();
+  private SimpleDateFormat _dateFormatterOut = 
+                                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+  private SimpleDateFormat _dateFormatterIn = 
+                                    new SimpleDateFormat("yyyy-MM-dd HH:mm");
+  private Calendar _cal = new GregorianCalendar();
+
+  private static final int _millisPerHour = 60 * 60 * 1000;
+  private static final int _millisPerMinute = 60 * 1000;
 
        static {
                // always same object saves a little space
                POPUP_EMTYLINE.put("key", ""); POPUP_EMTYLINE.put("value", "--");
+    try {
+      GENERIC_ENTITY_CLASS = Class.forName("mir.entity.StorableObjectEntity");
+      STORABLE_OBJECT_ENTITY_CLASS = Class.forName("mir.entity.StorableObjectEntity");
+    }
+    catch (Exception e) {
+      System.err.println("FATAL: Database.java could not initialize" + e.toString());
+    }
+  }
 
-               /** @todo OS: init ObjectStore*/
-       }
 
        /**
         * Kontruktor bekommt den Filenamen des Konfigurationsfiles übergeben.
@@ -78,7 +110,7 @@ public class Database implements StorageObject {
                String theAdaptorName=MirConfig.getProp("Database.Adaptor");
                defaultLimit = Integer.parseInt(MirConfig.getProp("Database.Limit"));
                try {
-                       theEntityClass = Class.forName("mir.entity.GenericEntity");
+                       theEntityClass = GENERIC_ENTITY_CLASS;
                        theAdaptor = (DatabaseAdaptor)Class.forName(theAdaptorName).newInstance();
                } catch (Exception e){
                        theLog.printError("Error in Database() constructor with "+
@@ -245,13 +277,24 @@ public class Database implements StorageObject {
                                        case java.sql.Types.LONGVARBINARY:
                                                outValue = rs.getString(valueIndex);
                                                //if (outValue != null)
-                                                       //outValue = StringUtil.encodeHtml(StringUtil.unquote(outValue));
+                                               //outValue = StringUtil.encodeHtml(StringUtil.unquote(outValue));
                                                break;
                                        case java.sql.Types.TIMESTAMP:
-                                               Timestamp timestamp = (rs.getTimestamp(valueIndex));
-                                               if (!rs.wasNull()) {
-                                                       outValue = timestamp.toString();
-                                               }
+            // it's important to use Timestamp here as getting it
+            // as a string is undefined and is only there for debugging
+            // according to the API. we can make it a string through formatting.
+            // -mh
+                                         Timestamp timestamp = (rs.getTimestamp(valueIndex));
+            if(!rs.wasNull()) {
+              java.util.Date date = new java.util.Date(timestamp.getTime());
+              outValue = _dateFormatterOut.format(date);
+              _cal.setTime(date);
+              int offset = _cal.get(Calendar.ZONE_OFFSET)+
+                            _cal.get(Calendar.DST_OFFSET);
+              String tzOffset = StringUtil.zeroPaddingNumber(
+                                                     offset/_millisPerHour,2,2);
+              outValue = outValue+"+"+tzOffset;
+            }
                                                break;
                                        default:
                                                outValue = "<unsupported value>";
@@ -271,15 +314,21 @@ public class Database implements StorageObject {
         *   @param id Primaerschluessel des Datensatzes.
         *   @return liefert EntityObject des gefundenen Datensatzes oder null.
         */
-       public Entity selectById(String id)
-               throws StorageObjectException {
-
+       public Entity selectById(String id)     throws StorageObjectException
+  {
                if (id==null||id.equals(""))
                        throw new StorageObjectException("id war null");
 
-               /** @todo OS: build StoreIdenfier and check ObjectStore for StoreIdentifier */
-               if (cache != null && (cache.containsKey(id) > -1))
-                       return (Entity)cache.get(id);  // wenn cache gesetzt, evtl. kein roundtrip zur Datenbank
+    // ask object store for object
+    if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+      String uniqueId = id;
+      if ( theEntityClass.equals(StorableObjectEntity.class) )
+        uniqueId+="@"+theTable;
+      StoreIdentifier search_sid = new StoreIdentifier(theEntityClass, uniqueId);
+      theLog.printDebugInfo("CACHE: (dbg) looking for sid " + search_sid.toString());
+      Entity hit = (Entity)o_store.use(search_sid);
+      if ( hit!=null ) return hit;
+    }
 
                Statement stmt=null;Connection con=getPooledCon();
                Entity returnEntity=null;
@@ -415,12 +464,25 @@ public class Database implements StorageObject {
         */
 
        public EntityList selectByWhereClause(String wc, String ob, int offset, int limit)
-               throws StorageObjectException   {
+               throws StorageObjectException
+  {
+
+    // check o_store for entitylist
+    if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+      StoreIdentifier search_sid =
+        new StoreIdentifier( theEntityClass,
+                             StoreContainerType.STOC_TYPE_ENTITYLIST,
+                             StoreUtil.getEntityListUniqueIdentifierFor(theTable,wc,ob,offset,limit) );
+      EntityList hit = (EntityList)o_store.use(search_sid);
+      if ( hit!=null ) {
+        theLog.printDebugInfo("CACHE (hit): " + search_sid.toString());
+        return hit;
+      }
+    }
 
                // local
                EntityList    theReturnList=null;
-               Connection    con=null;
-               Statement     stmt=null;
+               Connection    con=null; Statement stmt=null;
                ResultSet     rs;
                int           offsetCount = 0, count=0;
 
@@ -453,8 +515,6 @@ public class Database implements StorageObject {
                        }
                }
 
-               /** @todo OS: make StoreIdentifier and ask Ostore*/
-
                // execute sql
                try {
                        con = getPooledCon();
@@ -497,20 +557,21 @@ public class Database implements StorageObject {
                                theReturnList.setOffset(offset);
                                theReturnList.setWhere(wc);
                                theReturnList.setOrder(ob);
-                               if (offset >= limit) {
+        theReturnList.setStorage(this);
+        theReturnList.setLimit(limit);
+                               if ( offset >= limit )
                                        theReturnList.setPrevBatch(offset - limit);
-                               }
-                               if (offset + offsetCount < count) {
+                               if ( offset+offsetCount < count )
                                        theReturnList.setNextBatch(offset + limit);
-                               }
+        if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+          StoreIdentifier sid=theReturnList.getStoreIdentifier();
+          theLog.printDebugInfo("CACHE (add): " + sid.toString());
+          o_store.add(sid);
+        }
                        }
                }
-               catch (SQLException sqe) {
-                       throwSQLException(sqe, "selectByWhereClause");
-               }
-               finally {
-                       freeConnection(con, stmt);
-               }
+               catch (SQLException sqe) { throwSQLException(sqe, "selectByWhereClause"); }
+               finally { freeConnection(con, stmt); }
 
                return  theReturnList;
        }
@@ -534,11 +595,11 @@ public class Database implements StorageObject {
                        int size = metadataFields.size();
                        for (int i = 0; i < size; i++) {
                                // alle durchlaufen bis nix mehr da
+
                                theType = metadataTypes[i];
                                if (theType == java.sql.Types.LONGVARBINARY) {
-                                       InputStream us = rs.getAsciiStream(i + 1);
-                                       if (us != null) {
-                                               InputStreamReader is = new InputStreamReader(us);
+                                       InputStreamReader is = (InputStreamReader)rs.getCharacterStream(i + 1);
+                                       if (is != null) {
                                                char[] data = new char[32768];
                                                StringBuffer theResultString = new StringBuffer();
                                                int len;
@@ -559,26 +620,17 @@ public class Database implements StorageObject {
                                        theResultHash.put(metadataFields.get(i), theResult);
                                }
                        }
-
-                       /** @todo why fetching all data (above) and then consulting the
-                        *  cache? we should fetch pkeyname first // rk  */
-                       if (cache != null && theResultHash.containsKey(thePKeyName) &&
-                                       (cache.containsKey((String)theResultHash.get(thePKeyName)) > -1)) {
-                               returnEntity = (Entity)cache.get((String)theResultHash.get(thePKeyName));
-                       } else {
-                               if (theEntityClass != null) {
-                                       returnEntity = (Entity)theEntityClass.newInstance();
-                                       returnEntity.setValues(theResultHash);
-                                       returnEntity.setStorage(myselfDatabase);
-                                       if (cache != null) {
-                                               //theLog.printDebugInfo("CACHE: ( in) " + returnEntity.getId() + " :"+theTable);
-                                               /** @todo put element into ObjectStore */
-                                               cache.put(returnEntity.getId(), returnEntity);
-                                       }
-                               } else {
-                                       throwStorageObjectException("Internal Error: theEntityClass not set!");
-                               }
-                       }
+      if (theEntityClass != null) {
+        returnEntity = (Entity)theEntityClass.newInstance();
+        returnEntity.setValues(theResultHash);
+        returnEntity.setStorage(myselfDatabase);
+        if ( returnEntity instanceof StorableObject ) {
+          theLog.printDebugInfo("CACHE: ( in) " + returnEntity.getId() + " :"+theTable);
+          o_store.add(((StorableObject)returnEntity).getStoreIdentifier());
+        }
+      } else {
+        throwStorageObjectException("Internal Error: theEntityClass not set!");
+      }
                } catch (IllegalAccessException e) {
                        throwStorageObjectException("Kein Zugriff! -- " + e.toString());
                } catch (IOException e) {
@@ -600,14 +652,21 @@ public class Database implements StorageObject {
         * @return der Wert des Primary-keys der eingefügten Entity
         */
        public String insert (Entity theEntity) throws StorageObjectException {
-               String returnId = null;
-               Connection con = null;
-               PreparedStatement pstmt = null;
                //cache
                invalidatePopupCache();
-               /** @todo OS: if Entity is StorableObject, invalidate in ObjectStore
-                *  careful: Entity has no id yet! */
-               try {
+
+    // invalidating all EntityLists corresponding with theEntityClass
+    if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+      StoreContainerType stoc_type =
+        StoreContainerType.valueOf( theEntityClass,
+                                    StoreContainerType.STOC_TYPE_ENTITYLIST);
+      o_store.invalidate(stoc_type);
+    }
+
+               String returnId = null;
+               Connection con = null; PreparedStatement pstmt = null;
+
+    try {
                        ArrayList streamedInput = theEntity.streamedInput();
                        StringBuffer f = new StringBuffer();
                        StringBuffer v = new StringBuffer();
@@ -619,7 +678,8 @@ public class Database implements StorageObject {
                                if (!aField.equals(thePKeyName)) {
                                        aValue = null;
                                        // sonderfaelle
-                                       if (aField.equals("webdb_create")) {
+                                       if (aField.equals("webdb_create") ||
+              aField.equals("webdb_lastchange")) {
                                                aValue = "NOW()";
                                        }
                                        else {
@@ -680,6 +740,7 @@ public class Database implements StorageObject {
                        }
                        freeConnection(con, pstmt);
                }
+    /** @todo store entity in o_store */
                return  returnId;
        }
 
@@ -689,17 +750,27 @@ public class Database implements StorageObject {
         *
         * @param theEntity
         */
-       public void update (Entity theEntity) throws StorageObjectException {
-               Connection con = null;
+       public void update (Entity theEntity) throws StorageObjectException
+  {
+               Connection con = null; PreparedStatement pstmt = null;
                /** @todo this is stupid: why do we prepare statement, when we
                 *  throw it away afterwards. should be regular statement
                 *  update/insert could better be one routine called save()
                 *  that chooses to either insert or update depending if we
                 *  have a primary key in the entity. i don't know if we
                 *  still need the streamed input fields. // rk  */
-               /** @todo extension: check if Entity did change, otherwise we don't need */
-               /** @todo OS: invalidate in Ostore if Entity is StorableObject */
-               PreparedStatement pstmt = null;
+
+               /** @todo extension: check if Entity did change, otherwise we don't need
+     *  the roundtrip to the database */
+
+               /** invalidating corresponding entitylists in o_store*/
+    if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+      StoreContainerType stoc_type =
+        StoreContainerType.valueOf( theEntityClass,
+                                    StoreContainerType.STOC_TYPE_ENTITYLIST);
+      o_store.invalidate(stoc_type);
+    }
+
                ArrayList streamedInput = theEntity.streamedInput();
                String id = theEntity.getId();
                String aField;
@@ -729,6 +800,27 @@ public class Database implements StorageObject {
                if (metadataFields.contains("webdb_lastchange")) {
                        sql.append(",webdb_lastchange=NOW()");
                }
+    // special case: the webdb_create requires the field in yyyy-mm-dd HH:mm
+    // format so anything extra will be ignored. -mh
+               if (metadataFields.contains("webdb_create") &&
+        theEntity.hasValueForField("webdb_create")) {
+      // minimum of 10 (yyyy-mm-dd)...
+      if (theEntity.getValue("webdb_create").length() >= 10) {
+        String dateString = theEntity.getValue("webdb_create");
+        // if only 10, then add 00:00 so it doesn't throw a ParseException
+        if (dateString.length() == 10)
+          dateString=dateString+" 00:00";
+
+        // TimeStamp stuff
+        try {
+          java.util.Date d = _dateFormatterIn.parse(dateString);
+          Timestamp tStamp = new Timestamp(d.getTime());
+          sql.append(",webdb_create='"+tStamp.toString()+"'");
+        } catch (ParseException e) {
+          throw new StorageObjectException(e.toString());
+        }
+      }
+               }
                if (streamedInput != null) {
                        for (int i = 0; i < streamedInput.size(); i++) {
                                sql.append(",").append(streamedInput.get(i)).append("=?");
@@ -766,31 +858,31 @@ public class Database implements StorageObject {
         *   @return boolean liefert true zurueck, wenn loeschen erfolgreich war.
         */
        public boolean delete (String id) throws StorageObjectException {
-               Statement stmt = null;
-               Connection con = null;
-               String sql;
-               int res = 0;
-               // loeschen des caches
+
                invalidatePopupCache();
-               /** @todo OS: invalidate if StorableObject */
+    // ostore send notification
+    if ( StoreUtil.implementsStorableObject(theEntityClass) ) {
+      String uniqueId = id;
+      if ( theEntityClass.equals(StorableObjectEntity.class) )
+        uniqueId+="@"+theTable;
+                       theLog.printInfo("CACHE: (del) " + id);
+                       StoreIdentifier search_sid =
+        new StoreIdentifier(theEntityClass, StoreContainerType.STOC_TYPE_ENTITY, uniqueId);
+      o_store.invalidate(search_sid);
+               }
+
                /** @todo could be prepared Statement */
-               sql = "delete from " + theTable + " where " + thePKeyName + "='" + id +
-                               "'";
+               Statement stmt = null; Connection con = null;
+               int res = 0;
+               String sql="delete from "+theTable+" where "+thePKeyName+"='"+id+"'";
                theLog.printInfo("DELETE " + sql);
                try {
-                       con = getPooledCon();
-                       stmt = con.createStatement();
+                       con = getPooledCon(); stmt = con.createStatement();
                        res = stmt.executeUpdate(sql);
-               } catch (SQLException sqe) {
-                       throwSQLException(sqe, "delete");
-               } finally {
-                       freeConnection(con, stmt);
-               }
-               /** @todo should take place before we delete */
-               if (cache != null) {
-                       theLog.printInfo("CACHE: deleted " + id);
-                       cache.remove(id);
                }
+    catch (SQLException sqe) { throwSQLException(sqe, "delete"); }
+    finally { freeConnection(con, stmt); }
+
                return  (res > 0) ? true : false;
        }
 
@@ -973,7 +1065,9 @@ public class Database implements StorageObject {
                throws SQLException,StorageObjectException
        {
                long  startTime = System.currentTimeMillis();
-               String sql = "SELECT count(*) FROM "+ theTable + " where " + where;
+               String sql = "SELECT Count(*) FROM "+ theTable;
+               if (where != null && !(where.length() == 0))
+                 sql = sql + " where " + where;
                Connection con = null;
                Statement stmt = null;
                int result = 0;
@@ -1061,7 +1155,7 @@ public class Database implements StorageObject {
                                aType = md.getColumnType(i);
                                metadataTypes[i - 1] = aType;
                                if (aField.equals(thePKeyName)) {
-                                       thePKeyType = aType;
+                                       thePKeyType = aType; thePKeyIndex = i;
                                }
                                if (md.isNullable(i) == md.columnNullable) {
                                        metadataNotNullFields.add(aField);