X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=source%2Fmir%2Fstorage%2FDatabase.java;fp=source%2Fmir%2Fstorage%2FDatabase.java;h=336c2b9ed21936c35f9fd675da61d2d89334e13f;hb=c9ac8fa71b679f8d967aac901bbef945c13b94c9;hp=5317cea9df6db9c90e88bf6fe6dd8092aceb4042;hpb=d63595f89aaa4b6a524dc0b4af9e0eef888f4c6b;p=mir.git diff --git a/source/mir/storage/Database.java b/source/mir/storage/Database.java index 5317cea9..336c2b9e 100755 --- a/source/mir/storage/Database.java +++ b/source/mir/storage/Database.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001, 2002 The Mir-coders group + * Copyright (C) 2001-2006 The Mir-coders group * * This file is part of Mir. * @@ -19,8 +19,6 @@ * * 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, - * 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 @@ -30,17 +28,21 @@ 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.misc.StringUtil; 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.ByteArrayInputStream; -import java.io.IOException; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.sql.*; @@ -51,146 +53,111 @@ import java.util.*; /** * Implements database access. * + * @version $Id: Database.java,v 1.48 2007/04/08 21:46:37 idfx Exp $ * @author rk + * @author Zapata * */ -public class Database implements StorageObject { - private static Class GENERIC_ENTITY_CLASS = mir.entity.StorableObjectEntity.class; +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(); - private static final int _millisPerHour = 60 * 60 * 1000; protected LoggerWrapper logger; - protected MirPropertiesConfiguration configuration; protected String mainTable; - protected String primaryKeySequence = null; protected String primaryKeyField = "id"; - protected boolean evaluatedMetaData = false; - protected ArrayList metadataFields; - protected ArrayList metadataLabels; - protected ArrayList metadataNotNullFields; - protected int[] metadataTypes; - protected Class theEntityClass; - protected boolean hasTimestamp = true; - private int defaultLimit; + private List fieldNames; + private int[] fieldTypes; + private Map fieldNameToType; - TimeZone timezone; - SimpleDateFormat internalDateFormat; - SimpleDateFormat userInputDateFormat; + protected Class entityClass; - /** - * 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. - */ - public Database() throws StorageObjectFailure { - configuration = MirPropertiesConfiguration.instance(); + // + 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")); - internalDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - internalDateFormat.setTimeZone(timezone); userInputDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); userInputDateFormat.setTimeZone(timezone); - String theAdaptorName = configuration.getString("Database.Adaptor"); - defaultLimit = Integer.parseInt(configuration.getString("Database.Limit")); + binaryFields = new HashSet(); + + String adapterName = configuration.getString("Database.Adaptor"); try { - theEntityClass = GENERIC_ENTITY_CLASS; + entityClass = GENERIC_ENTITY_CLASS; } catch (Throwable e) { - logger.error("Error in Database() constructor with " + theAdaptorName + " -- " + e.getMessage()); - throw new StorageObjectFailure("Error in Database() constructor.", e); + logger.error("Error in Database() constructor with " + adapterName + " -- " + e.getMessage()); + throw new DatabaseFailure("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; + public Class getEntityClass() { + return entityClass; } - /** - * 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; + public Entity createNewEntity() throws DatabaseFailure { + try { + AbstractEntity result = (AbstractEntity) entityClass.newInstance(); + result.setStorage(this); + + return result; + } + catch (Throwable t) { + throw new DatabaseFailure(t); + } } - /** - * 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() { + public String getIdFieldName() { return primaryKeyField; } - /** - * Liefert den Namen der Tabelle, auf das sich das Datenbankobjekt bezieht. - * - * @return Name der Tabelle - */ public String getTableName() { return mainTable; } /** - * Returns the id that was most recently added to the database + * Returns a list of field names for this Database */ - private String getLatestInsertedId(Connection aConnection) throws SQLException { - if (primaryKeySequence==null) - primaryKeySequence = mainTable+"_id_seq"; - - PreparedStatement statement = aConnection.prepareStatement("select currval('" + primaryKeySequence + "')"); + public List getFieldNames() throws DatabaseFailure { + if (fieldNames == null) { + acquireMetaData(); + } - ResultSet rs = statement.executeQuery(); - rs.next(); - return rs.getString(1); + return fieldNames; } - /** - * Liefert eine Liste der Felder der Tabelle - * @return ArrayList mit Feldern - */ - public List getFields() throws StorageObjectFailure { - if (metadataFields == null) { - get_meta_data(); - } - - return metadataFields; + public boolean hasField(String aFieldName) { + return getFieldNames().contains(aFieldName); } /** * Gets value out of ResultSet according to type and converts to String - * @param rs ResultSet. + * + * @param aResultSet ResultSet. * @param aType a type from java.sql.Types.* - * @param valueIndex index in ResultSet + * @param aFieldIndex index in ResultSet * @return returns the value as String. If no conversion is possible * /unsupported value/ is returned */ - private String getValueAsString(ResultSet rs, int valueIndex, int aType) - throws StorageObjectFailure { + private String getValueAsString(ResultSet aResultSet, int aFieldIndex, int aType) + throws DatabaseFailure { String outValue = null; - if (rs != null) { + if (aResultSet != null) { try { switch (aType) { case java.sql.Types.BIT: - outValue = (rs.getBoolean(valueIndex) == true) ? "1" : "0"; + outValue = (aResultSet.getBoolean(aFieldIndex) == true) ? "1" : "0"; break; @@ -199,21 +166,18 @@ public class Database implements StorageObject { case java.sql.Types.TINYINT: case java.sql.Types.BIGINT: - int out = rs.getInt(valueIndex); + int out = aResultSet.getInt(aFieldIndex); - if (!rs.wasNull()) { + if (!aResultSet.wasNull()) { outValue = new Integer(out).toString(); } break; case java.sql.Types.NUMERIC: + long outl = aResultSet.getLong(aFieldIndex); - /** todo Numeric can be float or double depending upon - * metadata.getScale() / especially with oracle */ - long outl = rs.getLong(valueIndex); - - if (!rs.wasNull()) { + if (!aResultSet.wasNull()) { outValue = new Long(outl).toString(); } @@ -221,9 +185,9 @@ public class Database implements StorageObject { case java.sql.Types.REAL: - float tempf = rs.getFloat(valueIndex); + float tempf = aResultSet.getFloat(aFieldIndex); - if (!rs.wasNull()) { + if (!aResultSet.wasNull()) { tempf *= 10; tempf += 0.5; @@ -238,9 +202,9 @@ public class Database implements StorageObject { case java.sql.Types.DOUBLE: - double tempd = rs.getDouble(valueIndex); + double tempd = aResultSet.getDouble(aFieldIndex); - if (!rs.wasNull()) { + if (!aResultSet.wasNull()) { tempd *= 10; tempd += 0.5; @@ -256,12 +220,12 @@ public class Database implements StorageObject { case java.sql.Types.CHAR: case java.sql.Types.VARCHAR: case java.sql.Types.LONGVARCHAR: - outValue = rs.getString(valueIndex); + outValue = aResultSet.getString(aFieldIndex); break; case java.sql.Types.LONGVARBINARY: - outValue = rs.getString(valueIndex); + outValue = aResultSet.getString(aFieldIndex); break; @@ -271,34 +235,22 @@ public class Database implements StorageObject { // 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)); + Timestamp timestamp = (aResultSet.getTimestamp(aFieldIndex)); - if (!rs.wasNull()) { + if (!aResultSet.wasNull()) { java.util.Date date = new java.util.Date(timestamp.getTime()); - - Calendar calendar = new GregorianCalendar(); - calendar.setTime(date); - calendar.setTimeZone(timezone); - outValue = internalDateFormat.format(date); - - int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); - String tzOffset = StringUtil.zeroPaddingNumber(Math.abs(offset) / _millisPerHour, 2, 2); - - if (offset<0) - outValue = outValue + "-"; - else - outValue = outValue + "+"; - outValue = outValue + tzOffset; + outValue = DatabaseHelper.convertDateToInternalRepresenation(date); } break; default: outValue = ""; - logger.warn("Unsupported Datatype: at " + valueIndex + " (" + aType + ")"); + logger.warn("Unsupported Datatype: at " + aFieldIndex + " (" + aType + ")"); } - } catch (SQLException e) { - throw new StorageObjectFailure("Could not get Value out of Resultset -- ", + } + catch (SQLException e) { + throw new DatabaseFailure("Could not get Value out of Resultset -- ", e); } } @@ -307,24 +259,23 @@ public class Database implements StorageObject { } /** - * select-Operator um einen Datensatz zu bekommen. - * @param id Primaerschluessel des Datensatzes. - * @return liefert EntityObject des gefundenen Datensatzes oder null. + * Return an entity specified by id, or null if no such + * entity exists. */ - public Entity selectById(String id) throws StorageObjectExc { - if ((id == null) || id.equals("")) { - throw new StorageObjectExc("Database.selectById: Missing id"); + 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(theEntityClass)) { - String uniqueId = id; + if (StoreUtil.extendsStorableEntity(entityClass)) { + String uniqueId = anId; - if (theEntityClass.equals(StorableObjectEntity.class)) { + if (entityClass.equals(StorableObjectEntity.class)) { uniqueId += ("@" + mainTable); } - StoreIdentifier search_sid = new StoreIdentifier(theEntityClass, uniqueId); + 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); @@ -334,184 +285,104 @@ public class Database implements StorageObject { } } - Statement stmt = null; Connection con = obtainConnection(); Entity returnEntity = null; + PreparedStatement statement = null; try { ResultSet rs; + String query = "select * from " + mainTable + " where " + primaryKeyField + " = ?"; - /** todo better prepared statement */ - String selectSql = - "select * from " + mainTable + " where " + primaryKeyField + "=" + id; - stmt = con.createStatement(); - rs = executeSql(stmt, selectSql); + statement = con.prepareStatement(query); + statement.setString(1, anId); - if (rs != null) { - if (evaluatedMetaData == false) { - evalMetaData(rs.getMetaData()); - } + 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: " + id + " in table " + mainTable); + logger.warn("No data for id: " + anId + " in table " + mainTable); } rs.close(); } else { - logger.warn("No Data for Id " + id + " in Table " + mainTable); + logger.warn("No Data for Id " + anId + " in Table " + mainTable); } } - catch (SQLException sqe) { - throwSQLException(sqe, "selectById"); - return null; - } - catch (NumberFormatException e) { - logger.error("ID is no number: " + id); + catch (Throwable e) { + throw new DatabaseFailure(e); } finally { - freeConnection(con, stmt); + freeConnection(con, statement); } return returnEntity; } - /** - * This method makes it possible to make selects across multiple tables - * - * @param mainTablePrefix prefix for the mainTable - * @param extraTables a vector of tables for relational select - * @param aWhereClause whereClause - * @return EntityList of selected Objects - * @throws StorageObjectFailure - */ - - public EntityList selectByWhereClauseWithExtraTables(String mainTablePrefix, - List extraTables, String aWhereClause ) - throws StorageObjectFailure { - return selectByWhereClause( mainTablePrefix, extraTables, aWhereClause, "", 0, defaultLimit); + 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 StorageObjectFailure { + 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 StorageObjectFailure { + public EntityList selectByFieldValue(String aField, String aValue, int offset) throws DatabaseExc, DatabaseFailure { return selectByWhereClause(aField + "='" + JDBCStringRoutines.escapeStringLiteral(aValue)+"'", offset); } - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Also offset wird der erste Datensatz genommen. - * - * @param where where-Clause - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ - public EntityList selectByWhereClause(String where) throws StorageObjectFailure { + public EntityList selectByWhereClause(String where) throws DatabaseExc, DatabaseFailure { 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 whereClause where-Clause - * @param offset ab welchem Datensatz. - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ - public EntityList selectByWhereClause(String whereClause, int offset) throws StorageObjectFailure { + public EntityList selectByWhereClause(String whereClause, int offset) throws DatabaseExc, DatabaseFailure { 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 where where-Clause - * @param order orderBy-Clause - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ - public EntityList selectByWhereClause(String where, String order) throws StorageObjectFailure { - return selectByWhereClause(where, order, 0); - } - - public EntityList selectByWhereClause(String mainTablePrefix, List extraTables, String where, String order) throws StorageObjectFailure { - return selectByWhereClause(mainTablePrefix, extraTables, where, order, 0, defaultLimit); + public EntityList selectByWhereClause(String mainTablePrefix, List extraTables, String where, String order) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(mainTablePrefix, extraTables, where, order, 0, DEFAULT_LIMIT); } - /** - * select-Operator liefert eine EntityListe mit den gematchten Datens?tzen zur?ck. - * Als maximale Anzahl wird das Limit auf der Konfiguration genommen. - * - * @param whereClause where-Clause - * @param orderBy orderBy-Clause - * @param offset ab welchem Datensatz - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ - public EntityList selectByWhereClause(String whereClause, String orderBy, int offset) throws StorageObjectFailure { - return selectByWhereClause(whereClause, orderBy, offset, defaultLimit); + public EntityList selectByWhereClause(String whereClause, String orderBy, int offset) throws DatabaseExc, DatabaseFailure { + return selectByWhereClause(whereClause, orderBy, offset, DEFAULT_LIMIT); } - /** - * select-Operator returns EntityList with matching rows in Database. - * @param aWhereClause where-Clause - * @param anOrderByClause orderBy-Clause - * @param offset ab welchem Datensatz - * @param limit wieviele Datens?tze - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ public EntityList selectByWhereClause(String aWhereClause, String anOrderByClause, - int offset, int limit) throws StorageObjectFailure { + int offset, int limit) throws DatabaseExc, DatabaseFailure { return selectByWhereClause("", null, aWhereClause, anOrderByClause, offset, limit); } - - /** - * select-Operator returns EntityList with matching rows in Database. - * @param aWhereClause where-Clause - * @param anOrderByClause orderBy-Clause - * @param offset ab welchem Datensatz - * @param limit wieviele Datens?tze - * @return EntityList mit den gematchten Entities - * @exception StorageObjectFailure - */ - public EntityList selectByWhereClause(String mainTablePrefix, List extraTables, + public EntityList selectByWhereClause( + String aMainTablePrefix, List anExtraTables, String aWhereClause, String anOrderByClause, - int offset, int limit) throws StorageObjectFailure { - - // TODO get rid of emtpy Strings in extraTables - // make extraTables null, if single empty String in it - // cause StringUtil.splitString puts in emptyString - if (extraTables != null && ((String)extraTables.get(0)).trim().equals("")) - { - logger.debug("+++ made extraTables to null!"); - extraTables=null; - } + int anOffset, int aLimit) throws DatabaseExc, DatabaseFailure { - String useTable = mainTable; - String selectStar = "*"; - if (mainTablePrefix!=null && mainTablePrefix.trim().length()>0) { - useTable+=" "+mainTablePrefix; - selectStar=mainTablePrefix.trim() + ".*"; - } + if (anExtraTables!=null && ((String) anExtraTables.get(0)).trim().equals("")){ + anExtraTables=null; + } // check o_store for entitylist // only if no relational select - if (extraTables==null) { - if (StoreUtil.extendsStorableEntity(theEntityClass)) { - StoreIdentifier searchSid = new StoreIdentifier(theEntityClass, + if (anExtraTables==null) { + if (StoreUtil.extendsStorableEntity(entityClass)) { + StoreIdentifier searchSid = new StoreIdentifier(entityClass, StoreContainerType.STOC_TYPE_ENTITYLIST, StoreUtil.getEntityListUniqueIdentifierFor(mainTable, - aWhereClause, anOrderByClause, offset, limit)); + aWhereClause, anOrderByClause, anOffset, aLimit)); EntityList hit = (EntityList) o_store.use(searchSid); if (hit != null) { @@ -520,161 +391,112 @@ public class Database implements StorageObject { } } - // local - EntityList theReturnList = null; - Connection con = null; - Statement stmt = null; - ResultSet rs; - int offsetCount = 0; - int count = 0; - - // build sql-statement + RecordRetriever retriever = new RecordRetriever(mainTable, aMainTablePrefix); - if ((aWhereClause != null) && (aWhereClause.trim().length() == 0)) { - aWhereClause = null; - } + EntityList result = null; + Connection connection = null; - StringBuffer countSql = - new StringBuffer("select count(*) from ").append(useTable); - StringBuffer selectSql = - new StringBuffer("select "+selectStar+" 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)); - selectSql.append( ", " + extraTables.get(i)); + if (anExtraTables!=null) { + Iterator i = anExtraTables.iterator(); + while (i.hasNext()) { + String table = (String) i.next(); + if (!"".equals(table)) { + retriever.addExtraTable(table); } } } if (aWhereClause != null) { - selectSql.append(" where ").append(aWhereClause); - countSql.append(" where ").append(aWhereClause); + retriever.appendWhereClause(aWhereClause); } if ((anOrderByClause != null) && !(anOrderByClause.trim().length() == 0)) { - selectSql.append(" order by ").append(anOrderByClause); + retriever.appendOrderByClause(anOrderByClause); } - if ((limit > -1) && (offset > -1)) { - selectSql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset); + 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 { - con = obtainConnection(); - 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 - count = offsetCount; - - if (theReturnList != null) { - // now we decide if we have to know an overall count... - count = offsetCount; + connection = obtainConnection(); + ResultSet resultSet = retriever.execute(connection); - if ((limit > -1) && (offset > -1)) { - if (offsetCount == limit) { - rs = executeSql(stmt, countSql.toString()); + boolean hasMore = false; - if (rs != null) { - if (rs.next()) { - count = rs.getInt(1); - } + if (resultSet != null) { + result = new EntityList(); + Entity entity; + int position = 0; - rs.close(); - } - else { - logger.error("Could not count: " + countSql); - } - } + while (((aLimit == -1) || (position= limit) { - theReturnList.setPrevBatch(offset - limit); - } + if (result != null) { + result.setOffset(anOffset); + result.setWhere(aWhereClause); + result.setOrder(anOrderByClause); + result.setStorage(this); + result.setLimit(aLimit); - if ((offset + offsetCount) < count) { - theReturnList.setNextBatch(offset + limit); + if (hasMore) { + result.setNextBatch(anOffset + aLimit); } - if (extraTables==null && StoreUtil.extendsStorableEntity(theEntityClass)) { - StoreIdentifier sid = theReturnList.getStoreIdentifier(); + if (anExtraTables==null && StoreUtil.extendsStorableEntity(entityClass)) { + StoreIdentifier sid = result.getStoreIdentifier(); logger.debug("CACHE (add): " + sid.toString()); o_store.add(sid); } } } - catch (SQLException sqe) { - throwSQLException(sqe, "selectByWhereClause"); + catch (Throwable e) { + throw new DatabaseFailure(e); } finally { try { - if (con != null) { - freeConnection(con, stmt); + if (connection != null) { + freeConnection(connection); } } catch (Throwable t) { } } - return theReturnList; + return result; } - /** - * Bastelt aus einer Zeile der Datenbank ein EntityObjekt. - * - * @param rs Das ResultSetObjekt. - * @return Entity Die Entity. - */ - private Entity makeEntityFromResultSet(ResultSet rs) - throws StorageObjectFailure { - Map theResultHash = new HashMap(); + private Entity makeEntityFromResultSet(ResultSet rs) { + Map fields = new HashMap(); String theResult = null; - int theType; + int type; Entity returnEntity = null; try { - if (StoreUtil.extendsStorableEntity(theEntityClass)) { + if (StoreUtil.extendsStorableEntity(entityClass)) { StoreIdentifier searchSid = StorableObjectEntity.getStoreIdentifier(this, - theEntityClass, rs); + entityClass, rs); Entity hit = (Entity) o_store.use(searchSid); if (hit != null) return hit; } + for (int i = 0; i < getFieldNames().size(); i++) { + type = fieldTypes[i]; - 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) { + if (type == java.sql.Types.LONGVARBINARY) { InputStreamReader is = (InputStreamReader) rs.getCharacterStream(i + 1); @@ -689,44 +511,35 @@ public class Database implements StorageObject { is.close(); theResult = theResultString.toString(); - } else { + } + else { theResult = null; } - } else { - theResult = getValueAsString(rs, (i + 1), theType); + } + else { + theResult = getValueAsString(rs, (i + 1), type); } if (theResult != null) { - theResultHash.put(metadataFields.get(i), theResult); + fields.put(getFieldNames().get(i), theResult); } } - if (theEntityClass != null) { - returnEntity = (Entity) theEntityClass.newInstance(); - returnEntity.setStorage(this); - returnEntity.setFieldValues(theResultHash); + 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 { - throwStorageObjectException("Internal Error: theEntityClass not set!"); + } + else { + throw new DatabaseExc("Internal Error: entityClass 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; + catch (Throwable e) { + throw new DatabaseFailure(e); } return returnEntity; @@ -735,98 +548,49 @@ public class Database implements StorageObject { /** * Inserts an entity into the database. * - * @param theEntity - * @return der Wert des Primary-keys der eingef?gten Entity + * @param anEntity + * @return the value of the primary key of the inserted record */ - public String insert(Entity theEntity) throws StorageObjectFailure { + public String insert(Entity anEntity) throws DatabaseFailure { invalidateStore(); + RecordInserter inserter = + new RecordInserter(mainTable, getPrimaryKeySequence()); + String returnId = null; Connection con = null; - PreparedStatement pstmt = null; try { - StringBuffer f = new StringBuffer(); - StringBuffer v = new StringBuffer(); - String aField; - String aValue; - boolean firstField = true; + String fieldName; // make sql-string - for (int i = 0; i < getFields().size(); i++) { - aField = (String) getFields().get(i); - - if (!aField.equals(primaryKeyField)) { - aValue = null; + for (int i = 0; i < getFieldNames().size(); i++) { + fieldName = (String) getFieldNames().get(i); + if (!fieldName.equals(primaryKeyField)) { // exceptions - if (!theEntity.hasFieldValue(aField) && ( - aField.equals("webdb_create") || - aField.equals("webdb_lastchange"))) { - aValue = "NOW()"; + if (!anEntity.hasFieldValue(fieldName) && ( + fieldName.equals("webdb_create") || + fieldName.equals("webdb_lastchange"))) { + inserter.assignVerbatim(fieldName, "now()"); } else { - if (theEntity.hasFieldValue(aField)) { - aValue = - "'" + - JDBCStringRoutines.escapeStringLiteral(theEntity.getFieldValue(aField)) + "'"; - } - } - - // wenn Wert gegeben, dann einbauen - if (aValue != null) { - if (firstField == false) { - f.append(","); - v.append(","); + if (anEntity.hasFieldValue(fieldName)) { + inserter.assignString(fieldName, anEntity.getFieldValue(fieldName)); } - else { - firstField = false; - } - - f.append(aField); - v.append(aValue); } } } - // end for - - // insert into db - StringBuffer sqlBuf = - new StringBuffer("insert into ").append(mainTable).append("(").append(f) - .append(") values (").append(v).append(")"); - String sql = sqlBuf.toString(); - logger.info("INSERT: " + sql); con = obtainConnection(); - con.setAutoCommit(false); - pstmt = con.prepareStatement(sql); - - int ret = pstmt.executeUpdate(); + returnId = inserter.execute(con); - if (ret == 0) { - //insert failed - return null; - } - -// pstmt = con.prepareStatement("select currval('" + + "_id_seq')"); - - returnId = getLatestInsertedId(con); - theEntity.setId(returnId); - } - catch (SQLException sqe) { - throwSQLException(sqe, "insert"); + anEntity.setId(returnId); } finally { - try { - con.setAutoCommit(true); - } - catch (Exception e) { - } - - freeConnection(con, pstmt); + freeConnection(con); } - /** todo store entity in o_store */ return returnId; } @@ -835,63 +599,34 @@ public class Database implements StorageObject { * * @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*/ - + public void update(Entity theEntity) throws DatabaseFailure { invalidateStore(); - String id = theEntity.getId(); - String aField; - StringBuffer fv = new StringBuffer(); - boolean firstField = true; + RecordUpdater generator = new RecordUpdater(getTableName(), theEntity.getId()); // build sql statement - for (int i = 0; i < getFields().size(); i++) { - aField = (String) metadataFields.get(i); - - // only normal cases - // todo if entity.hasFieldValue returns false, then the value should be stored as null - if (!(aField.equals(primaryKeyField) || - aField.equals("webdb_create") || - aField.equals("webdb_lastchange"))) { - if (theEntity.hasFieldValue(aField)) { - if (firstField == false) { - fv.append(", "); - } - else { - firstField = false; - } + for (int i = 0; i < getFieldNames().size(); i++) { + String field = (String) getFieldNames().get(i); - fv.append(aField).append("='").append(JDBCStringRoutines.escapeStringLiteral(theEntity.getFieldValue(aField))).append("'"); + if (!(field.equals(primaryKeyField) || + "webdb_create".equals(field) || + "webdb_lastchange".equals(field) || + binaryFields.contains(field))) { - // fv.append(aField).append("='").append(StringUtil.quote((String)theEntity.getFieldValue(aField))).append("'"); + if (theEntity.hasFieldValue(field)) { + generator.assignString(field, theEntity.getFieldValue(field)); } } } - StringBuffer sql = - new StringBuffer("update ").append(mainTable).append(" set ").append(fv); - // exceptions - if (metadataFields.contains("webdb_lastchange")) { - sql.append(",webdb_lastchange=NOW()"); + 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 (metadataFields.contains("webdb_create") && + if (hasField("webdb_create") && theEntity.hasFieldValue("webdb_create")) { // minimum of 10 (yyyy-mm-dd)... if (theEntity.getFieldValue("webdb_create").length() >= 10) { @@ -905,95 +640,76 @@ public class Database implements StorageObject { // TimeStamp stuff try { java.util.Date d = userInputDateFormat.parse(dateString); -// Timestamp tStamp = new Timestamp(d.getTime()); - sql.append(",webdb_create='" + JDBCStringRoutines.formatDate(d) + "'"); + generator.assignDateTime("webdb_create", d); } catch (ParseException e) { - throw new StorageObjectFailure(e); + throw new DatabaseFailure(e); } } } - - sql.append(" where id=").append(id); - logger.info("UPDATE: " + sql); + Connection connection = null; try { - con = obtainConnection(); - con.setAutoCommit(false); - pstmt = con.prepareStatement(sql.toString()); - - pstmt.executeUpdate(); - } - catch (SQLException sqe) { - throwSQLException(sqe, "update"); + connection = obtainConnection(); + generator.execute(connection); } finally { - try { - con.setAutoCommit(true); - } - catch (Exception e) { - ; - } - - freeConnection(con, pstmt); + freeConnection(connection); } } - - /* - * delete-Operator - * @param id des zu loeschenden Datensatzes - * @return boolean liefert true zurueck, wenn loeschen erfolgreich war. - */ - public boolean delete(String id) throws StorageObjectFailure { + + private void invalidateObject(String anId) { // ostore send notification - if (StoreUtil.extendsStorableEntity(theEntityClass)) { - String uniqueId = id; + if (StoreUtil.extendsStorableEntity(entityClass)) { + String uniqueId = anId; - if (theEntityClass.equals(StorableObjectEntity.class)) { + if (entityClass.equals(StorableObjectEntity.class)) { uniqueId += ("@" + mainTable); } - logger.debug("CACHE: (del) " + id); + logger.debug("CACHE: (del) " + anId); StoreIdentifier search_sid = - new StoreIdentifier(theEntityClass, + new StoreIdentifier(entityClass, 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 " + mainTable + " where " + primaryKeyField + "='" + id + "'"; + /* + * 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; - logger.debug("DELETE " + sql); try { - con = obtainConnection(); - stmt = con.createStatement(); - res = stmt.executeUpdate(sql); + 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 sqe) { - throwSQLException(sqe, "delete"); + catch (SQLException e) { + logger.warn("Can't delete record", e); } finally { - freeConnection(con, stmt); + freeConnection(connection, statement); } invalidateStore(); - return (res > 0) ? true : false; + return (resultCode > 0) ? true : false; } /** * Deletes entities based on a where clause - * - * @param aWhereClause - * @return - * @throws StorageObjectFailure */ - public int deleteByWhereClause(String aWhereClause) throws StorageObjectFailure { + public int deleteByWhereClause(String aWhereClause) throws DatabaseFailure { invalidateStore(); Statement stmt = null; @@ -1008,8 +724,8 @@ public class Database implements StorageObject { stmt = con.createStatement(); res = stmt.executeUpdate(sql); } - catch (SQLException sqe) { - throwSQLException(sqe, "delete"); + catch (Throwable e) { + throw new DatabaseFailure(e); } finally { freeConnection(con, stmt); @@ -1025,30 +741,25 @@ public class Database implements StorageObject { return false; } - /** - * Diese Methode fuehrt den Sqlstring sql aus und timed im Logfile. - * @param stmt Statemnt - * @param sql Sql-String - */ public ResultSet executeSql(Statement stmt, String sql) - throws StorageObjectFailure, SQLException { + throws DatabaseFailure, SQLException { ResultSet rs; + logQueryBefore(sql); long startTime = System.currentTimeMillis(); - try { rs = stmt.executeQuery(sql); - logger.info((System.currentTimeMillis() - startTime) + "ms. for: " + sql); + logQueryAfter(sql, (System.currentTimeMillis() - startTime)); } catch (SQLException e) { - logger.error(e.getMessage() +"\n" + (System.currentTimeMillis() - startTime) + "ms. for: " + sql); + logQueryError(sql, (System.currentTimeMillis() - startTime), e); throw e; } return rs; } - private Map processRow(ResultSet aResultSet) throws StorageObjectFailure { + private Map processRow(ResultSet aResultSet) throws DatabaseFailure { try { Map result = new HashMap(); ResultSetMetaData metaData = aResultSet.getMetaData(); @@ -1060,11 +771,15 @@ public class Database implements StorageObject { return result; } catch (Throwable e) { - throw new StorageObjectFailure(e); + throw new DatabaseFailure(e); } } - public List executeFreeSql(String sql, int aLimit) throws StorageObjectFailure, StorageObjectExc { + /** + * Executes 1 sql statement and returns the results as a List of + * Maps + */ + public List executeFreeSql(String sql, int aLimit) throws DatabaseFailure, DatabaseExc { Connection connection = null; Statement statement = null; try { @@ -1084,33 +799,40 @@ public class Database implements StorageObject { return result; } catch (Throwable e) { - throw new StorageObjectFailure(e); + throw new DatabaseFailure(e); } finally { if (connection!=null) { freeConnection(connection, statement); } } - }; + } - public Map executeFreeSingleRowSql(String anSqlStatement) throws StorageObjectFailure, StorageObjectExc { + /** + * 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); - else - return null; + return null; } finally { } } catch (Throwable t) { - throw new StorageObjectFailure(t); + throw new DatabaseFailure(t); } - }; + } - public String executeFreeSingleValueSql(String sql) throws StorageObjectFailure, StorageObjectExc { + /** + * 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) @@ -1119,23 +841,20 @@ public class Database implements StorageObject { Iterator i = row.values().iterator(); if (i.hasNext()) return (String) i.next(); - else - return null; - }; + return null; + } - public int getSize(String where) throws SQLException, StorageObjectFailure { + 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, StorageObjectFailure { - - long startTime = System.currentTimeMillis(); + public int getSize(String mainTablePrefix, List extraTables, String where) throws SQLException, DatabaseFailure { String useTable = mainTable; if (mainTablePrefix!=null && mainTablePrefix.trim().length()>0) { - useTable+=" "+mainTablePrefix; + useTable+=" "+mainTablePrefix; } StringBuffer countSql = new StringBuffer("select count(*) from ").append(useTable); @@ -1155,6 +874,8 @@ public class Database implements StorageObject { Connection con = null; Statement stmt = null; int result = 0; + logQueryBefore(countSql.toString()); + long startTime = System.currentTimeMillis(); try { con = obtainConnection(); @@ -1172,23 +893,25 @@ public class Database implements StorageObject { finally { freeConnection(con, stmt); } - logger.info((System.currentTimeMillis() - startTime) + "ms. for: " + countSql); + logQueryAfter(countSql.toString(), (System.currentTimeMillis() - startTime)); return result; } public int executeUpdate(Statement stmt, String sql) - throws StorageObjectFailure, SQLException { + throws DatabaseFailure, SQLException { int rs; + + logQueryBefore(sql); long startTime = System.currentTimeMillis(); try { rs = stmt.executeUpdate(sql); - logger.info((System.currentTimeMillis() - startTime) + "ms. for: " + sql); + logQueryAfter(sql, (System.currentTimeMillis() - startTime)); } catch (SQLException e) { - logger.error("Failed: " + (System.currentTimeMillis() - startTime) + "ms. for: " + sql); + logQueryError(sql, (System.currentTimeMillis() - startTime), e); throw e; } @@ -1196,109 +919,90 @@ public class Database implements StorageObject { } public int executeUpdate(String sql) - throws StorageObjectFailure, SQLException { + throws DatabaseFailure, SQLException { int result = -1; - long startTime = System.currentTimeMillis(); 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) { - logger.error("Database.executeUpdate(" + sql + "): " + e.getMessage()); - throw new StorageObjectFailure("Database.executeUpdate(" + sql + "): " + e.getMessage(), e); + logQueryError(sql, System.currentTimeMillis() - startTime, e); + throw new DatabaseFailure("Database.executeUpdate(" + sql + "): " + e.getMessage(), e); } finally { freeConnection(con, pstmt); } - - logger.info((System.currentTimeMillis() - startTime) + "ms. for: " + sql); return result; } /** - * Wertet ResultSetMetaData aus und setzt interne Daten entsprechend - * @param md ResultSetMetaData + * Processes the metadata for the table this Database object is responsible for. */ - private void evalMetaData(ResultSetMetaData md) throws StorageObjectFailure { - this.evaluatedMetaData = true; - this.metadataFields = new ArrayList(); - this.metadataLabels = new ArrayList(); - this.metadataNotNullFields = new ArrayList(); + private void processMetaData(ResultSetMetaData aMetaData) throws DatabaseFailure { + fieldNames = new ArrayList(); + fieldNameToType = new HashMap(); try { - int numFields = md.getColumnCount(); - this.metadataTypes = new int[numFields]; - - String aField; - int aType; + int numFields = aMetaData.getColumnCount(); + fieldTypes = new int[numFields]; 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(primaryKeyField)) { - } - - if (md.isNullable(i) == ResultSetMetaData.columnNullable) { - metadataNotNullFields.add(aField); - } + fieldNames.add(aMetaData.getColumnName(i)); + fieldTypes[i - 1] = aMetaData.getColumnType(i); + fieldNameToType.put(aMetaData.getColumnName(i), new Integer(aMetaData.getColumnType(i))); } } - catch (SQLException e) { - throwSQLException(e, "evalMetaData"); + catch (Throwable e) { + throw new DatabaseFailure(e); } } /** - * Wertet die Metadaten eines Resultsets fuer eine Tabelle aus, - * um die alle Columns und Typen einer Tabelle zu ermitteln. + * Retrieves metadata from the table this Database object represents */ - private void get_meta_data() throws StorageObjectFailure { - Connection con = null; - PreparedStatement pstmt = null; + private void acquireMetaData() throws DatabaseFailure { + Connection connection = null; + PreparedStatement statement = null; String sql = "select * from " + mainTable + " where 0=1"; try { - con = obtainConnection(); - pstmt = con.prepareStatement(sql); + connection = obtainConnection(); + statement = connection.prepareStatement(sql); logger.debug("METADATA: " + sql); - ResultSet rs = pstmt.executeQuery(); - evalMetaData(rs.getMetaData()); - rs.close(); + ResultSet resultSet = statement.executeQuery(); + try { + processMetaData(resultSet.getMetaData()); + } + finally { + resultSet.close(); + } } - catch (SQLException e) { - throwSQLException(e, "get_meta_data"); + catch (Throwable e) { + throw new DatabaseFailure(e); } finally { - freeConnection(con, pstmt); + freeConnection(connection, statement); } } - public Connection obtainConnection() throws StorageObjectFailure { + public Connection obtainConnection() throws DatabaseFailure { try { return MirGlobal.getDatabaseEngine().obtainConnection(); } catch (Exception e) { - throw new StorageObjectFailure(e); + throw new DatabaseFailure(e); } } - public void freeConnection(Connection aConnection, Statement aStatement) throws StorageObjectFailure { - try { - aStatement.close(); - } - catch (Throwable t) { - logger.warn("Can't close statemnet: " + t.toString()); - } - + public void freeConnection(Connection aConnection) throws DatabaseFailure { try { MirGlobal.getDatabaseEngine().releaseConnection(aConnection); } @@ -1307,62 +1011,35 @@ public class Database implements StorageObject { } } - /** - * Wertet SQLException aus und wirft dannach eine StorageObjectException - * @param sqe SQLException - * @param aFunction Funktonsname, in der die SQLException geworfen wurde - */ - 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(); + public void freeConnection(Connection aConnection, Statement aStatement) throws DatabaseFailure { + try { + aStatement.close(); + } + catch (Throwable t) { + logger.warn("Can't close statement", t); } - String information = - "SQL Error: " + - "state= " + state + - ", vendor= " + vendor + - ", message=" + message + - ", function= " + aFunction; - - logger.error(information); - - throw new StorageObjectFailure(information, sqe); + freeConnection(aConnection); } protected void _throwStorageObjectException(Exception e, String aFunction) - throws StorageObjectFailure { + throws DatabaseFailure { if (e != null) { logger.error(e.getMessage() + aFunction); - throw new StorageObjectFailure(aFunction, e); + throw new DatabaseFailure(aFunction, e); } } - /** - * Loggt Fehlermeldung mit dem Parameter Message und wirft dannach - * eine StorageObjectException - * @param aMessage Nachricht mit dem Fehler - * @exception StorageObjectFailure - */ - void throwStorageObjectException(String aMessage) throws StorageObjectFailure { - logger.error(aMessage); - throw new StorageObjectFailure(aMessage, null); - } /** * Invalidates any cached entity list */ private void invalidateStore() { - // invalidating all EntityLists corresponding with theEntityClass - if (StoreUtil.extendsStorableEntity(theEntityClass)) { + // invalidating all EntityLists corresponding with entityClass + if (StoreUtil.extendsStorableEntity(entityClass)) { StoreContainerType stoc_type = - StoreContainerType.valueOf(theEntityClass, StoreContainerType.STOC_TYPE_ENTITYLIST); + StoreContainerType.valueOf(entityClass, StoreContainerType.STOC_TYPE_ENTITYLIST); o_store.invalidate(stoc_type); } } @@ -1370,11 +1047,10 @@ public class Database implements StorageObject { /** * Retrieves a binary value */ - public InputStream getBinaryField(String aQuery) throws StorageObjectFailure, SQLException { + public byte[] getBinaryField(String aQuery) throws DatabaseFailure, SQLException { Connection connection=null; Statement statement=null; InputStream inputStream; - InputStream imageInputStream = null; try { connection = obtainConnection(); @@ -1385,56 +1061,79 @@ public class Database implements StorageObject { if(resultSet!=null) { if (resultSet.next()) { - inputStream = resultSet.getBlob(1).getBinaryStream(); - imageInputStream = new BinaryFieldInputStream(inputStream, connection, statement); + 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)); - 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()); - } - - throw new StorageObjectFailure(t); + throw new DatabaseFailure(t); } - return imageInputStream; + return new byte[0]; } /** - * Sets a binary value. The query is supposed to contain 1 ? denoting where the - * binary value should be inserted. - * - * e.g. update images set image_data = ? where id= 22 + * Sets a binary value for a particular field in a record specified by its identifier */ - public void setBinaryField(String aQuery, byte aData[]) throws StorageObjectFailure, SQLException { + public void setBinaryField(String aFieldName, String anObjectId, byte aData[]) throws DatabaseFailure, SQLException { PreparedStatement statement = null; Connection connection = obtainConnection(); + try { connection.setAutoCommit(false); try { - statement = connection.prepareStatement(aQuery); - statement.setBinaryStream(1, new ByteArrayInputStream(aData), aData.length); - statement.execute(); - connection.commit(); + // 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); @@ -1446,33 +1145,41 @@ public class Database implements StorageObject { } /** - * a small wrapper class that allows us to store the DB connection resources - * that the BlobInputStream is using and free them upon closing of the stream + * Can be overridden to specify a primary key sequence name not named according to + * the convention (tablename _id_seq) */ - private class BinaryFieldInputStream extends InputStream { - InputStream inputStream; - Connection connection; - Statement statement; + protected String getPrimaryKeySequence() { + return mainTable+"_id_seq"; + } - public BinaryFieldInputStream(InputStream aBlobInputStream, Connection aConnection, Statement aStatement ) { - inputStream = aBlobInputStream; - connection = aConnection; - statement = aStatement; - } + /** + * 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); + } - public void close () throws IOException { - inputStream.close(); - try { - connection.setAutoCommit(true); - freeConnection(connection, statement); - } - catch (Exception e) { - throw new IOException("close(): "+e.toString()); - } - } + private void logQueryBefore(String aQuery) { + logger.debug("about to perform QUERY " + aQuery); +// (new Throwable()).printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE)); + } - public int read() throws IOException { - return inputStream.read(); + 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(); } }