X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=source%2Fmir%2Fstorage%2FDatabase.java;h=f86f043d1209013cf82369e87446bf90f3163ac1;hb=42680c1f9fe3250bcbd0f9ed5d9dee6188333b15;hp=6b9cd35a7f14f625d2c0f83648e7a2ecb626feb6;hpb=b5dfdb12e72c15762c6a9d21a3d37db9ba97c92a;p=mir.git diff --git a/source/mir/storage/Database.java b/source/mir/storage/Database.java index 6b9cd35a..f86f043d 100755 --- a/source/mir/storage/Database.java +++ b/source/mir/storage/Database.java @@ -1,1459 +1,1185 @@ -/* - * Copyright (C) 2001, 2002 The Mir-coders group - * - * This file is part of Mir. - * - * Mir is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Mir is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Mir; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * In addition, as a special exception, The Mir-coders gives permission to link - * the code of this program with the com.oreilly.servlet library, any library - * licensed under the Apache Software License, The Sun (tm) Java Advanced - * Imaging library (JAI), The Sun JIMI library (or with modified versions of - * the above that use the same license as the above), and distribute linked - * combinations including the two. You must obey the GNU General Public - * License in all respects for all of the code used other than the above - * mentioned libraries. If you modify this file, you may extend this exception - * to your version of the file, but you are not obligated to do so. If you do - * not wish to do so, delete this exception statement from your version. - */ -package mir.storage; - -import java.io.IOException; -import java.io.InputStreamReader; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; - -import java.text.ParseException; -import java.text.SimpleDateFormat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.HashMap; - -import com.codestudio.util.SQLManager; - -import freemarker.template.SimpleHash; -import freemarker.template.SimpleList; - -import mir.config.MirPropertiesConfiguration; - -import mir.config.MirPropertiesConfiguration.PropertiesConfigExc; - -import mir.entity.Entity; -import mir.entity.EntityList; -import mir.entity.StorableObjectEntity; - -import mir.log.LoggerWrapper; - -import mir.misc.HTMLTemplateProcessor; -import mir.misc.StringUtil; - -import mir.storage.store.ObjectStore; -import mir.storage.store.StorableObject; -import mir.storage.store.StoreContainerType; -import mir.storage.store.StoreIdentifier; -import mir.storage.store.StoreUtil; - -import mir.util.JDBCStringRoutines; - - -/** - * Diese Klasse implementiert die Zugriffsschicht auf die Datenbank. - * Alle Projektspezifischen Datenbankklassen erben von dieser Klasse. - * In den Unterklassen wird im Minimalfall nur die Tabelle angegeben. - * Im Konfigurationsfile findet sich eine Verweis auf den verwendeten - * Treiber, Host, User und Passwort, ueber den der Zugriff auf die - * Datenbank erfolgt. - * - * @version $Id: Database.java,v 1.33 2003/02/20 16:05:33 zapata Exp $ - * @author rk - * - */ -public class Database implements StorageObject { - private static Class GENERIC_ENTITY_CLASS = mir.entity.StorableObjectEntity.class; - private static Class STORABLE_OBJECT_ENTITY_CLASS = mir.entity.StorableObjectEntity.class; - - - private static SimpleHash POPUP_EMPTYLINE = new SimpleHash(); - protected static final ObjectStore o_store = ObjectStore.getInstance(); - private static final int _millisPerHour = 60 * 60 * 1000; - private static final int _millisPerMinute = 60 * 1000; - - static { - // always same object saves a little space - POPUP_EMPTYLINE.put("key", ""); - POPUP_EMPTYLINE.put("value", "--"); - } - - protected LoggerWrapper logger; - protected MirPropertiesConfiguration configuration; - protected String theTable; - protected String theCoreTable = null; - protected String thePKeyName = "id"; - protected int thePKeyType; - protected int thePKeyIndex; - protected boolean evaluatedMetaData = false; - protected ArrayList metadataFields; - protected ArrayList metadataLabels; - protected ArrayList metadataNotNullFields; - protected int[] metadataTypes; - protected Class theEntityClass; - protected StorageObject myselfDatabase; - 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 int defaultLimit; - protected DatabaseAdaptor theAdaptor; - 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(); - - /** - * Kontruktor bekommt den Filenamen des Konfigurationsfiles ?bergeben. - * Aus diesem file werden Database.Logfile, - * Database.Username,Database.Password, - * Database.Host und Database.Adaptor - * ausgelesen und ein Broker f?r die Verbindugen zur Datenbank - * erzeugt. - * - * @param String confFilename Dateiname der Konfigurationsdatei - */ - public Database() throws StorageObjectFailure { - try { - configuration = MirPropertiesConfiguration.instance(); - } - catch (PropertiesConfigExc e) { - throw new StorageObjectFailure(e); - } - logger = new LoggerWrapper("Database"); - - String theAdaptorName = configuration.getString("Database.Adaptor"); - defaultLimit = Integer.parseInt(configuration.getString("Database.Limit")); - - try { - theEntityClass = GENERIC_ENTITY_CLASS; - theAdaptor = (DatabaseAdaptor) Class.forName(theAdaptorName).newInstance(); - } - catch (Throwable e) { - logger.error("Error in Database() constructor with " + theAdaptorName + " -- " + e.getMessage()); - throw new StorageObjectFailure("Error in Database() constructor.", e); - } - } - - /** - * Liefert die Entity-Klasse zur?ck, in der eine Datenbankzeile gewrappt - * wird. Wird die Entity-Klasse durch die erbende Klasse nicht ?berschrieben, - * wird eine mir.entity.GenericEntity erzeugt. - * - * @return Class-Objekt der Entity - */ - public java.lang.Class getEntityClass() { - return theEntityClass; - } - - /** - * Liefert die Standardbeschr?nkung von select-Statements zur?ck, also - * wieviel Datens?tze per Default selektiert werden. - * - * @return Standard-Anzahl der Datens?tze - */ - public int getLimit() { - return defaultLimit; - } - - /** - * Liefert den Namen des Primary-Keys zur?ck. Wird die Variable nicht von - * der erbenden Klasse ?berschrieben, so ist der Wert PKEY - * @return Name des Primary-Keys - */ - public String getIdName() { - return thePKeyName; - } - - /** - * Liefert den Namen der Tabelle, auf das sich das Datenbankobjekt bezieht. - * - * @return Name der Tabelle - */ - public String getTableName() { - return theTable; - } - - /* - * Dient dazu vererbte Tabellen bei objectrelationalen DBMS - * zu speichern, wenn die id einer Tabelle in der parenttabelle verwaltet - * wird. - * @return liefert theCoreTabel als String zurueck, wenn gesetzt, sonst - * the Table - */ - public String getCoreTable() { - if (theCoreTable != null) { - return theCoreTable; - } else { - return theTable; - } - } - - /** - * Liefert Feldtypen der Felder der Tabelle zurueck (s.a. java.sql.Types) - * @return int-Array mit den Typen der Felder - * @exception StorageObjectException - */ - public int[] getTypes() throws StorageObjectFailure { - if (metadataTypes == null) { - get_meta_data(); - } - - return metadataTypes; - } - - /** - * Liefert eine Liste der Labels der Tabellenfelder - * @return ArrayListe mit Labeln - * @exception StorageObjectException - */ - public List getLabels() throws StorageObjectFailure { - if (metadataLabels == null) { - get_meta_data(); - } - - return metadataLabels; - } - - /** - * Liefert eine Liste der Felder der Tabelle - * @return ArrayList mit Feldern - * @exception StorageObjectException - */ - public List getFields() throws StorageObjectFailure { - if (metadataFields == null) { - get_meta_data(); - } - - return metadataFields; - } - - /* - * Gets value out of ResultSet according to type and converts to String - * @param inValue Wert aus ResultSet. - * @param aType Datenbanktyp. - * @return liefert den Wert als String zurueck. Wenn keine Umwandlung moeglich - * dann /unsupported value/ - */ - private String getValueAsString(ResultSet rs, int valueIndex, int aType) - throws StorageObjectFailure { - String outValue = null; - - if (rs != null) { - try { - switch (aType) { - case java.sql.Types.BIT: - outValue = (rs.getBoolean(valueIndex) == true) ? "1" : "0"; - - break; - - case java.sql.Types.INTEGER: - case java.sql.Types.SMALLINT: - case java.sql.Types.TINYINT: - case java.sql.Types.BIGINT: - - int out = rs.getInt(valueIndex); - - if (!rs.wasNull()) { - outValue = new Integer(out).toString(); - } - - break; - - case java.sql.Types.NUMERIC: - - /** @todo Numeric can be float or double depending upon - * metadata.getScale() / especially with oracle */ - long outl = rs.getLong(valueIndex); - - if (!rs.wasNull()) { - outValue = new Long(outl).toString(); - } - - break; - - case java.sql.Types.REAL: - - float tempf = rs.getFloat(valueIndex); - - if (!rs.wasNull()) { - tempf *= 10; - tempf += 0.5; - - int tempf_int = (int) tempf; - tempf = (float) tempf_int; - tempf /= 10; - outValue = "" + tempf; - outValue = outValue.replace('.', ','); - } - - break; - - case java.sql.Types.DOUBLE: - - double tempd = rs.getDouble(valueIndex); - - if (!rs.wasNull()) { - tempd *= 10; - tempd += 0.5; - - int tempd_int = (int) tempd; - tempd = (double) tempd_int; - tempd /= 10; - outValue = "" + tempd; - outValue = outValue.replace('.', ','); - } - - break; - - case java.sql.Types.CHAR: - case java.sql.Types.VARCHAR: - case java.sql.Types.LONGVARCHAR: - outValue = rs.getString(valueIndex); - - break; - - case java.sql.Types.LONGVARBINARY: - outValue = rs.getString(valueIndex); - - break; - - case java.sql.Types.TIMESTAMP: - - // 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 = ""; - logger.warn( "Unsupported Datatype: at " + valueIndex + " (" + aType + ")"); - } - } catch (SQLException e) { - throw new StorageObjectFailure("Could not get Value out of Resultset -- ", - e); - } - } - - return outValue; - } - - /* - * select-Operator um einen Datensatz zu bekommen. - * @param id Primaerschluessel des Datensatzes. - * @return liefert EntityObject des gefundenen Datensatzes oder null. - */ - public Entity selectById(String id) throws StorageObjectExc { - if ((id == null) || id.equals("")) { - throw new StorageObjectExc("Database.selectById: Missing id"); - } - - // 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); - logger.debug("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; - - try { - ResultSet rs; - - /** @todo better prepared statement */ - String selectSql = - "select * from " + theTable + " where " + thePKeyName + "=" + id; - stmt = con.createStatement(); - rs = executeSql(stmt, selectSql); - - if (rs != null) { - if (evaluatedMetaData == false) { - evalMetaData(rs.getMetaData()); - } - - if (rs.next()) { - returnEntity = makeEntityFromResultSet(rs); - } - else { - logger.debug("No data for id: " + id + " in table " + theTable); - } - - rs.close(); - } - else { - logger.debug("No Data for Id " + id + " in Table " + theTable); - } - } - catch (SQLException sqe) { - throwSQLException(sqe, "selectById"); - return null; - } - catch (NumberFormatException e) { - logger.error("ID is no number: " + id); - } - finally { - freeConnection(con, stmt); - } - - return returnEntity; - } - - /** - * select-Operator um Datensaetze zu bekommen, die key = value erfuellen. - * @param key Datenbankfeld der Bedingung. - * @param value Wert die der key anehmen muss. - * @return EntityList mit den gematchten Entities - */ - public EntityList selectByFieldValue(String aField, String aValue) throws StorageObjectFailure { - return selectByFieldValue(aField, aValue, 0); - } - - /** - * select-Operator um Datensaetze zu bekommen, die key = value erfuellen. - * @param key Datenbankfeld der Bedingung. - * @param value Wert die der key anehmen muss. - * @param offset Gibt an ab welchem Datensatz angezeigt werden soll. - * @return EntityList mit den gematchten Entities - */ - public EntityList selectByFieldValue(String aField, String aValue, int offset) throws StorageObjectFailure { - return selectByWhereClause(aField + "=" + aValue, offset); - } - - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Also offset wird der erste Datensatz genommen. - * - * @param wc where-Clause - * @return EntityList mit den gematchten Entities - * @exception StorageObjectException - */ - public EntityList selectByWhereClause(String where) - throws StorageObjectFailure { - return selectByWhereClause(where, 0); - } - - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Als maximale Anzahl wird das Limit auf der Konfiguration genommen. - * - * @param wc where-Clause - * @param offset ab welchem Datensatz. - * @return EntityList mit den gematchten Entities - * @exception StorageObjectException - */ - public EntityList selectByWhereClause(String whereClause, int offset) - throws StorageObjectFailure { - return selectByWhereClause(whereClause, null, offset); - } - - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Also offset wird der erste Datensatz genommen. - * Als maximale Anzahl wird das Limit auf der Konfiguration genommen. - * - * @param wc where-Clause - * @param ob orderBy-Clause - * @return EntityList mit den gematchten Entities - * @exception StorageObjectException - */ - public EntityList selectByWhereClause(String where, String order) - throws StorageObjectFailure { - return selectByWhereClause(where, order, 0); - } - - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Als maximale Anzahl wird das Limit auf der Konfiguration genommen. - * - * @param wc where-Clause - * @param ob orderBy-Clause - * @param offset ab welchem Datensatz - * @return EntityList mit den gematchten Entities - * @exception StorageObjectException - */ - public EntityList selectByWhereClause(String whereClause, String orderBy, - int offset) throws StorageObjectFailure { - return selectByWhereClause(whereClause, orderBy, offset, defaultLimit); - } - - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * @param wc where-Clause - * @param ob orderBy-Clause - * @param offset ab welchem Datensatz - * @param limit wieviele Datens?tze - * @return EntityList mit den gematchten Entities - * @exception StorageObjectException - */ - public EntityList selectByWhereClause(String wc, String ob, int offset, - int limit) throws StorageObjectFailure { - // 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) { - logger.debug("CACHE (hit): " + search_sid.toString()); - - return hit; - } - } - - // local - EntityList theReturnList = null; - Connection con = null; - Statement stmt = null; - ResultSet rs; - int offsetCount = 0; - int count = 0; - - // build sql-statement - - /** @todo count sql string should only be assembled if we really count - * see below at the end of method //rk */ - if ((wc != null) && (wc.length() == 0)) { - wc = null; - } - - StringBuffer countSql = - new StringBuffer("select count(*) from ").append(theTable); - StringBuffer selectSql = - new StringBuffer("select * from ").append(theTable); - - if (wc != null) { - selectSql.append(" where ").append(wc); - countSql.append(" where ").append(wc); - } - - if ((ob != null) && !(ob.length() == 0)) { - selectSql.append(" order by ").append(ob); - } - - if (theAdaptor.hasLimit()) { - if ((limit > -1) && (offset > -1)) { - selectSql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset); - } - } - - // execute sql - try { - con = getPooledCon(); - stmt = con.createStatement(); - - // selecting... - rs = executeSql(stmt, selectSql.toString()); - - if (rs != null) { - if (!evaluatedMetaData) { - evalMetaData(rs.getMetaData()); - } - - theReturnList = new EntityList(); - - Entity theResultEntity; - - while (rs.next()) { - theResultEntity = makeEntityFromResultSet(rs); - theReturnList.add(theResultEntity); - offsetCount++; - } - - rs.close(); - } - - // making entitylist infos - if (!(theAdaptor.hasLimit())) { - count = offsetCount; - } - - if (theReturnList != null) { - // now we decide if we have to know an overall count... - count = offsetCount; - - if ((limit > -1) && (offset > -1)) { - if (offsetCount == limit) { - /** @todo counting should be deffered to entitylist - * getSize() should be used */ - rs = executeSql(stmt, countSql.toString()); - - if (rs != null) { - if (rs.next()) { - count = rs.getInt(1); - } - - rs.close(); - } - else { - logger.error("Could not count: " + countSql); - } - } - } - - theReturnList.setCount(count); - theReturnList.setOffset(offset); - theReturnList.setWhere(wc); - theReturnList.setOrder(ob); - theReturnList.setStorage(this); - theReturnList.setLimit(limit); - - if (offset >= limit) { - theReturnList.setPrevBatch(offset - limit); - } - - if ((offset + offsetCount) < count) { - theReturnList.setNextBatch(offset + limit); - } - - if (StoreUtil.implementsStorableObject(theEntityClass)) { - StoreIdentifier sid = theReturnList.getStoreIdentifier(); - logger.debug("CACHE (add): " + sid.toString()); - o_store.add(sid); - } - } - } catch (SQLException sqe) { - throwSQLException(sqe, "selectByWhereClause"); - } finally { - try { - if (con != null) { - freeConnection(con, stmt); - } - } catch (Throwable t) { - } - } - - return theReturnList; - } - - /** - * Bastelt aus einer Zeile der Datenbank ein EntityObjekt. - * - * @param rs Das ResultSetObjekt. - * @return Entity Die Entity. - */ - private Entity makeEntityFromResultSet(ResultSet rs) - throws StorageObjectFailure { - /** @todo OS: get Pkey from ResultSet and consult ObjectStore */ - HashMap theResultHash = new HashMap(); - String theResult = null; - int theType; - Entity returnEntity = null; - - try { - 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) { - InputStreamReader is = - (InputStreamReader) rs.getCharacterStream(i + 1); - - if (is != null) { - char[] data = new char[32768]; - StringBuffer theResultString = new StringBuffer(); - int len; - - while ((len = is.read(data)) > 0) { - theResultString.append(data, 0, len); - } - - is.close(); - theResult = theResultString.toString(); - } else { - theResult = null; - } - } else { - theResult = getValueAsString(rs, (i + 1), theType); - } - - if (theResult != null) { - theResultHash.put(metadataFields.get(i), theResult); - } - } - - if (theEntityClass != null) { - returnEntity = (Entity) theEntityClass.newInstance(); - returnEntity.setValues(theResultHash); - returnEntity.setStorage(myselfDatabase); - - if (returnEntity instanceof StorableObject) { - logger.debug("CACHE: ( in) " + returnEntity.getId() + " :" + theTable); - o_store.add(((StorableObject) returnEntity).getStoreIdentifier()); - } - } else { - throwStorageObjectException("Internal Error: theEntityClass not set!"); - } - } catch (IllegalAccessException e) { - throwStorageObjectException("No access! -- " + e.getMessage()); - } catch (IOException e) { - throwStorageObjectException("IOException! -- " + e.getMessage()); - } catch (InstantiationException e) { - throwStorageObjectException("No Instatiation! -- " + e.getMessage()); - } catch (SQLException sqe) { - throwSQLException(sqe, "makeEntityFromResultSet"); - - return null; - } - - return returnEntity; - } - - /** - * insert-Operator: f?gt eine Entity in die Tabelle ein. Eine Spalte WEBDB_CREATE - * wird automatisch mit dem aktuellen Datum gefuellt. - * - * @param theEntity - * @return der Wert des Primary-keys der eingef?gten Entity - */ - public String insert(Entity theEntity) throws StorageObjectFailure { - //cache - invalidatePopupCache(); - - // 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 { - List streamedInput = theEntity.streamedInput(); - StringBuffer f = new StringBuffer(); - StringBuffer v = new StringBuffer(); - String aField; - String aValue; - boolean firstField = true; - - // make sql-string - for (int i = 0; i < getFields().size(); i++) { - aField = (String) getFields().get(i); - - if (!aField.equals(thePKeyName)) { - aValue = null; - - // exceptions - if (aField.equals("webdb_create") || - aField.equals("webdb_lastchange")) { - aValue = "NOW()"; - } else { - if ((streamedInput != null) && streamedInput.contains(aField)) { - aValue = "?"; - } else { - if (theEntity.hasValueForField(aField)) { - aValue = - "'" + - JDBCStringRoutines.escapeStringLiteral((String) theEntity.getValue( - aField)) + "'"; - } - } - } - - // wenn Wert gegeben, dann einbauen - if (aValue != null) { - if (firstField == false) { - f.append(","); - v.append(","); - } - else { - firstField = false; - } - - f.append(aField); - v.append(aValue); - } - } - } - // end for - - // insert into db - StringBuffer sqlBuf = - new StringBuffer("insert into ").append(theTable).append("(").append(f) - .append(") values (").append(v).append(")"); - String sql = sqlBuf.toString(); - - //theLog.printInfo("INSERT: " + sql); - con = getPooledCon(); - con.setAutoCommit(false); - pstmt = con.prepareStatement(sql); - - if (streamedInput != null) { - for (int i = 0; i < streamedInput.size(); i++) { - String inputString = - (String) theEntity.getValue((String) streamedInput.get(i)); - pstmt.setBytes(i + 1, inputString.getBytes()); - } - } - - int ret = pstmt.executeUpdate(); - - if (ret == 0) { - //insert failed - return null; - } - - pstmt = - con.prepareStatement(theAdaptor.getLastInsertSQL( - (Database) myselfDatabase)); - - ResultSet rs = pstmt.executeQuery(); - rs.next(); - returnId = rs.getString(1); - theEntity.setId(returnId); - } - catch (SQLException sqe) { - throwSQLException(sqe, "insert"); - } - finally { - try { - con.setAutoCommit(true); - } - catch (Exception e) { - } - - freeConnection(con, pstmt); - } - - /** @todo store entity in o_store */ - return returnId; - } - - /** - * update-Operator: aktualisiert eine Entity. Eine Spalte WEBDB_LASTCHANGE - * wird automatisch mit dem aktuellen Datum gefuellt. - * - * @param theEntity - */ - public void update(Entity theEntity) throws StorageObjectFailure { - 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 - * 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); - } - - List streamedInput = theEntity.streamedInput(); - String id = theEntity.getId(); - String aField; - StringBuffer fv = new StringBuffer(); - boolean firstField = true; - - //cache - invalidatePopupCache(); - - // build sql statement - for (int i = 0; i < getFields().size(); i++) { - aField = (String) metadataFields.get(i); - - // only normal cases - if ( !(aField.equals(thePKeyName) || - aField.equals("webdb_create") || - aField.equals("webdb_lastchange") || - ((streamedInput != null) && streamedInput.contains(aField)))) { - if (theEntity.hasValueForField(aField)) { - if (firstField == false) { - fv.append(", "); - } - else { - firstField = false; - } - - fv.append(aField).append("='").append(JDBCStringRoutines.escapeStringLiteral((String) theEntity.getValue(aField))).append("'"); - - // fv.append(aField).append("='").append(StringUtil.quote((String)theEntity.getValue(aField))).append("'"); - } - } - } - - StringBuffer sql = - new StringBuffer("update ").append(theTable).append(" set ").append(fv); - - // exceptions - 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 StorageObjectFailure(e); - } - } - } - - if (streamedInput != null) { - for (int i = 0; i < streamedInput.size(); i++) { - sql.append(",").append(streamedInput.get(i)).append("=?"); - } - } - - sql.append(" where id=").append(id); - - //theLog.printInfo("UPDATE: " + sql); - // execute sql - try { - con = getPooledCon(); - con.setAutoCommit(false); - pstmt = con.prepareStatement(sql.toString()); - - if (streamedInput != null) { - for (int i = 0; i < streamedInput.size(); i++) { - String inputString = - theEntity.getValue((String) streamedInput.get(i)); - pstmt.setBytes(i + 1, inputString.getBytes()); - } - } - - pstmt.executeUpdate(); - } catch (SQLException sqe) { - throwSQLException(sqe, "update"); - } finally { - try { - con.setAutoCommit(true); - } catch (Exception e) { - ; - } - - freeConnection(con, pstmt); - } - } - - /* - * delete-Operator - * @param id des zu loeschenden Datensatzes - * @return boolean liefert true zurueck, wenn loeschen erfolgreich war. - */ - public boolean delete(String id) throws StorageObjectFailure { - invalidatePopupCache(); - - // ostore send notification - if (StoreUtil.implementsStorableObject(theEntityClass)) { - String uniqueId = id; - - if (theEntityClass.equals(StorableObjectEntity.class)) { - uniqueId += ("@" + theTable); - } - - logger.debug("CACHE: (del) " + id); - - StoreIdentifier search_sid = - new StoreIdentifier(theEntityClass, - StoreContainerType.STOC_TYPE_ENTITY, uniqueId); - o_store.invalidate(search_sid); - } - - /** @todo could be prepared Statement */ - 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(); - res = stmt.executeUpdate(sql); - } catch (SQLException sqe) { - throwSQLException(sqe, "delete"); - } finally { - freeConnection(con, stmt); - } - - return (res > 0) ? true : false; - } - - /* noch nicht implementiert. - * @return immer false - */ - public boolean delete(EntityList theEntityList) { - invalidatePopupCache(); - - return false; - } - - /** - * Diese Methode sollte ueberschrieben werden, wenn fuer die abgeleitete Database-Klasse - * eine SimpleList mit Standard-Popupdaten erzeugt werden koennen soll. - * @return null - */ - public SimpleList getPopupData() throws StorageObjectFailure { - return null; - } - - /** - * Holt Daten fuer Popups. - * @param name Name des Feldes. - * @param hasNullValue Wenn true wird eine leerer Eintrag fuer die Popups erzeugt. - * @return SimpleList Gibt freemarker.template.SimpleList zurueck. - */ - public SimpleList getPopupData(String name, boolean hasNullValue) - throws StorageObjectFailure { - return getPopupData(name, hasNullValue, null); - } - - /** - * Holt Daten fuer Popups. - * @param name Name des Feldes. - * @param hasNullValue Wenn true wird eine leerer Eintrag fuer die Popups erzeugt. - * @param where Schraenkt die Selektion der Datensaetze ein. - * @return SimpleList Gibt freemarker.template.SimpleList zurueck. - */ - public SimpleList getPopupData(String name, boolean hasNullValue, String where) - throws StorageObjectFailure { - return getPopupData(name, hasNullValue, where, null); - } - - /** - * Holt Daten fuer Popups. - * @param name Name des Feldes. - * @param hasNullValue Wenn true wird eine leerer Eintrag fuer die Popups erzeugt. - * @param where Schraenkt die Selektion der Datensaetze ein. - * @param order Gibt ein Feld als Sortierkriterium an. - * @return SimpleList Gibt freemarker.template.SimpleList zurueck. - */ - public SimpleList getPopupData(String name, boolean hasNullValue, - String where, String order) throws StorageObjectFailure { - // caching - if (hasPopupCache && (popupCache != null)) { - return popupCache; - } - - SimpleList simpleList = null; - Connection con = null; - Statement stmt = null; - - // build sql - StringBuffer sql = - new StringBuffer("select ").append(thePKeyName).append(",").append(name) - .append(" from ").append(theTable); - - if ((where != null) && !(where.length() == 0)) { - sql.append(" where ").append(where); - } - - sql.append(" order by "); - - if ((order != null) && !(order.length() == 0)) { - sql.append(order); - } else { - sql.append(name); - } - - // execute sql - try { - con = getPooledCon(); - } catch (Exception e) { - throw new StorageObjectFailure(e); - } - - try { - stmt = con.createStatement(); - - ResultSet rs = executeSql(stmt, sql.toString()); - - if (rs != null) { - if (!evaluatedMetaData) { - get_meta_data(); - } - - simpleList = new SimpleList(); - - // if popup has null-selector - if (hasNullValue) { - simpleList.add(POPUP_EMPTYLINE); - } - - SimpleHash popupDict; - - while (rs.next()) { - popupDict = new SimpleHash(); - popupDict.put("key", getValueAsString(rs, 1, thePKeyType)); - popupDict.put("value", rs.getString(2)); - simpleList.add(popupDict); - } - - rs.close(); - } - } - catch (Exception e) { - logger.error("getPopupData: " + e.getMessage()); - throw new StorageObjectFailure(e); - } finally { - freeConnection(con, stmt); - } - - if (hasPopupCache) { - popupCache = simpleList; - } - - return simpleList; - } - - /** - * Liefert alle Daten der Tabelle als SimpleHash zurueck. Dies wird verwandt, - * wenn in den Templates ein Lookup-Table benoetigt wird. Sollte nur bei kleinen - * Tabellen Verwendung finden. - * @return SimpleHash mit den Tabellezeilen. - */ - public SimpleHash getHashData() { - /** @todo dangerous! this should have a flag to be enabled, otherwise - * very big Hashes could be returned */ - if (hashCache == null) { - try { - hashCache = - HTMLTemplateProcessor.makeSimpleHash(selectByWhereClause("", -1)); - } - catch (StorageObjectFailure e) { - logger.debug(e.getMessage()); - } - } - - return hashCache; - } - - /* invalidates the popupCache - */ - protected void invalidatePopupCache() { - /** @todo invalidates toooo much */ - popupCache = null; - hashCache = null; - } - - /** - * Diese Methode fuehrt den Sqlstring sql aus und timed im Logfile. - * @param stmt Statemnt - * @param sql Sql-String - * @return ResultSet - * @exception StorageObjectException - */ - public ResultSet executeSql(Statement stmt, String sql) - throws StorageObjectFailure, SQLException { - ResultSet rs; - long startTime = System.currentTimeMillis(); - - try { - rs = stmt.executeQuery(sql); - - logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql); - } - catch (SQLException e) { - logger.error(e.getMessage() +"\n" + (System.currentTimeMillis() - startTime) + "ms. for: " + sql); - throw e; - } - - return rs; - } - - /** - * returns the number of rows in the table - */ - public int getSize(String where) throws SQLException, StorageObjectFailure { - long startTime = System.currentTimeMillis(); - 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; - - try { - con = getPooledCon(); - stmt = con.createStatement(); - - ResultSet rs = executeSql(stmt, sql); - - while (rs.next()) { - result = rs.getInt(1); - } - } - catch (SQLException e) { - logger.error("Database.getSize: " + e.getMessage()); - } - finally { - freeConnection(con, stmt); - } - - //theLog.printInfo(theTable + " has "+ result +" rows where " + where); - logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql); - - return result; - } - - public int executeUpdate(Statement stmt, String sql) - throws StorageObjectFailure, SQLException { - int rs; - long startTime = System.currentTimeMillis(); - - try { - rs = stmt.executeUpdate(sql); - - logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql); - } - catch (SQLException e) { - logger.debug("Failed: " + (System.currentTimeMillis() - startTime) + "ms. for: " + sql); - throw e; - } - - return rs; - } - - public int executeUpdate(String sql) - throws StorageObjectFailure, SQLException { - int result = -1; - long startTime = System.currentTimeMillis(); - Connection con = null; - PreparedStatement pstmt = null; - - try { - con = getPooledCon(); - pstmt = con.prepareStatement(sql); - result = pstmt.executeUpdate(); - } - catch (Throwable e) { - logger.error("Database.executeUpdate(" + sql + "): " + e.getMessage()); - throw new StorageObjectFailure("Database.executeUpdate(" + sql + "): " + e.getMessage(), e); - } - finally { - freeConnection(con, pstmt); - } - - logger.debug((System.currentTimeMillis() - startTime) + "ms. for: " + sql); - return result; - } - - /** - * Wertet ResultSetMetaData aus und setzt interne Daten entsprechend - * @param md ResultSetMetaData - * @exception StorageObjectException - */ - private void evalMetaData(ResultSetMetaData md) throws StorageObjectFailure { - this.evaluatedMetaData = true; - this.metadataFields = new ArrayList(); - this.metadataLabels = new ArrayList(); - this.metadataNotNullFields = new ArrayList(); - - try { - int numFields = md.getColumnCount(); - this.metadataTypes = new int[numFields]; - - String aField; - int aType; - - for (int i = 1; i <= numFields; i++) { - aField = md.getColumnName(i); - metadataFields.add(aField); - metadataLabels.add(md.getColumnLabel(i)); - aType = md.getColumnType(i); - metadataTypes[i - 1] = aType; - - if (aField.equals(thePKeyName)) { - thePKeyType = aType; - thePKeyIndex = i; - } - - if (md.isNullable(i) == ResultSetMetaData.columnNullable) { - metadataNotNullFields.add(aField); - } - } - } - catch (SQLException e) { - throwSQLException(e, "evalMetaData"); - } - } - - /** - * Wertet die Metadaten eines Resultsets fuer eine Tabelle aus, - * um die alle Columns und Typen einer Tabelle zu ermitteln. - */ - private void get_meta_data() throws StorageObjectFailure { - Connection con = null; - PreparedStatement pstmt = null; - String sql = "select * from " + theTable + " where 0=1"; - - try { - con = getPooledCon(); - pstmt = con.prepareStatement(sql); - - logger.debug("METADATA: " + sql); - ResultSet rs = pstmt.executeQuery(); - evalMetaData(rs.getMetaData()); - rs.close(); - } - catch (SQLException e) { - throwSQLException(e, "get_meta_data"); - } - finally { - freeConnection(con, pstmt); - } - } - - public Connection getPooledCon() throws StorageObjectFailure { - Connection con = null; - - try { - con = SQLManager.getInstance().requestConnection(); - } - catch (SQLException e) { - logger.error("could not connect to the database " + e.getMessage()); - System.err.println("could not connect to the database " + e.getMessage()); - throw new StorageObjectFailure("Could not connect to the database", e); - } - - return con; - } - - public void freeConnection(Connection con, Statement stmt) - throws StorageObjectFailure { - SQLManager.closeStatement(stmt); - SQLManager.getInstance().returnConnection(con); - } - - /** - * Wertet SQLException aus und wirft dannach eine StorageObjectException - * @param sqe SQLException - * @param wo Funktonsname, in der die SQLException geworfen wurde - * @exception StorageObjectException - */ - protected void throwSQLException(SQLException sqe, String aFunction) - throws StorageObjectFailure { - String state = ""; - String message = ""; - int vendor = 0; - - if (sqe != null) { - state = sqe.getSQLState(); - message = sqe.getMessage(); - vendor = sqe.getErrorCode(); - } - - String information = - "SQL Error: " + - "state= " + state + - ", vendor= " + vendor + - ", message=" + message + - ", function= " + aFunction; - - logger.error(information); - - throw new StorageObjectFailure(information, sqe); - } - - protected void _throwStorageObjectException(Exception e, String aFunction) - throws StorageObjectFailure { - - if (e != null) { - logger.error(e.getMessage() + aFunction); - throw new StorageObjectFailure(aFunction, e); - } - } - - /** - * Loggt Fehlermeldung mit dem Parameter Message und wirft dannach - * eine StorageObjectException - * @param message Nachricht mit dem Fehler - * @exception StorageObjectException - */ - void throwStorageObjectException(String aMessage) throws StorageObjectFailure { - logger.error(aMessage); - throw new StorageObjectFailure(aMessage, null); - } -} +/* + * Copyright (C) 2001-2006 The Mir-coders group + * + * This file is part of Mir. + * + * Mir is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Mir is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Mir; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, The Mir-coders gives permission to link + * the code of this program with any library licensed under the Apache Software License, + * and distribute linked combinations including the two. You must obey the + * GNU General Public License in all respects for all of the code used other than + * the above mentioned libraries. If you modify this file, you may extend this + * exception to your version of the file, but you are not obligated to do so. + * If you do not wish to do so, delete this exception statement from your version. + */ +package mir.storage; + +import mir.config.MirPropertiesConfiguration; +import mir.entity.AbstractEntity; +import mir.entity.Entity; +import mir.entity.EntityList; +import mir.entity.StorableObjectEntity; +import mir.log.LoggerWrapper; +import mir.storage.store.*; +import mir.util.JDBCStringRoutines; +import mir.util.StreamCopier; +import mircoders.global.MirGlobal; +import org.apache.commons.dbcp.DelegatingConnection; +import org.postgresql.PGConnection; +import org.postgresql.largeobject.LargeObject; +import org.postgresql.largeobject.LargeObjectManager; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Implements database access. + * + * @version $Id: Database.java,v 1.44.2.37 2006/12/25 20:10:22 zapata Exp $ + * @author rk + * @author Zapata + * + */ +public class Database { + private static final int DEFAULT_LIMIT = 20; + private static final Class GENERIC_ENTITY_CLASS = StorableObjectEntity.class; + protected static final ObjectStore o_store = ObjectStore.getInstance(); + + protected LoggerWrapper logger; + + protected String mainTable; + protected String primaryKeyField = "id"; + + private List fieldNames; + private int[] fieldTypes; + private Map fieldNameToType; + + protected Class entityClass; + + // + private Set binaryFields; + + private TimeZone timezone; + private SimpleDateFormat userInputDateFormat; + + public Database() throws DatabaseFailure { + MirPropertiesConfiguration configuration = MirPropertiesConfiguration.instance(); + logger = new LoggerWrapper("Database"); + timezone = TimeZone.getTimeZone(configuration.getString("Mir.DefaultTimezone")); + + userInputDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + userInputDateFormat.setTimeZone(timezone); + + binaryFields = new HashSet(); + + String adapterName = configuration.getString("Database.Adaptor"); + + try { + entityClass = GENERIC_ENTITY_CLASS; + } + catch (Throwable e) { + logger.error("Error in Database() constructor with " + adapterName + " -- " + e.getMessage()); + throw new DatabaseFailure("Error in Database() constructor.", e); + } + } + + public Class getEntityClass() { + return entityClass; + } + + public Entity createNewEntity() throws DatabaseFailure { + try { + AbstractEntity result = (AbstractEntity) entityClass.newInstance(); + result.setStorage(this); + + return result; + } + catch (Throwable t) { + throw new DatabaseFailure(t); + } + } + + public String getIdFieldName() { + return primaryKeyField; + } + + public String getTableName() { + return mainTable; + } + + /** + * Returns a list of field names for this Database + */ + public List getFieldNames() throws DatabaseFailure { + if (fieldNames == null) { + acquireMetaData(); + } + + return fieldNames; + } + + public boolean hasField(String aFieldName) { + return getFieldNames().contains(aFieldName); + } + + /** + * Gets value out of ResultSet according to type and converts to String + * + * @param aResultSet ResultSet. + * @param aType a type from java.sql.Types.* + * @param aFieldIndex index in ResultSet + * @return returns the value as String. If no conversion is possible + * /unsupported value/ is returned + */ + private String getValueAsString(ResultSet aResultSet, int aFieldIndex, int aType) + throws DatabaseFailure { + String outValue = null; + + if (aResultSet != null) { + try { + switch (aType) { + case java.sql.Types.BIT: + outValue = (aResultSet.getBoolean(aFieldIndex) == true) ? "1" : "0"; + + break; + + case java.sql.Types.INTEGER: + case java.sql.Types.SMALLINT: + case java.sql.Types.TINYINT: + case java.sql.Types.BIGINT: + + int out = aResultSet.getInt(aFieldIndex); + + if (!aResultSet.wasNull()) { + outValue = new Integer(out).toString(); + } + + break; + + case java.sql.Types.NUMERIC: + long outl = aResultSet.getLong(aFieldIndex); + + if (!aResultSet.wasNull()) { + outValue = new Long(outl).toString(); + } + + break; + + case java.sql.Types.REAL: + + float tempf = aResultSet.getFloat(aFieldIndex); + + if (!aResultSet.wasNull()) { + tempf *= 10; + tempf += 0.5; + + int tempf_int = (int) tempf; + tempf = (float) tempf_int; + tempf /= 10; + outValue = "" + tempf; + outValue = outValue.replace('.', ','); + } + + break; + + case java.sql.Types.DOUBLE: + + double tempd = aResultSet.getDouble(aFieldIndex); + + if (!aResultSet.wasNull()) { + tempd *= 10; + tempd += 0.5; + + int tempd_int = (int) tempd; + tempd = (double) tempd_int; + tempd /= 10; + outValue = "" + tempd; + outValue = outValue.replace('.', ','); + } + + break; + + case java.sql.Types.CHAR: + case java.sql.Types.VARCHAR: + case java.sql.Types.LONGVARCHAR: + outValue = aResultSet.getString(aFieldIndex); + + break; + + case java.sql.Types.LONGVARBINARY: + outValue = aResultSet.getString(aFieldIndex); + + break; + + case java.sql.Types.TIMESTAMP: + + // 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 = (aResultSet.getTimestamp(aFieldIndex)); + + if (!aResultSet.wasNull()) { + java.util.Date date = new java.util.Date(timestamp.getTime()); + outValue = DatabaseHelper.convertDateToInternalRepresenation(date); + } + + break; + + default: + outValue = ""; + logger.warn("Unsupported Datatype: at " + aFieldIndex + " (" + aType + ")"); + } + } + catch (SQLException e) { + throw new DatabaseFailure("Could not get Value out of Resultset -- ", + e); + } + } + + return outValue; + } + + /** + * Return an entity specified by id, or null if no such + * entity exists. + */ + public Entity selectById(String anId) throws DatabaseExc { + if ((anId == null) || anId.equals("")) { + throw new DatabaseExc("Database.selectById: Missing id"); + } + + // ask object store for object + if (StoreUtil.extendsStorableEntity(entityClass)) { + String uniqueId = anId; + + if (entityClass.equals(StorableObjectEntity.class)) { + uniqueId += ("@" + mainTable); + } + + StoreIdentifier search_sid = new StoreIdentifier(entityClass, uniqueId); + logger.debug("CACHE: (dbg) looking for sid " + search_sid.toString()); + + Entity hit = (Entity) o_store.use(search_sid); + + if (hit != null) { + return hit; + } + } + + Connection con = obtainConnection(); + Entity returnEntity = null; + PreparedStatement statement = null; + + try { + ResultSet rs; + String query = "select * from " + mainTable + " where " + primaryKeyField + " = ?"; + + statement = con.prepareStatement(query); + statement.setString(1, anId); + + logQueryBefore(query); + + long startTime = System.currentTimeMillis(); + try { + rs = statement.executeQuery(); + + logQueryAfter(query, (System.currentTimeMillis() - startTime)); + } + catch (SQLException e) { + logQueryError(query, (System.currentTimeMillis() - startTime), e); + throw e; + } + + if (rs != null) { + if (rs.next()) { + returnEntity = makeEntityFromResultSet(rs); + } + else { + logger.warn("No data for id: " + anId + " in table " + mainTable); + } + + rs.close(); + } + else { + logger.warn("No Data for Id " + anId + " in Table " + mainTable); + } + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + finally { + freeConnection(con, statement); + } + + return returnEntity; + } + + public EntityList selectByWhereClauseWithExtraTables(String mainTablePrefix, List extraTables, String aWhereClause) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause( mainTablePrefix, extraTables, aWhereClause, "", 0, DEFAULT_LIMIT); + } + + public EntityList selectByFieldValue(String aField, String aValue) throws DatabaseExc, DatabaseFailure { + return selectByFieldValue(aField, aValue, 0); + } + + public EntityList selectByFieldValue(String aField, String aValue, int offset) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(aField + "='" + JDBCStringRoutines.escapeStringLiteral(aValue)+"'", offset); + } + + public EntityList selectByWhereClause(String where) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(where, 0); + } + + public EntityList selectByWhereClause(String whereClause, int offset) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(whereClause, null, offset); + } + + public EntityList selectByWhereClause(String mainTablePrefix, List extraTables, String where, String order) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(mainTablePrefix, extraTables, where, order, 0, DEFAULT_LIMIT); + } + + public EntityList selectByWhereClause(String whereClause, String orderBy, int offset) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(whereClause, orderBy, offset, DEFAULT_LIMIT); + } + + public EntityList selectByWhereClause(String aWhereClause, String anOrderByClause, + int offset, int limit) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause("", null, aWhereClause, anOrderByClause, offset, limit); + } + + public EntityList selectByWhereClause( + String aMainTablePrefix, List anExtraTables, + String aWhereClause, String anOrderByClause, + int anOffset, int aLimit) throws DatabaseExc, DatabaseFailure { + + if (anExtraTables!=null && ((String) anExtraTables.get(0)).trim().equals("")){ + anExtraTables=null; + } + + // check o_store for entitylist + // only if no relational select + if (anExtraTables==null) { + if (StoreUtil.extendsStorableEntity(entityClass)) { + StoreIdentifier searchSid = new StoreIdentifier(entityClass, + StoreContainerType.STOC_TYPE_ENTITYLIST, + StoreUtil.getEntityListUniqueIdentifierFor(mainTable, + aWhereClause, anOrderByClause, anOffset, aLimit)); + EntityList hit = (EntityList) o_store.use(searchSid); + + if (hit != null) { + return hit; + } + } + } + + RecordRetriever retriever = new RecordRetriever(mainTable, aMainTablePrefix); + + EntityList result = null; + Connection connection = null; + + if (anExtraTables!=null) { + Iterator i = anExtraTables.iterator(); + while (i.hasNext()) { + String table = (String) i.next(); + if (!"".equals(table)) { + retriever.addExtraTable(table); + } + } + } + + if (aWhereClause != null) { + retriever.appendWhereClause(aWhereClause); + } + + if ((anOrderByClause != null) && !(anOrderByClause.trim().length() == 0)) { + retriever.appendOrderByClause(anOrderByClause); + } + + if (anOffset>-1 && aLimit>-1) { + retriever.setLimit(aLimit+1); + retriever.setOffset(anOffset); + } + + Iterator i = getFieldNames().iterator(); + while (i.hasNext()) { + retriever.addField((String) i.next()); + } + + // execute sql + try { + connection = obtainConnection(); + ResultSet resultSet = retriever.execute(connection); + + boolean hasMore = false; + + if (resultSet != null) { + result = new EntityList(); + Entity entity; + int position = 0; + + while (((aLimit == -1) || (position 0) { + theResultString.append(data, 0, len); + } + + is.close(); + theResult = theResultString.toString(); + } + else { + theResult = null; + } + } + else { + theResult = getValueAsString(rs, (i + 1), type); + } + + if (theResult != null) { + fields.put(getFieldNames().get(i), theResult); + } + } + + if (entityClass != null) { + returnEntity = createNewEntity(); + returnEntity.setFieldValues(fields); + + if (returnEntity instanceof StorableObject) { + logger.debug("CACHE: ( in) " + returnEntity.getId() + " :" + mainTable); + o_store.add(((StorableObject) returnEntity).getStoreIdentifier()); + } + } + else { + throw new DatabaseExc("Internal Error: entityClass not set!"); + } + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + + return returnEntity; + } + + /** + * Inserts an entity into the database. + * + * @param anEntity + * @return the value of the primary key of the inserted record + */ + public String insert(Entity anEntity) throws DatabaseFailure { + invalidateStore(); + + RecordInserter inserter = + new RecordInserter(mainTable, getPrimaryKeySequence()); + + String returnId = null; + Connection con = null; + + try { + String fieldName; + + // make sql-string + for (int i = 0; i < getFieldNames().size(); i++) { + fieldName = (String) getFieldNames().get(i); + + if (!fieldName.equals(primaryKeyField)) { + // exceptions + if (!anEntity.hasFieldValue(fieldName) && ( + fieldName.equals("webdb_create") || + fieldName.equals("webdb_lastchange"))) { + inserter.assignVerbatim(fieldName, "now()"); + } + else { + if (anEntity.hasFieldValue(fieldName)) { + inserter.assignString(fieldName, anEntity.getFieldValue(fieldName)); + } + } + } + } + + con = obtainConnection(); + returnId = inserter.execute(con); + + anEntity.setId(returnId); + } + finally { + freeConnection(con); + } + + return returnId; + } + + /** + * Updates an entity in the database + * + * @param theEntity + */ + public void update(Entity theEntity) throws DatabaseFailure { + invalidateStore(); + + RecordUpdater generator = new RecordUpdater(getTableName(), theEntity.getId()); + + // build sql statement + for (int i = 0; i < getFieldNames().size(); i++) { + String field = (String) getFieldNames().get(i); + + if (!(field.equals(primaryKeyField) || + "webdb_create".equals(field) || + "webdb_lastchange".equals(field) || + binaryFields.contains(field))) { + + if (theEntity.hasFieldValue(field)) { + generator.assignString(field, theEntity.getFieldValue(field)); + } + } + } + + // exceptions + if (hasField("webdb_lastchange")) { + generator.assignVerbatim("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 (hasField("webdb_create") && + theEntity.hasFieldValue("webdb_create")) { + // minimum of 10 (yyyy-mm-dd)... + if (theEntity.getFieldValue("webdb_create").length() >= 10) { + String dateString = theEntity.getFieldValue("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 = userInputDateFormat.parse(dateString); + generator.assignDateTime("webdb_create", d); + } + catch (ParseException e) { + throw new DatabaseFailure(e); + } + } + } + Connection connection = null; + + try { + connection = obtainConnection(); + generator.execute(connection); + } + finally { + freeConnection(connection); + } + } + + private void invalidateObject(String anId) { + // ostore send notification + if (StoreUtil.extendsStorableEntity(entityClass)) { + String uniqueId = anId; + + if (entityClass.equals(StorableObjectEntity.class)) { + uniqueId += ("@" + mainTable); + } + + logger.debug("CACHE: (del) " + anId); + + StoreIdentifier search_sid = + new StoreIdentifier(entityClass, + StoreContainerType.STOC_TYPE_ENTITY, uniqueId); + o_store.invalidate(search_sid); + } + } + + /* + * delete-Operator + * @param id des zu loeschenden Datensatzes + * @return boolean liefert true zurueck, wenn loeschen erfolgreich war. + */ + public boolean delete(String id) throws DatabaseFailure { + invalidateObject(id); + + int resultCode = 0; + Connection connection = obtainConnection(); + PreparedStatement statement = null; + + try { + statement = connection.prepareStatement("delete from " + mainTable + " where " + primaryKeyField + "=?"); + statement.setInt(1, Integer.parseInt(id)); + logQueryBefore("delete from " + mainTable + " where " + primaryKeyField + "=" + id + ""); + resultCode = statement.executeUpdate(); + } + catch (SQLException e) { + logger.warn("Can't delete record", e); + } + finally { + freeConnection(connection, statement); + } + + invalidateStore(); + + return (resultCode > 0) ? true : false; + } + + /** + * Deletes entities based on a where clause + */ + public int deleteByWhereClause(String aWhereClause) throws DatabaseFailure { + invalidateStore(); + + Statement stmt = null; + Connection con = null; + int res = 0; + String sql = + "delete from " + mainTable + " where " + aWhereClause; + + //theLog.printInfo("DELETE " + sql); + try { + con = obtainConnection(); + stmt = con.createStatement(); + res = stmt.executeUpdate(sql); + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + finally { + freeConnection(con, stmt); + } + + return res; + } + + /* noch nicht implementiert. + * @return immer false + */ + public boolean delete(EntityList theEntityList) { + return false; + } + + public ResultSet executeSql(Statement stmt, String sql) + throws DatabaseFailure, SQLException { + ResultSet rs; + logQueryBefore(sql); + long startTime = System.currentTimeMillis(); + try { + rs = stmt.executeQuery(sql); + + logQueryAfter(sql, (System.currentTimeMillis() - startTime)); + } + catch (SQLException e) { + logQueryError(sql, (System.currentTimeMillis() - startTime), e); + throw e; + } + + return rs; + } + + private Map processRow(ResultSet aResultSet) throws DatabaseFailure { + try { + Map result = new HashMap(); + ResultSetMetaData metaData = aResultSet.getMetaData(); + int nrColumns = metaData.getColumnCount(); + for (int i=0; iList of + * Maps + */ + public List executeFreeSql(String sql, int aLimit) throws DatabaseFailure, DatabaseExc { + Connection connection = null; + Statement statement = null; + try { + List result = new ArrayList(); + connection = obtainConnection(); + statement = connection.createStatement(); + ResultSet resultset = executeSql(statement, sql); + try { + while (resultset.next() && result.size() < aLimit) { + result.add(processRow(resultset)); + } + } + finally { + resultset.close(); + } + + return result; + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + finally { + if (connection!=null) { + freeConnection(connection, statement); + } + } + } + + /** + * Executes 1 sql statement and returns the first result row as a Maps + * (null if there wasn't any row) + */ + public Map executeFreeSingleRowSql(String anSqlStatement) throws DatabaseFailure, DatabaseExc { + try { + List resultList = executeFreeSql(anSqlStatement, 1); + try { + if (resultList.size()>0) + return (Map) resultList.get(0); + return null; + } + finally { + } + } + catch (Throwable t) { + throw new DatabaseFailure(t); + } + } + + /** + * Executes 1 sql statement and returns the first column of the first result row as a Strings + * (null if there wasn't any row) + */ + public String executeFreeSingleValueSql(String sql) throws DatabaseFailure, DatabaseExc { + Map row = executeFreeSingleRowSql(sql); + + if (row==null) + return null; + + Iterator i = row.values().iterator(); + if (i.hasNext()) + return (String) i.next(); + return null; + } + + public int getSize(String where) throws SQLException, DatabaseFailure { + return getSize("", null, where); + } + /** + * returns the number of rows in the table + */ + public int getSize(String mainTablePrefix, List extraTables, String where) throws SQLException, DatabaseFailure { + + String useTable = mainTable; + if (mainTablePrefix!=null && mainTablePrefix.trim().length()>0) { + useTable+=" "+mainTablePrefix; + } + StringBuffer countSql = + new StringBuffer("select count(*) from ").append(useTable); + // append extratables, if necessary + if (extraTables!=null) { + for (int i=0;i < extraTables.size();i++) { + if (!extraTables.get(i).equals("")) { + countSql.append( ", " + extraTables.get(i)); + } + } + } + + if ((where != null) && (where.length() != 0)) { + countSql.append( " where " + where); + } + + Connection con = null; + Statement stmt = null; + int result = 0; + logQueryBefore(countSql.toString()); + long startTime = System.currentTimeMillis(); + + try { + con = obtainConnection(); + stmt = con.createStatement(); + + ResultSet rs = executeSql(stmt, countSql.toString()); + + while (rs.next()) { + result = rs.getInt(1); + } + } + catch (SQLException e) { + logger.error("Database.getSize: " + e.getMessage()); + } + finally { + freeConnection(con, stmt); + } + logQueryAfter(countSql.toString(), (System.currentTimeMillis() - startTime)); + + return result; + } + + public int executeUpdate(Statement stmt, String sql) + throws DatabaseFailure, SQLException { + int rs; + + logQueryBefore(sql); + long startTime = System.currentTimeMillis(); + + try { + rs = stmt.executeUpdate(sql); + + logQueryAfter(sql, (System.currentTimeMillis() - startTime)); + } + catch (SQLException e) { + logQueryError(sql, (System.currentTimeMillis() - startTime), e); + throw e; + } + + return rs; + } + + public int executeUpdate(String sql) + throws DatabaseFailure, SQLException { + int result = -1; + Connection con = null; + PreparedStatement pstmt = null; + + logQueryBefore(sql); + long startTime = System.currentTimeMillis(); + try { + con = obtainConnection(); + pstmt = con.prepareStatement(sql); + result = pstmt.executeUpdate(); + logQueryAfter(sql, System.currentTimeMillis() - startTime); + } + catch (Throwable e) { + logQueryError(sql, System.currentTimeMillis() - startTime, e); + throw new DatabaseFailure("Database.executeUpdate(" + sql + "): " + e.getMessage(), e); + } + finally { + freeConnection(con, pstmt); + } + return result; + } + + /** + * Processes the metadata for the table this Database object is responsible for. + */ + private void processMetaData(ResultSetMetaData aMetaData) throws DatabaseFailure { + fieldNames = new ArrayList(); + fieldNameToType = new HashMap(); + + try { + int numFields = aMetaData.getColumnCount(); + fieldTypes = new int[numFields]; + + for (int i = 1; i <= numFields; i++) { + fieldNames.add(aMetaData.getColumnName(i)); + fieldTypes[i - 1] = aMetaData.getColumnType(i); + fieldNameToType.put(aMetaData.getColumnName(i), new Integer(aMetaData.getColumnType(i))); + } + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + } + + /** + * Retrieves metadata from the table this Database object represents + */ + private void acquireMetaData() throws DatabaseFailure { + Connection connection = null; + PreparedStatement statement = null; + String sql = "select * from " + mainTable + " where 0=1"; + + try { + connection = obtainConnection(); + statement = connection.prepareStatement(sql); + + logger.debug("METADATA: " + sql); + ResultSet resultSet = statement.executeQuery(); + try { + processMetaData(resultSet.getMetaData()); + } + finally { + resultSet.close(); + } + } + catch (Throwable e) { + throw new DatabaseFailure(e); + } + finally { + freeConnection(connection, statement); + } + } + + public Connection obtainConnection() throws DatabaseFailure { + try { + return MirGlobal.getDatabaseEngine().obtainConnection(); + } + catch (Exception e) { + throw new DatabaseFailure(e); + } + } + + public void freeConnection(Connection aConnection) throws DatabaseFailure { + try { + MirGlobal.getDatabaseEngine().releaseConnection(aConnection); + } + catch (Throwable t) { + logger.warn("Can't release connection: " + t.toString()); + } + } + + public void freeConnection(Connection aConnection, Statement aStatement) throws DatabaseFailure { + try { + aStatement.close(); + } + catch (Throwable t) { + logger.warn("Can't close statement", t); + } + + freeConnection(aConnection); + } + + protected void _throwStorageObjectException(Exception e, String aFunction) + throws DatabaseFailure { + + if (e != null) { + logger.error(e.getMessage() + aFunction); + throw new DatabaseFailure(aFunction, e); + } + } + + + /** + * Invalidates any cached entity list + */ + private void invalidateStore() { + // invalidating all EntityLists corresponding with entityClass + if (StoreUtil.extendsStorableEntity(entityClass)) { + StoreContainerType stoc_type = + StoreContainerType.valueOf(entityClass, StoreContainerType.STOC_TYPE_ENTITYLIST); + o_store.invalidate(stoc_type); + } + } + + /** + * Retrieves a binary value + */ + public byte[] getBinaryField(String aQuery) throws DatabaseFailure, SQLException { + Connection connection=null; + Statement statement=null; + InputStream inputStream; + + try { + connection = obtainConnection(); + try { + connection.setAutoCommit(false); + statement = connection.createStatement(); + ResultSet resultSet = executeSql(statement, aQuery); + + if(resultSet!=null) { + if (resultSet.next()) { + if (resultSet.getMetaData().getColumnType(1) == java.sql.Types.BINARY) { + return resultSet.getBytes(1); + } + else { + inputStream = resultSet.getBlob(1).getBinaryStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + StreamCopier.copy(inputStream, outputStream); + return outputStream.toByteArray(); + } + } + resultSet.close(); + } + } + finally { + try { + connection.setAutoCommit(true); + } + catch (Throwable e) { + logger.error("EntityImages.getImage resetting transaction mode failed: " + e.toString()); + e.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE)); + } + + try { + freeConnection(connection, statement); + } + catch (Throwable e) { + logger.error("EntityImages.getImage freeing connection failed: " +e.toString()); + } + + } + } + catch (Throwable t) { + logger.error("EntityImages.getImage failed: " + t.toString()); + t.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE)); + + throw new DatabaseFailure(t); + } + + return new byte[0]; + } + + /** + * Sets a binary value for a particular field in a record specified by its identifier + */ + public void setBinaryField(String aFieldName, String anObjectId, byte aData[]) throws DatabaseFailure, SQLException { + PreparedStatement statement = null; + Connection connection = obtainConnection(); + + try { + connection.setAutoCommit(false); + try { + // are we using bytea ? + if (getFieldType(aFieldName) == java.sql.Types.BINARY) { + statement = connection.prepareStatement( + "update " + mainTable + " set " + aFieldName + " = ? where " + getIdFieldName() + "=" + Integer.parseInt(anObjectId)); + statement.setBytes(1, aData); + statement.execute(); + connection.commit(); + } + // or the old oid's + else { + PGConnection postgresqlConnection = (org.postgresql.PGConnection) ((DelegatingConnection) connection).getDelegate(); + LargeObjectManager lobManager = postgresqlConnection.getLargeObjectAPI(); + int oid = lobManager.create(LargeObjectManager.READ | LargeObjectManager.WRITE); + LargeObject obj = lobManager.open(oid, LargeObjectManager.WRITE); // Now open the file File file = + obj.write(aData); + obj.close(); + statement = connection.prepareStatement( + "update " + mainTable + " set " + aFieldName + " = ? where " + getIdFieldName() + "=" + Integer.parseInt(anObjectId)); + statement.setInt(1, oid); + statement.execute(); + connection.commit(); + } + } + finally { + connection.setAutoCommit(true); + } + } + finally { + freeConnection(connection, statement); + } + } + + /** + * Can be overridden to specify a primary key sequence name not named according to + * the convention (tablename _id_seq) + */ + protected String getPrimaryKeySequence() { + return mainTable+"_id_seq"; + } + + /** + * Can be called by subclasses to specify fields that are binary, and that shouldn't + * be updated outside of {@link #setBinaryField} + * + * @param aBinaryField The field name of the binary field + */ + protected void markBinaryField(String aBinaryField) { + binaryFields.add(aBinaryField); + } + + private void logQueryBefore(String aQuery) { + logger.debug("about to perform QUERY " + aQuery); +// (new Throwable()).printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE)); + } + + private void logQueryAfter(String aQuery, long aTime) { + logger.info("QUERY " + aQuery + " took " + aTime + "ms."); + } + + private void logQueryError(String aQuery, long aTime, Throwable anException) { + logger.error("QUERY " + aQuery + " took " + aTime + "ms, but threw exception " + anException.toString()); + } + + private int getFieldType(String aFieldName) { + if (fieldNameToType == null) { + acquireMetaData(); + } + + return ((Integer) fieldNameToType.get(aFieldName)).intValue(); + } +}