Package de.danet.an.util.persistentmaps

Source Code of de.danet.an.util.persistentmaps.JDBCPersistentMap$MapValue

/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* $Id: JDBCPersistentMap.java 2893 2009-01-20 17:02:33Z drmlipp $
*
* $Log$
* Revision 1.4  2006/09/29 12:32:13  drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.3  2005/04/22 15:10:49  drmlipp
* Merged changes from 1.3 branch up to 1.3p15.
*
* Revision 1.1.1.3.6.5  2005/04/14 11:45:07  drmlipp
* More (minor) optimizations.
*
* Revision 1.1.1.3.6.4  2005/04/13 16:14:06  drmlipp
* Optimized db access.
*
* Revision 1.1.1.3.6.3  2005/04/11 14:33:11  drmlipp
* Improved message.
*
* Revision 1.2  2005/04/08 11:28:03  drmlipp
* Merged changes from 1.3 branch up to 1.3p6.
*
* Revision 1.1.1.3.6.2  2005/03/18 21:59:00  drmlipp
* Fixed db access optimization.
*
* Revision 1.1.1.3.6.1  2005/03/18 15:02:00  drmlipp
* Optimized (reduced number of DB accesses).
*
* Revision 1.1.1.3  2004/08/18 15:17:35  drmlipp
* Update to 1.2
*
* Revision 1.37  2004/02/09 14:57:49  lipp
* Added support for map id of type Long.
*
* Revision 1.36  2003/11/27 16:26:09  lipp
* Added specific exception type to allow reconstruction of SQLException
* when using JDBCPersistence.
*
* Revision 1.35  2003/11/03 12:41:23  lipp
* Added null vs. "" support for Oracle.
*
* Revision 1.34  2003/06/27 08:51:46  lipp
* Fixed copyright/license information.
*
* Revision 1.33  2003/05/23 12:11:13  lipp
* Fixed driver/db characteristics caching.
*
* Revision 1.32  2003/04/11 13:21:04  lipp
* Fixed NullPointerException
*
* Revision 1.31  2003/03/28 10:09:20  lipp
* Intriduced logging using commons-logging.
*
* Revision 1.30  2003/03/06 15:23:03  lipp
* Some special handling for oracle.
*
* Revision 1.29  2003/03/06 13:47:45  schlue
* Handling of null values fixed.
*
* Revision 1.28  2003/03/05 13:18:32  schlue
* Fixed column names for map and item removed (now dynamic).
*
* Revision 1.27  2003/03/05 09:42:39  lipp
* Temporary fix for UniversalPrepStmt.
*
* Revision 1.26  2003/02/21 16:38:51  lipp
* Introduced UniversalPrepStmt.
*
* Revision 1.25  2003/02/19 12:23:14  lipp
* Adapted parameter sequence for setBinary to usual style.
*
* Revision 1.24  2003/02/18 15:20:38  schlue
* svalue handling fixed.
*
* Revision 1.23  2003/02/05 15:34:22  lipp
* Removed unnecessary exception.
*
* Revision 1.22  2002/11/19 15:20:31  lipp
* Removed not needed semicolon.
*
* Revision 1.21  2002/10/08 11:57:03  schlue
* Code fixed for changing a connection with non-saved modifications.
* Problems with serialization fixed.
* Formal corrections acc. to style checker.
*
* Revision 1.20  2002/08/30 07:24:25  schlue
* isModified added.
*
* Revision 1.19  2002/08/26 20:23:13  lipp
* Lots of method renames.
*
* Revision 1.18  2002/08/26 14:17:07  lipp
* JavaDoc fixes.
*
* Revision 1.17  2002/08/26 09:43:31  schlue
* Code optimizations.
*
* Revision 1.16  2002/08/23 13:17:09  schlue
* Modifications and fixes acc. to code review.
*
* Revision 1.15  2002/08/22 17:17:26  schlue
* Testing of threshold for svalue against DB schema implemented.
* New utility operation for determining column length added.
* Optional oracle test environment.
*
* Revision 1.14  2002/08/22 10:58:53  schlue
* Utility method to determine column length added.
*
* Revision 1.13  2002/08/22 08:36:07  schlue
* Max key length added.
* Garbage collection optimzed.
*
* Revision 1.12  2002/08/20 09:34:34  lipp
* Removed static.
*
* Revision 1.11  2002/08/20 08:47:49  schlue
* Default value for maximum key length added.
* Advanced error/warning logging.
*
* Revision 1.10  2002/08/19 19:23:40  lipp
* Various minor improvements.
*
* Revision 1.9  2002/08/19 15:15:04  lipp
* Added logger.
*
* Revision 1.8  2002/08/19 13:38:50  schlue
* Determination of maximum key length added.
* clear() operation optimized.
*
* Revision 1.7  2002/08/17 19:00:57  lipp
* Made map id changeable.
*
* Revision 1.6  2002/08/15 16:57:20  schlue
* Performance optimization: Optional usage of statement batches.
*
* Revision 1.5  2002/04/03 12:53:04  lipp
* JavaDoc fixes.
*
* Revision 1.4  2001/12/10 09:15:17  schlue
* javadoc warnings corrected
*
* Revision 1.3  2001/12/07 14:39:54  schlue
* Modifications acc. to review
*
* Revision 1.2  2001/12/07 12:33:17  schlue
* Implementation of persistent map finished
*
* Revision 1.1  2001/12/04 15:37:51  lipp
* New package for persistent maps.
*
*/

package de.danet.an.util.persistentmaps;

import java.io.IOException;
import java.io.Serializable;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import de.danet.an.util.JDBCUtil;
import de.danet.an.util.PersistentMap;
import de.danet.an.util.UniversalPrepStmt;
import de.danet.an.util.persistentmaps.JDBCPersistentMap.DAO.EntryId;


/**
* This class provides an implementation of
* {@link de.danet.an.util.PersistentMap <code>PersistentMap</code>} that
* uses JDBC to implement the persistence.<P>
*
* The class uses an abstract SQL table model. The table consists of
* a column for the map id, the entry key and two value
* entries.<P>
*
* All columns except the second value entry are of type string,
* with lengths as appropriate for the
* application. The second value field is of type BLOB (binary large
* object) and is used if either the value to be stored is not
* of type {@link java.lang.String <code>String</code>} or longer
* than a given limit.
*
* The map id enables storing of several
* jdbc persistent maps in a single table. It is passed as paramter
* to all constructors of this class. Only a single instance for a
* map id may be created as creating two instances may
* corrupt caching.<P>
*
* The SQL statements used to retrieve and save data are configurable.
* Thus any table or view may be used as a base for a jdbc persistent map.<P>
*
* <b>Note that this implementation is not synchronized.</b> If multiple
* threads access this map concurrently, and at least one of the threads
* modifies the map structurally, it must be synchronized externally.
* (A structural modification is any operation that adds or deletes one or more
* mappings; merely changing the value associated with a key that an instance
* already contains is not a structural modification.) This is typically
* accomplished by synchronizing on some object that naturally encapsulates the
* map.
* This can be done at creation time, to prevent accidental unsynchronized
* access to the map:
* <code>JDBCPersistentMap pm = new JDBCPersistentMap(...);</code>
* <code>Map m = Collections.synchronizedMap(pm);</code>
* Since only the methods defined by Map are synchronized by this
* mechanism, all supplemented methods (like 'load' or 'store' have to be
* synchronized on the map object 'm', created above, e.g.:
* <code>synchronized(m) {</code>
* <code>    pm.load(); // Must be in the synchronized block</code>
* <code>}</code><P>
*
* The class is serializable. Note, however, that the database connection
* is transient.
*/
public class JDBCPersistentMap extends HashMap
    implements PersistentMap, Serializable  {
    private static final org.apache.commons.logging.Log logger
  = org.apache.commons.logging.LogFactory.getLog(JDBCPersistentMap.class);

    /**
     * The map id used by this map.
     */
    private Object id = null;

    /**
     * The database table name used for this map.
     */
    private String dbTableName = null;

    /**
     * The column name of the "map" key attribute.
     * Modifications are triggered by changes to the update statement.
     */
    private String mapColumnName = "MAPID";

    /**
     * The column name of the "item" key attribute.
     * Modifications are triggered by changes to the update statement.
     */
    private String itemColumnName = "ITEM";

    /**
     * The SQL statement used to retrieve data.
     */
    private String loadStatement = null;

    /**
     * The SQL statement used to insert data.
     */
    private String insertStatement = null;

    /**
     * The SQL statement used to update data.
     */
    private String updateStatement = null;

    /**
     * The SQL statement used to delete data.
     */
    private String deleteStatement = null;

    /**
     * The SQL statement used to delete all data of a map.
     */
    private String deleteAllStatement = null;

    /**
     * The maximum length of the string value in the database.
     */
    private int sValueMax = 256;

    /**
     * The associated data source.
     */
    private transient DataSource dataSource = null;

    /**
     * The database URL of the current configuration.
     */
    private transient String configDbUrl = null;

    /**
     * Indicated that the dataSource has changed;
     */
    private transient boolean dataSourceChanged = true;

    /**
     * The database connection used for store and load.
     */
    private transient Connection dbConnection = null;

    /**
     * The databse properties.
     */
    private transient JDBCUtil.DBProperties dbProps = null;

    /** Indicates that map id has never been used before. */
    private boolean isNewMap = false;

    /**
     * Flag, indicating if the current map has already been loaded.
     */
    private boolean hasBeenLoaded = false;

    /**
     * Flag, indicating if the current map has been cleared.
     */
    private boolean hasBeenCleared = false;

    /**
     * Flag, indicating if there are any non-saved modifications.
     */
    private boolean isModified = false;

    /**
     * List of inserted keys to map entries.
     */
    private Set insertList = new HashSet();

    /**
     * List of updated keys to map entries.
     */
    private Set updateList = new HashSet();

    /**
     * List of deleted map entries.
     */
    private Set deleteList = new HashSet();

    /**
     * Prepared statement for loading map entries.
     */
    private transient PreparedStatement loadPStmt = null;

    /**
     * Prepared statement for inserting map entries.
     */
    private transient UniversalPrepStmt insertPStmt = null;

    /**
     * Prepared statement for updating map entries.
     */
    private transient UniversalPrepStmt updatePStmt = null;

    /**
     * Prepared statement for deleting map entries.
     */
    private transient PreparedStatement deletePStmt = null;

    /**
     * Prepared statement for deleting all map entries.
     */
    private transient PreparedStatement deleteAllPStmt = null;

    /**
     * Class for storing string/binary element of a map value.
     */
    private class MapValue {
  String svalue = null;
  Object bvalue = null;
    }

    /**
     * Returns a new JDBC persistent map. The map is formed by all entries
     * in the underlying table with the given map id. The
     * table used by the default SQL statements is
     * <code>DEFAULTPERSISTENCEMAP</code>.
     *
     * @param dataSource the data source that will be used to obtain
     * connections.
     * @param mapId the map id.
     * @throws SQLException if a database related error occurs
     */
    public JDBCPersistentMap (DataSource dataSource, String mapId)
  throws SQLException {
  this (dataSource, mapId, "DEFAULTPERSISTENCEMAP");
    }

    /**
     * Returns a new JDBC persistent map. The map is formed by all entries
     * in the underlying table with the given map id. The
     * table used by the default SQL statements is
     * <code>DEFAULTPERSISTENCEMAP</code>.
     *
     * @param dataSource the data source that will be used to obtain
     * connections.
     * @param mapId the map id.
     * @throws SQLException if a database related error occurs
     */
    public JDBCPersistentMap (DataSource dataSource, Long mapId)
  throws SQLException {
  this (dataSource, mapId, "DEFAULTPERSISTENCEMAP");
    }

    /**
     * Returns a new JDBC persistent map. The map is formed by all entries
     * in the underlying table with the given persistence group.
     *
     * @param dataSource the data source that will be used to obtain
     * connections.
     * @param mapId the map id.
     * @param dbtable the table used in the default SQL statements.
     * @throws SQLException if a database related error occurs
     */
    public JDBCPersistentMap
  (DataSource dataSource, String mapId, String dbtable)
  throws SQLException {
  init (dataSource, mapId, dbtable);
    }

    /**
     * Returns a new JDBC persistent map. The map is formed by all entries
     * in the underlying table with the given persistence group.
     *
     * @param dataSource the data source that will be used to obtain
     * connections.
     * @param mapId the map id.
     * @param dbtable the table used in the default SQL statements.
     * @throws SQLException if a database related error occurs
     */
    public JDBCPersistentMap
  (DataSource dataSource, Long mapId, String dbtable)
  throws SQLException {
  init (dataSource, mapId, dbtable);
    }

    private void init (DataSource ds, Object mapId, String dbtable)
  throws SQLException {
  setDataSource (ds);
  id = mapId;
  dbTableName = dbtable;
  loadStatement = "SELECT ITEM, SVALUE, BVALUE FROM "
      + dbtable + " WHERE MAPID = ?";
  insertStatement = "INSERT INTO " + dbtable
      + " (MAPID, ITEM, SVALUE, BVALUE) VALUES (?, ?, ?, ?)";
  updateStatement = "UPDATE " + dbtable
      + " SET SVALUE = ?, BVALUE = ? WHERE MAPID = ? AND ITEM = ?";
  deleteStatement = "DELETE FROM " + dbtable
      + " WHERE MAPID = ? AND ITEM = ?";
  deleteAllStatement = "DELETE FROM " + dbtable
      + " WHERE MAPID = ?";
    }

    /**
     * Set the data source for this map. This must be called after
     * deserialization. Calling this method resets the connection and
     * must therefore be followed by a call to {@link #setConnection
     * <code>setConnection</code>}.
     * @param dataSource the data source that will be used to obtain
     * connections.
     * @throws SQLException if a database related error occurs
     */
    public void setDataSource (DataSource dataSource)
  throws SQLException {
  setConnection (null);
  this.dataSource = dataSource;
  dataSourceChanged = true;
    }

    /**
     * Sets the map id for this map. Clears the cached modifications
     * as a side effect if the map id differs from the current map id.
     *
     * @param mapId the new map id.
     * @see #getMapId
     */
    public void setMapId (String mapId) {
  if (mapId.equals (id)) {
      return;
  }
  clearModifications ();
  id = mapId;
    }

    /**
     * Sets the map id for this map. Clears the cached modifications
     * as a side effect if the map id differs from the current map id.
     *
     * @param mapId the new map id.
     * @see #getMapId
     */
    public void setMapId (Long mapId) {
  if (mapId.equals (id)) {
      return;
  }
  clearModifications ();
  id = mapId;
    }

    private void clearModifications () {
  super.clear ();
  insertList.clear();
  updateList.clear();
  deleteList.clear();
  hasBeenLoaded = false;
  hasBeenCleared = false;
  isModified = false;
  isNewMap = false;
    }

    /**
     * May be called after constructor or <code>setMapId</code> and
     * before the first call to <code>store()</code> to indicate that
     * the map is newly created. This allows the persistent map
     * implementation to optimize the storage bahaviour.
     */
    public void setNewMap () {
  isNewMap = true;
    }

    /**
     * Returns the map id of this map.
     * @return the map id.
     * @see #setMapId
     */
    public Object getMapId () {
  return id;
    }

    /**
     * Sets the SQL statement used to retrieve all entries for this map
     * from the database table. The default is
     * <pre>SELECT ITEM, SVALUE, BVALUE FROM <i>table</i> WHERE MAPID = ?</pre>
     * where <code><i>table</i></code> defaults to
     * <code>DEFAULTPERSISTENCEMAP</code> and can be set by calling the
     * {@link #JDBCPersistentMap(String,String) extended constructor}.
     * <P><b>NOTE: The statement must declare semantically corresponding
     * columns for all the selection and restriction values described in
     * the example above!</b></P>
     *
     * @param statement the load statement.
     * @see #getLoadStatement
     */
    public void setLoadStatement(String statement) {
  loadStatement = statement;
  try {
      loadPStmt = (UniversalPrepStmt)
    closePreparedStatement(loadPStmt);
  } catch (SQLException e) {
      logger.warn ("Problem while closing prepared statement: "
       + e.getMessage ());
  }
    }

    /**
     * Returns the SQL statement used to retrieve all entries for this
     * map from the database table.
     *
     * @return the SQL statement.
     * @see #setLoadStatement
     */
    public String getLoadStatement () {
  return loadStatement;
    }

    /**
     * Sets the SQL statement used to insert an entry for this map
     * in the database table. The default is
     * <pre>INSERT INTO <i>table</i> (MAPID, ITEM, SVALUE, BVALUE)
     * VALUES (?, ?, ?, ?)</pre>
     * where <code><i>table</i></code> defaults to
     * <code>DEFAULTPERSISTENCEMAP</code> and can be set by calling the
     * {@link #JDBCPersistentMap(String,String) extended constructor}.
     * <P><b>NOTE: The statement must declare semantically corresponding
     * columns for all the values described in the example above!</b></P>
     *
     * @param statement the insert statement.
     * @see #getInsertStatement
     */
    public void setInsertStatement(String statement) {
  insertStatement = statement;
  try {
      insertPStmt = (UniversalPrepStmt)
    closePreparedStatement(insertPStmt);
  } catch (SQLException e) {
      logger.warn ("Problem while closing prepared statement: "
       + e.getMessage ());
  }
    }

    /**
     * Returns the SQL statement used to insert an entry for this
     * map into the database table.
     *
     * @return the SQL statement.
     * @see #setInsertStatement
     */
    public String getInsertStatement () {
  return insertStatement;
    }

    /**
     * Sets the SQL statement used to update an entry for this map in
     * the database table. The default is <pre>UPDATE <i>table</i> SET
     * SVALUE = ?, BVALUE = ? WHERE MAPID=? AND ITEM=?</pre> where
     * <code><i>table</i></code> defaults to
     * <code>DEFAULTPERSISTENCEMAP</code> and can be set by calling
     * the {@link #JDBCPersistentMap(String,String) extended
     * constructor}<P><b>NOTE: The statement must declare
     * semantically corresponding columns for all the data and
     * restriction values described in the example above! The names of
     * the key columns in the restriction phrase have to be in the
     * same order as above (first: map-ident, second: item-ident)</b></P>
     *
     * @param statement the update statement.
     * @see #getUpdateStatement
     */
    public void setUpdateStatement(String statement) {
  int startWhere = statement.toLowerCase().indexOf("where");
  if ( startWhere == -1 ) {
      throw new IllegalArgumentException("No WHERE clause in statement.");
  }
  updateStatement = statement;
  try {
      updatePStmt = (UniversalPrepStmt)
    closePreparedStatement(updatePStmt);
  } catch (SQLException e) {
      logger.warn ("Problem while closing prepared statement: "
       + e.getMessage ());
  }
  // Determine key columns names
  mapColumnName = null;
  itemColumnName = null;
  String whereClause = updateStatement.substring(startWhere);
  StringTokenizer wtok = new StringTokenizer(whereClause);
  int tokCnt = 0;
  while (wtok.hasMoreTokens()) {
      String tok = wtok.nextToken();
      if (tokCnt == 1) {
    mapColumnName = tok;
      } else if (tokCnt == 5) {
    itemColumnName = tok;
      }
      tokCnt++;
  }
  if (mapColumnName == null) {
      throw new IllegalArgumentException
    ("Cannot find map name column in WHERE clause of \""
     + statement + "\"");
  }
  if (itemColumnName == null) {
      throw new IllegalArgumentException
    ("Cannot find item name column in WHERE clause of \""
     + statement + "\"");
  }
    }

    /**
     * Returns the SQL statement used to update an entry for this
     * map in the database table.
     *
     * @return the SQL statement.
     * @see #setUpdateStatement
     */
    public String getUpdateStatement () {
  return updateStatement;
    }

    /**
     * Sets the SQL statement used to delete an entry for this map
     * in the database table. The default is
     * <pre>DELETE FROM <i>table</i> WHERE MAPID=? AND ITEM=?</pre>
     * where <code><i>table</i></code> defaults to
     * <code>DEFAULTPERSISTENCEMAP</code> and can be set by calling the
     * {@link #JDBCPersistentMap(String,String) extended constructor}.
     * <P><b>NOTE: The statement must declare semantically corresponding
     * columns for all the restriction values described in the example
     * above!</b></P>
     *
     * @param statement the delete statement.
     * @see #getDeleteStatement
     */
    public void setDeleteStatement(String statement) {
  deleteStatement = statement;
  deletePStmt = null;
    }

    /**
     * Returns the SQL statement used to delete an entry for this
     * map in the database table.
     *
     * @return the SQL statement.
     * @see #setDeleteStatement
     */
    public String getDeleteStatement () {
  return deleteStatement;
    }

    /**
     * Sets the SQL statement used to delete all entries for this map
     * in the database table. The default is
     * <pre>DELETE FROM <i>table</i> WHERE MAPID=?</pre>
     * where <code><i>table</i></code> defaults to
     * <code>DEFAULTPERSISTENCEMAP</code> and can be set by calling the
     * {@link #JDBCPersistentMap(String,String) extended constructor}.
     * <P><b>NOTE: The statement must declare semantically corresponding
     * columns for all the restriction values described in the example
     * above!</b></P>
     *
     * @param statement the delete statement.
     * @see #getDeleteAllStatement
     */
    public void setDeleteAllStatement(String statement) {
  deleteAllStatement = statement;
  deleteAllPStmt = null;
    }

    /**
     * Returns the SQL statement used to delete all entries for this
     * map in the database table.
     *
     * @return the SQL statement.
     * @see #setDeleteAllStatement
     */
    public String getDeleteAllStatement () {
  return deleteAllStatement;
    }

    /**
     * Sets the maximum length for string value in the
     * <code>SVALUE</code> column of the database table.  The default
     * is <code>256</code>.  If the given length value is greater than
     * the column length in the database scheme (a DB connection that
     * supports column information assumed), the length value is restricted
     * to the DB column length.
     * @param limit the maximum length for values in the <code>SVALUE</code>
     * column.
     * @see #getSValueMax
     */
    public void setSValueMax (int limit) {
  if (limit <= 0) {
      throw new IllegalArgumentException
    ("limit must be greater than zero");
  }
  if (dbConnection == null) {
      sValueMax = limit;
      return;
  }
  try {
      int sLength = JDBCUtil.getTableColumnSize
    (dbConnection, dbTableName, "SVALUE", limit);
      if (limit > sLength) {
    logger.warn
        ("Illegal limit (" + limit + ") for maximum string value. "
         + "Using column length from database schema ("
         + sLength + ").");
    sValueMax = sLength;
      } else {
    sValueMax = limit;
      }
  } catch (SQLException exc) {
      logger.error (exc.getMessage(), exc);
  }
    }

    /**
     * Returns the maximum length for string value in the <code>SVALUE</code>
     * column of the database table.
     *
     * @return the current maximum length
     * @see #setSValueMax
     */
    public int getSValueMax() {
  return sValueMax;
    }

    /**
     * Sets the database connection used in {@link #load <code>load</code>}
     * and {@link #store <code>store</code>}.
     * If connection is closed or changes, all prepared statements are
     * released.
     * If a valid connection with a different database url (compared with
     * the previous valid connection) is set, the svalue threshold is
     * validated if it hasn't been checked before (@see #setSValueMax).
     * @param con the Connection.
     * @throws SQLException from the underlying JDBC calls
     * @see #getConnection
     */
    public void setConnection (Connection con) throws SQLException {
  boolean sameConnection
      = ((con == null) && (dbConnection == null))
      || ((con != null) && (dbConnection != null)
    && (dbConnection.equals(con)));
  if ((con == null) || !sameConnection) {
      // Close all prepared statements
      loadPStmt = closePreparedStatement(loadPStmt);
      insertPStmt = (UniversalPrepStmt)
    closePreparedStatement(insertPStmt);
      updatePStmt = (UniversalPrepStmt)
    closePreparedStatement(updatePStmt);
      deletePStmt = closePreparedStatement(deletePStmt);
      deleteAllPStmt = closePreparedStatement(deleteAllPStmt);
  }
  if ((dataSource == null || dataSourceChanged) && con != null) {
      dbProps = JDBCUtil.dbProperties (dataSource, con);
      String newDbUrl = null;
      boolean sameDb = false;
      if (dataSource == null) {
    newDbUrl = con.getMetaData().getURL();
    sameDb = (configDbUrl != null && newDbUrl.equals (configDbUrl));
    configDbUrl = newDbUrl;
      }
      if (dataSourceChanged || !sameDb) {
    // get table column size for "string" column
    int sLength = JDBCUtil.getTableColumnSize
        (con, dbTableName, "SVALUE", sValueMax);
    if (sValueMax > sLength) {
        logger.warn
      ("Illegal limit for maximum string value. "
       + "Using column length from database schema.");
        sValueMax = sLength;
    }
    // Simulate that all current values have just
    // been inserted, so that they will be stored with
    // the naxt call to "store"
    insertList.clear();
    updateList.clear();
    deleteList.clear();
    insertList.addAll(this.keySet());
    hasBeenLoaded = false;
      }
      dataSourceChanged = false;
  }
  dbConnection = con;
    }

    /**
     * Returns the database connection set by
     * {@link #setConnection <code>setConnection</code>}.
     *
     * @return the current connection
     * @see #setConnection
     */
    public Connection getConnection () {
  return dbConnection;
    }

    /**
     * Retrieves the information about the maximum string length of key
     * entries.
     * @return the maximum string length of key entries
     * @throws IOException problems while storing the map
     */
    public int maxKeyLength () throws IOException {
  if (dbConnection == null) {
      throw new IllegalStateException ("No connection set");
  }
  final int defaultMaxKeyLength = 50;
  try {
      // Request keylength value from database schema
      int kLength = JDBCUtil.getTableColumnSize
    (dbConnection, dbTableName, mapColumnName, defaultMaxKeyLength);
      return kLength;
  } catch (SQLException exc) {
      throw new PersistentMapSQLException(exc);
 
    }

    /**
     * Loads the map from persistent store.
     * The map is cleared before loading.
     *
     * @throws IOException problems while loading the map
     */
    public void load() throws IOException {
  if (dbConnection == null) {
      throw new IllegalStateException ("No connection set");
  }
  ResultSet result = null;
  try {
      if (loadPStmt == null ) {
    loadPStmt = new UniversalPrepStmt
        (dataSource, dbConnection, loadStatement);
      }
      setMapId (loadPStmt, 1);
      result = loadPStmt.executeQuery();
      // Remove current entries and then add loaded values
      super.clear();
      insertList.clear();
      updateList.clear();
      deleteList.clear();
      while (result.next()) {
    String key = result.getString(1);
    Object value = result.getString(2);
    if (value == null) {
        try {
      value = JDBCUtil.getBinary(result,3);
        } catch (ClassNotFoundException exc) {
      throw new IOException(exc.getMessage());
        }
    }
    super.put(key, value);
      }
      result.close();
  } catch (SQLException exc) {
      throw new PersistentMapSQLException(exc);
  }
  hasBeenLoaded = true;
  isModified = false;
    }
   
    /**
     * Stores the map in persistent store.
     * If the set has not been loaded before, all entries with the current
     * map id are deleted from the persistent store first.
     * Only modified (new, changed, deleted) map entries are changed in the
     * peristent store.
     *
     * @throws IOException problems while storing the map
     */
    public void store() throws IOException {
  if (dbConnection == null) {
      throw new IllegalStateException ("No connection set");
 
  if (!hasBeenLoaded || hasBeenCleared) {
      if (!isNewMap) {
    /* First delete all existing values for that id in the
     * persistent store */
    try {
        if (deleteAllPStmt == null) {
      deleteAllPStmt = new UniversalPrepStmt
          (dataSource, dbConnection, deleteAllStatement);
        }
        setMapId (deleteAllPStmt, 1);
        int hits = deleteAllPStmt.executeUpdate();
    } catch (SQLException exc) {
        throw new PersistentMapSQLException(exc);
    }
      }
      hasBeenLoaded = true;
      hasBeenCleared = false;
  }
  isNewMap = false;
  // Iterate through all the modification tracking lists
  doDelete();
  doInsert();
  doUpdate();
  isModified = false;
    }

    /**
     * Removes all mappings from this map.
     * This implementation ensures consistency between the map and the
     * persistent store.
     *
     */
    public void clear()  {
  super.clear();
  insertList.clear();
  updateList.clear();
  deleteList.clear();
  hasBeenCleared = true;
  isModified = false;
    }

    /**
     * Associates the specified value with the specified key in this map.
     * The key has to have a non-null value.
     * If the map previously contained a mapping for this key, the old value
     * is replaced.
     * This implementation ensures consistency between the map and the
     * persistent store.
     *
     * @param key key with which the specified value is to be associated.
     * @param value value to be associated with the specified key.
     * @return previous value associated with specified key, or null if
     * there was no mapping for key.
     */
    public Object put(Object key, Object value)  {
  if (key == null || !(key instanceof String)) {
      throw new IllegalArgumentException
    ("Key must be non-null and of type String");
  }
  Object oldValue = null;
  if (containsKey(key)) {
      // Existing entry
      oldValue = super.put(key, value);
      // Changing an inserted entry leaves it in state "inserted"
      if (!insertList.contains(key)
    && ((oldValue == null && value != null)
        || (oldValue != null && value == null)
        || (oldValue != null && !oldValue.equals(value)))) {
    updateList.add(key);
      }
  } else {
      // New (or prior deleted) entry
      // Adding a prior deleted entry brings it to state "updated"
      super.put(key, value);
      // The modification lists maybe null during readObject of
      // a JDBCPersistantMap (for the restauration of the map entries)
      if (insertList != null) {
    if (deleteList.contains(key)) {
        deleteList.remove(key);
        updateList.add(key);
    } else {
        insertList.add(key);
    }
      }
  }
  isModified = true;
  return oldValue;
    }
    /**
     * Removes the mapping for this key from this map if present.
     * This implementation ensures consistency between the map and the
     * persistent store.
     *
     * @param key key with which the specified value is to be associated.
     * @return previous value associated with specified key, or null if
     * there was no mapping for key.
     */
    public Object remove(Object key)  {
  if (key == null || !containsKey(key)) {
      return null;
  }
  // If it has been inserted just before, remove all traces
  if (insertList.contains(key)) {
      insertList.remove(key);
  } else {
      // otherwise mark as removed
      // It doesn't matter any more if entry has been updated before
      updateList.remove(key);
      deleteList.add(key);
  }
  isModified = true;
  return super.remove(key);
    }

    /**
     * Copies all of the mappings from the specified map to this map.
     * These mappings will replace any mappings that this map had for any of
     * the keys currently in the specified map.
     * This implementation ensures consistency between the map and the
     * persistent store.
     *
     * @param t Mappings to be stored in this map.
     */
    public void putAll(Map t)  {
  for (Iterator it = t.keySet().iterator(); it.hasNext();) {
      Object key = it.next();
      put(key, t.get(key));
  }
    }

    /**
     * Indicates whether modifications need to be saved currently.
     * @return true, if there are non-saved modifications, otherwise false.
     */
    public boolean isModified () {
  return isModified;
    }

    /**
     * Performs an update of the entry with the given key in the persistent
     * store.
     *
     * @param withBatch flag, indicating if statement batches should be used
     * @throws IOException problems while updating the entry
     * @throws IllegalStateException if no connection has been set
     */
    private void doUpdate() throws IOException {
  try {
      if (updateList.size() == 0) {
    return;
      }
      if (updatePStmt == null) {
    updatePStmt = new UniversalPrepStmt
        (dataSource, dbConnection, updateStatement);
      }
      Iterator keys = updateList.iterator();
      while (keys.hasNext()) {
    final String key = (String)keys.next();
    final MapValue value = getValue(key);
    updatePStmt.setString(1, value.svalue);
          updatePStmt.setBinary(2, value.bvalue);
    setMapId (updatePStmt, 3);
    updatePStmt.setString(4, key);
    if (dbProps.supportsBatchUpdates()) {
        updatePStmt.addBatch();
    } else {
        int hits = updatePStmt.executeUpdate();
        if (hits != 1) {
      throw new IOException("Data store not consistent "
                 + "(hits on update = "
                 + hits + ")!");
        }
    }
      }
      if (dbProps.supportsBatchUpdates()) {
    int[] result = updatePStmt.executeBatch();
    if (result.length != updateList.size()) {
        throw new IOException("Incorrect number of commands "
             + "while executing update batch.");
    } else {
        for (int i = 0; i < result.length; i++) {
      if ((result[i] != 1) && (result[i] != -2)) {
          throw new IOException("Data store not consistent "
               + "(hits on update = "
               + result[i] + ")!");
      }
        }
    }
      }
  } catch (SQLException exc) {
      throw new PersistentMapSQLException(exc);
 
  updateList.clear();
    }
   
    /**
     * Deletes entry of the current map with the given key in the
     * persistent store.
     *
     * @throws IOException problems while deleting the entry
     * @throws IllegalStateException if no connection has been set
     */
    private void doDelete() throws IOException {
  try {
      if (deleteList.size() == 0) {
    return;
      }
      if (deletePStmt == null) {
    deletePStmt = new UniversalPrepStmt
        (dataSource, dbConnection, deleteStatement);
      }
      Iterator keys = deleteList.iterator();
      while (keys.hasNext()) {
    final String key = (String)keys.next();
    setMapId (deletePStmt, 1);
    deletePStmt.setString(2, key);
    if (dbProps.supportsBatchUpdates()) {
        deletePStmt.addBatch();
    } else {
        int hits = deletePStmt.executeUpdate();
        if (hits != 1) {
      throw new IOException("Data store not consistent "
                 + "(hits on delete = "
                 + hits + ")!");
        }
    }
      }
      if (dbProps.supportsBatchUpdates()) {
    int[] result = deletePStmt.executeBatch();
    if (result.length != deleteList.size()) {
        throw new IOException("Incorrect number of commands "
             + "while executing delete batch.");
    } else {
        for (int i = 0; i < result.length; i++) {
      if ((result[i] != 1) && (result[i] != -2)) {
          throw new IOException("Data store not consistent "
               + "(hits on delete = "
               + result[i] + ")!");
      }
        }
    }     
      }
  } catch (SQLException exc) {
      throw new PersistentMapSQLException(exc);
 
  deleteList.clear();
    }
   
    /**
     * Inserts entry of the current map with the given key in the
     * persistent store.
     *
     * @throws IOException problems while inserting the entry
     * @throws IllegalStateException if no connection has been set
     */
    private void doInsert() throws IOException {
  try {
      if (insertList.size() == 0) {
    return;
      }
      if (insertPStmt == null) {
    insertPStmt = new UniversalPrepStmt
        (dataSource, dbConnection, insertStatement,
         new String[] { mapColumnName, itemColumnName});
      }
      Iterator keys = insertList.iterator();
      while (keys.hasNext()) {
    final String key = (String)keys.next();
    final MapValue value = getValue(key);
    setMapId (insertPStmt, 1);
    insertPStmt.setString(2, key);
    insertPStmt.setString(3, value.svalue);
    insertPStmt.setBinary(4, value.bvalue);
    if (dbProps.supportsBatchUpdates()) {
        insertPStmt.addBatch();
    } else {
        int hits = insertPStmt.executeUpdate();
        if (hits != 1) {
      throw new IOException("Data store not consistent "
                + "(hits on insert = "
                + hits + ")!");
        }
    }
      }
      if (dbProps.supportsBatchUpdates()) {
    int[] result = insertPStmt.executeBatch();
    if (result.length != insertList.size()) {
        throw new IOException("Incorrect number of commands "
             + "while executing insert batch.");
    } else {
        for (int i = 0; i < result.length; i++) {
      if ((result[i] != 1) && (result[i] != -2)) {
          throw new IOException("Data store not consistent "
               + "(hits on insert = "
               + result[i] + ")!");
      }
        }
    }
      }
  } catch (SQLException exc) {
      throw new PersistentMapSQLException(exc);
 
  insertList.clear();
    }

    /**
     * Verify if the string will be stored as string, not as object.
     * @param value the string
     * @return <code>true</code> if the string will be stored as "svalue"
     */
    protected boolean isSValue (String value) {
  return (value.length() <= sValueMax)
      && (!dbProps.isOracle() || ((String)value).length() > 0);
    }
   
    /**
     * Returns a map value for a given key.
     *
     * @param key key to retrieve value for.
     * @return value for given key.
     */
    private MapValue getValue(String key) {
  MapValue mapValue = new MapValue();
  Object value = get(key);
  if ((value instanceof String) && isSValue ((String)value)) {
      mapValue.svalue = (String)value;
  } else {
      mapValue.bvalue = value;
  }
  return mapValue;
    }

    /**
     * Sets the map id in a prepared statement.
     * @param stmt the statement
     * @param offset the offset
     */
    private void setMapId (PreparedStatement stmt, int offset)
  throws SQLException {
  if (id instanceof String) {
      stmt.setString(offset, (String)id);
  } else {
      stmt.setLong(offset, ((Long)id).longValue());
  }
    }

    /**
     * Closes an existing prepared statement.
     *
     * @param pStmt the prepared statement to be closed
     * @throws SQLException if no connection has been set
     * @return always null.
     */
    private PreparedStatement closePreparedStatement(PreparedStatement pStmt)
  throws SQLException {
  if (pStmt != null) {
      pStmt.close();
  }
  return null;
    }

    public static class DAO {
       
        public static class EntryId implements Serializable {
            private long mapId;
            private String item;
           
            /**
             * @return Returns the mapId.
             */
            public long getMapId() {
                return mapId;
            }

            /**
             * @param mapId The mapId to set.
             */
            public void setMapId(long mapId) {
                this.mapId = mapId;
            }

            /**
             * @return Returns the item.
             */
            public String getItem() {
                return item;
            }

            /**
             * @param item The item to set.
             */
            public void setItem(String item) {
                this.item = item;
            }

            /* (non-Javadoc)
             * Comment copied from interface or superclass.
             */
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result
                        + ((item == null) ? 0 : item.hashCode());
                result = prime * result + (int) (mapId ^ (mapId >>> 32));
                return result;
            }

            /* (non-Javadoc)
             * Comment copied from interface or superclass.
             */
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null) {
                    return false;
                }
                if (!(obj instanceof EntryId)) {
                    return false;
                }
                EntryId other = (EntryId) obj;
                if (item == null) {
                    if (other.item != null) {
                        return false;
                    }
                } else if (!item.equals(other.item)) {
                    return false;
                }
                if (mapId != other.mapId) {
                    return false;
                }
                return true;
            }
        }

        private long mapId;
        private String item;
        private String svalue;
        private Serializable bvalue;
       
        /**
         * Create a new instance with all attributes initialized
         * to defaults or the given values.
         *
         */
        public DAO() {
            super();
        }

        /**
         * @return Returns the mapId.
         */
        public long getMapId() {
            return mapId;
        }

        /**
         * @param mapId The mapId to set.
         */
        public void setMapId(long mapId) {
            this.mapId = mapId;
        }

        /**
         * @return Returns the item.
         */
        public String getItem() {
            return item;
        }

        /**
         * @param item The item to set.
         */
        public void setItem(String item) {
            this.item = item;
        }

        /**
         * @return Returns the svalue.
         */
        public String getSvalue() {
            return svalue;
        }

        /**
         * @param svalue The svalue to set.
         */
        public void setSvalue(String svalue) {
            this.svalue = svalue;
        }

        /**
         * @return Returns the bvalue.
         */
        public Serializable getBvalue() {
            return bvalue;
        }

        /**
         * @param bvalue The bvalue to set.
         */
        public void setBvalue(Serializable bvalue) {
            this.bvalue = bvalue;
        }
    }
}
TOP

Related Classes of de.danet.an.util.persistentmaps.JDBCPersistentMap$MapValue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
script>