Package de.iritgo.aktera.persist.defaultpersist

Source Code of de.iritgo.aktera.persist.defaultpersist.DefaultPersistent

/**
* This file is part of the Iritgo/Aktera Framework.
*
* Copyright (C) 2005-2011 Iritgo Technologies.
* Copyright (C) 2003-2005 BueroByte GbR.
*
* Iritgo licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package de.iritgo.aktera.persist.defaultpersist;


import EDU.oswego.cs.dl.util.concurrent.Mutex;
import de.iritgo.aktera.persist.DatabaseType;
import de.iritgo.aktera.persist.Helper;
import de.iritgo.aktera.persist.PersistenceException;
import de.iritgo.aktera.persist.Persistent;
import de.iritgo.aktera.persist.PersistentMetaData;
import de.iritgo.aktera.persist.Relation;
import de.iritgo.aktera.persist.Transaction;
import de.iritgo.aktera.util.string.SuperString;
import org.apache.commons.beanutils.PropertyUtils;
import java.beans.PropertyDescriptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;


/**
* @version $Revision: 1.5 $ $Date: 2006/10/05 15:12:02 $
* @author Michael Nash
*/
public class DefaultPersistent extends DefaultPersistentBase implements Persistent
{
  /**
   * Add a new record to the target table. Assumes that the fields of this
   * object are populated with data for the new record. All key fields at
   * least must be supplied with values, and all fields that are specified as
   * "no nulls". This method also validates all referential integrity
   * constraints specified by the object.
   *
   * @throws PersistenceException
   *             If the record cannot be added - this includes if the record
   *             has a duplicate key
   */
  public synchronized void add() throws PersistenceException
  {
    checkAllowed("A");

    //Added by Santanu Dutt to clear all previous errors due to Add
    this.currentErrors.clear();
    currentErrors.putAll(validateAll());

    if (currentErrors.size() > 0)
    {
      throw new PersistenceException("Field validation errors in persistent '" + myMetaData.getName() + "': ",
              getErrors());
    }

    /*
     * Go through all the fields, setting to their default values if they
     * are null
     */
    String oneFieldName = null;
    Object oneFieldValue = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();
      oneFieldValue = getField(oneFieldName);

      if (getField(oneFieldName) == null)
      {
        setField(oneFieldName, myMetaData.getDefaultValue(oneFieldName));
      }
      else if (oneFieldValue.toString().equals(""))
      {
        setField(oneFieldName, myMetaData.getDefaultValue(oneFieldName));
      }
    }

    boolean needComma = false;

    haveAllKeys(false, true);

    if (getHelper() != null)
    {
      getHelper().beforeAdd(this);
    }

    SuperString sqlCommand = new SuperString(96);

    sqlCommand.append("INSERT INTO ");
    sqlCommand.append(myMetaData.getTableName());
    sqlCommand.append(" (");

    SuperString valuesCommand = new SuperString(64);

    valuesCommand.append(") VALUES (");

    boolean needCommaValues = false;

    String identityFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();

      checkField(oneFieldName, getFieldString(oneFieldName));

      if ("identity".equals(myMetaData.getAutoIncremented(oneFieldName)))
      {
        identityFieldName = oneFieldName;

        continue;
      }

      if (needComma)
      {
        sqlCommand.append(", ");
      }

      sqlCommand.append(myMetaData.getDBFieldName(oneFieldName));
      needComma = true;

      if (needCommaValues)
      {
        valuesCommand.append(", ");
      }

      if (myMetaData.isAutoIncremented(oneFieldName))
      {
        /*
         * If identityFieldName is not null, we've already used the one
         * (and only) identity for this table
         */
        if ((identityFieldName == null) && myMetaData.isKeyField(oneFieldName)
                && myMetaData.getDatabaseType().isIdentitySupported()
                && (myMetaData.getIdGenerator(oneFieldName) == null))
        {
          // Native auto-inc is supported using "IDENTITY" type
          // syntax
          identityFieldName = oneFieldName;
          valuesCommand.append(myMetaData.getDatabaseType().getInsertIdentitySyntax());
        }
        else
        {
          setAutoIncrement(oneFieldName);
          valuesCommand.append("?");
        }
      }
      else
      {
        valuesCommand.append("?");
      }

      needCommaValues = true;
    } /* for each field */
    //Now we merge the values of the parallel loops
    sqlCommand.append(valuesCommand);
    sqlCommand.append(")");

    try
    {
      Connection myConnection = null;
      PreparedStatement ps = null;
      Statement s = null;
      ResultSet rs = null;

      try
      {
        if (currentTransaction != null)
        {
          myConnection = currentTransaction.getConnection();
        }
        else
        {
          try
          {
            myConnection = myDataSource.getConnection();
          }
          catch (ConcurrentModificationException cme)
          {
            addError(cme);
            throw new PersistenceException("Unable to prepare statement", cme, getErrors());
          }
        }

        ps = myConnection.prepareStatement(sqlCommand.toString());

        // Lock the DB table so we can retrieve the identity field
        // in a concurrent-safe fashion
        this.lock();

        // populate the fields for the update
        int count = 1;

        for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
        {
          oneFieldName = (String) i.next();

          if ("identity".equals(myMetaData.getAutoIncremented(oneFieldName)))
          {
            continue;
          }

          //ACR: Changed to properly set based on types. Still some
          // work
          //to do for blobs....
          this.setPreparedStatementObject(ps, count, oneFieldName);
          count++;

          //}
        } /* for each field */
        if (log.isDebugEnabled())
        {
          log.debug("Executing: " + sqlCommand.toString() + "(" + ps.toString() + ") on " + toString());
        }

        int updateCount = ps.executeUpdate();

        if ((updateCount == 0) && (getCheckZeroUpdate()))
        {
          throw new PersistenceException("No records updated", getErrors());
        }

        ps.close();
        ps = null;

        if (identityFieldName != null)
        {
          s = myConnection.createStatement();
          rs = s.executeQuery(myMetaData.getDatabaseType().getRetrieveIdentitySyntax(myMetaData,
                  identityFieldName));

          if (rs.next())
          {
            String identityValue = rs.getString(1);

            setField(identityFieldName, identityValue);
            rs.close();
            rs = null;
            s.close();
            s = null;
          }
          else
          {
            throw new PersistenceException("No value returned for identity field: " + identityFieldName,
                    getErrors());
          }
        }
      }
      catch (ClassCastException cce)
      {
        addError(cce);
        throw new PersistenceException("Unable to add record to database" + toString() + ":" + cce.getMessage()
                + " (" + sqlCommand + ")", getErrors());
      }
      catch (PersistenceException de)
      {
        addError(de);
        throw new PersistenceException("Unable to add record to database" + toString() + ":" + de.getMessage()
                + " (" + sqlCommand + ")", getErrors());
      }
      finally
      {
        //unlock the DB table
        this.unlock();
        cleanUp(myConnection, s, rs, ps);
      }

      setStatus(Persistent.CURRENT);

      if (getHelper() != null)
      {
        getHelper().afterAdd(this);
      }
    }
    catch (SQLException se)
    {
      addError(se);
      throw new PersistenceException(se, getErrors());
    }
  } /* add() */

  /**
   * Determine if a record with this key exists already - if not, add a new
   * record. Note that this method uses just the key fields to determine if
   * the record already exists.
   */
  public synchronized void addIfNeeded() throws PersistenceException
  {
    Persistent searchObj = getFactory().create(getName());
    String oneFieldName = null;

    for (Iterator i = myMetaData.getKeyFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();
      searchObj.setField(oneFieldName, getField(oneFieldName));
    }

    if (! searchObj.find())
    {
      add();
    }
  } /* addOrUpdate() */

  /**
   * Determine if a record with these fields exists already - if so, update.
   * If not, add a new record. Note that this method uses *all* of the fields
   * currently set as search criteria (e.g. not just the key)
   */
  public synchronized void addOrUpdate() throws PersistenceException
  {
    Persistent searchObj = getFactory().create(getName());
    String oneFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();
      searchObj.setField(oneFieldName, getField(oneFieldName));
    }

    if (! searchObj.find())
    {
      add();
    }
    else
    {
      update();
    }
  } /* addOrUpdate() */

  /**
   * Find the average of the values in the specified field of records
   * se;lected by the Persistent selection criteria.
   *
   * @param String
   *            Persistent fieldName to average
   * @return double Average of the records matching the criteria
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public double average(String fieldName) throws PersistenceException
  {
    return sqlAggrFunction("AVG", fieldName);
  } /* average() */

  /**
   * Set all fields to empty value & clear the last result set
   *
   * @throws PersistenceException
   *             If the fields cannot be cleared
   */
  public synchronized void clear() throws PersistenceException
  {
    String oneFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();
      setField(oneFieldName, null);
    }

    sortKeys = null;
  } /* clear() */

  /**
   * Just like find, but only retrieves the count, not the records
   * themselves.
   *
   * @return integer Count of the records matching the criteria
   * @throws PersistenceException
   *             If the search could not be completed
   * @see #find
   */
  public synchronized int count() throws PersistenceException
  {
    if (! (this instanceof RowSecurablePersistent))
    {
      checkAllowed("L");
    }

    foundKeys = null;

    SuperString myStatement = new SuperString(48);

    myStatement.append("SELECT COUNT(*) FROM ");
    myStatement.append(myMetaData.getTableName());

    if (customWhereClause != null)
    {
      myStatement.append(customWhereClause);
    }
    else
    {
      myStatement.append(buildWhereClauseBuffer(true));
    }

    int retVal = 0;

    Statement s = null;
    ResultSet rs = null;

    Connection myConnection = null;

    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      s = myConnection.createStatement();

      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + myStatement.toString());
      }

      rs = s.executeQuery(myStatement.toString());

      if (rs.next())
      {
        retVal = rs.getInt(1);
      }

      s.close();
      s = null;

      rs.close();
      rs = null;
    }
    catch (Exception de)
    {
      throw new PersistenceException(de);
    }
    finally
    {
      cleanUp(myConnection, s, rs, null);
    }

    return retVal;
  } /* count() */

  /**
   * Delete a record from the target table. The current field values for the
   * key of this object tell us which record to delete. This default version
   * of delete also deletes associated detail records (e.g. performs a
   * "cascading delete" of details.
   *
   * @throws PersistenceException
   *             If the delete fails or if the referential integrity would be
   *             violated by the delete
   */
  public synchronized void delete() throws PersistenceException
  {
    delete(true);
  }

  /**
   * Delete this record, optionally deleting any associated detail records.
   * WARNING: Deleting details is *recursive* - e.g. any details of those
   * details will also be deleted. Use with caution on extremely large data
   * sets - might be a better idea to use deleteAll on each appropriate
   * table, which is non-recursive and uses a single SQL statement to delete
   * groups of records.
   *
   * @param deleteDetails
   *            Delete associated detail records if true
   * @throws PersistenceException
   *             If the delete fails or if the referential integrity would be
   *             violated by the delete
   */
  public synchronized void delete(boolean deleteDetails) throws PersistenceException
  {
    checkAllowed("D");

    currentErrors.clear();

    if (getHelper() != null)
    {
      getHelper().beforeDelete(this);
    }

    haveAllKeys(true, true);

    SuperString sqlCommand = new SuperString(64);

    sqlCommand.append("DELETE FROM ");
    sqlCommand.append(myMetaData.getTableName());

    if (customWhereClause != null)
    {
      sqlCommand.append(customWhereClause);
    }
    else
    {
      sqlCommand.append(buildWhereClauseBuffer(false));
    }

    Connection myConnection = null;
    Statement s = null;

    try
    {
      if (deleteDetails)
      {
        deleteDetails();
      } /* if we delete the details */
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      s = myConnection.createStatement();

      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + sqlCommand.toString());
      }

      int updateCount = s.executeUpdate(sqlCommand.toString());

      s.close();
      s = null;

      if ((updateCount == 0) && (getCheckZeroUpdate()))
      {
        log.error("No record(s) deleted for SQL '" + sqlCommand.toString() + "'");
        throw new PersistenceException("No record(s) deleted.");
      }
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, s, null, null);
    }

    setStatus(Persistent.DELETED);

    if (getHelper() != null)
    {
      getHelper().afterDelete(this);
    }
  } /* delete() */

  /**
   * Delete all of the records specified by the current search criteria.
   * IMPORTANT: deleteAll does *not* check for details, nor does it offer the
   * option of deleting associated detail records. In order to do this,
   * retrieve each record with query and use delete(true), not deleteAll().
   * deleteAll() is more efficient for deleting a large number of records
   * from a single table.
   *
   * @throws PersistenceException
   *             If the delete fails or if the referential integrity would be
   *             violated by the delete
   */
  public synchronized void deleteAll() throws PersistenceException
  {
    currentErrors.clear();

    if (recordSet == null)
    {
      recordSet = new ArrayList();
    }
    else
    {
      recordSet.clear();
    }

    SuperString myStatement = new SuperString(64);

    myStatement.append("DELETE FROM ");

    myStatement.append(myMetaData.getTableName());

    if (customWhereClause != null)
    {
      myStatement.append(customWhereClause);
      customWhereClause = null;
    }
    else
    {
      myStatement.append(buildWhereClauseBuffer(true));
    }

    Connection myConnection = null;
    Statement s = null;

    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      s = myConnection.createStatement();
      /* int up[dateCount = */
      s.executeUpdate(myStatement.toString());
      s.close();
      s = null;

      /*
       * Can't do the below check on MySQL (at least with current
       * drivers)
       */

      /* the records are deleted, but the update count still says zero */
      //            if ((updateCount == 0) && (getCheckZeroUpdate())) {
      //                throw new PersistenceException("No records deleted for " +
      // myStatement.toString());
      //            }
    }
    catch (PersistenceException de)
    {
      throw de;
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, s, null, null);
    }
  } /* deleteAll() */

  /**
   * A lot like retrieve, but works with any fields, not just the key field.
   * Does not throw an exception if the record is not found, just returns
   * false. Finds only first record matching the criteria. The current fields
   * in this object are populated with the data in the first (or only) record
   * found after the call to find() if the find is successful.
   *
   * @return boolean If the search resulted in a record
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public synchronized boolean find() throws PersistenceException
  {
    boolean checkSecurity = false;

    if (this instanceof RowSecurablePersistent)
    {
      checkSecurity = true;
    }
    else
    {
      checkAllowed("L");
    }

    boolean needComma = false;

    foundKeys = null;

    ArrayList retrievedFieldList = new ArrayList();

    SuperString myStatement = new SuperString(64);

    myStatement.append("SELECT ");

    String oneFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();

      if (needComma)
      {
        myStatement.append(", ");
      }

      retrievedFieldList.add(oneFieldName);
      myStatement.append(selectFieldString(oneFieldName));

      needComma = true;
    } /* for */
    myStatement.append(" FROM ");
    myStatement.append(myMetaData.getTableName());

    if (customWhereClause != null)
    {
      myStatement.append(customWhereClause);
      customWhereClause = null;
    }
    else
    {
      myStatement.append(buildWhereClauseBuffer(true));
    }

    Connection myConnection = null;
    ResultSet rs = null;
    Statement s = null;

    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      s = myConnection.createStatement();

      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + myStatement.toString());
      }

      rs = s.executeQuery(myStatement.toString());

      Object oneFieldValue = null;
      StringBuffer oneKeyString = new StringBuffer("");

      if (foundKeys == null)
      {
        foundKeys = new ArrayList();
      }

      if (rs.next())
      {
        log.debug("Found record");

        int i = 1;

        for (Iterator it = retrievedFieldList.iterator(); it.hasNext();)
        {
          oneFieldName = (String) it.next();

          if (rs.getString(i) == null && myMetaData.allowsNull(oneFieldName))
          {
            i++;
          }
          else
          {
            oneFieldValue = retrieveField(oneFieldName, rs, i);
            setField(oneFieldName, oneFieldValue);
            i++;
          }

          if (myMetaData.isKeyField(oneFieldName))
          {
            if (i > 1)
            {
              oneKeyString.append("/");
            }

            oneKeyString.append(oneFieldValue);
          } /* if field is key */
        } /* for each field */
        foundKeys.add(oneKeyString.toString());
      }
      else
      {
        if (log.isDebugEnabled())
        {
          log.debug("No matching record for " + myStatement.toString());
        }

        return false;
      }

      if (checkSecurity)
      {
        checkAllowed("L");
      }

      setStatus(Persistent.CURRENT);

      return true;
    }
    catch (SQLException se)
    {
      addError(se);
      throw new PersistenceException(se.getMessage() + " - " + toString());
    }
    finally
    {
      cleanUp(myConnection, s, rs, null);
    }
  } /* find() */

  /**
   * Return an "attribute". Attributes are temporary (e.g. not stored in the
   * DBMS) values associated with this particular persistent instance.
   *
   * @param attribName
   * @return
   */
  public Object getAttribute(String attribName)
  {
    Object returnValue = null;

    if (attributes != null)
    {
      returnValue = attributes.get(attribName);
    }

    return returnValue;
  } /* getAttribute(String) */

  public Map getAttributes()
  {
    return attributes;
  }

  /**
   * Query the general-purpose attribute for a field
   */
  public Object getAttribute(String fieldName, String attribName) throws PersistenceException
  {
    return myMetaData.getAttribute(fieldName, attribName);
  }

  /**
   * Return the value of a field as a Date object
   *
   * @param fieldName
   *            The field to be retrieved
   * @param formatPattern
   *            A formatting pattern according to java.text.DecimalFormat.
   *            Leave null for the default format for this locale.
   *
   * <pre>
   *  Symbol Meaning 0 a digit # a digit, zero shows as absent . placeholder for decimal separator , placeholder for grouping separator. E separates mantissa and exponent for exponential formats. ; separates formats. - default negative prefix. % multiply by 100 and show as percentage ? multiply by 1000 and show as per mille ? currency sign; replaced by currency symbol; if doubled, replaced by international currency symbol. If present in a pattern, the monetary decimal separator is used instead of the decimal separator. X any other characters can be used in the prefix or suffix ' used to quote special characters in a prefix or suffix.
   * </pre>
   *
   *
   * @return The Date object equivilant to this field's value
   * @throws PersistenceException
   *             If the field does not exist or it's value is not a date or
   *             cannot be converted to a date
   * @see java.text.DecimalFormat
   */
  public String getFieldDecimalFormatted(String fieldName, String formatPattern) throws PersistenceException
  {
    if (! myMetaData.hasField(fieldName))
    {
      throw new PersistenceException("(" + getName() + ") No such field as " + fieldName);
    }

    Object o = getFieldData(fieldName);

    if (o == null)
    {
      return null;
    }

    String strVal = o.toString();

    if (strVal.equals(""))
    {
      return null;
    }

    Double myDouble = new Double(strVal);
    DecimalFormat df = null;

    if (formatPattern == null)
    {
      df = new DecimalFormat();
    }
    else
    {
      df = new DecimalFormat(formatPattern);
    }

    String returnValue = df.format(myDouble);

    return returnValue;
  } /* getFieldDecimalFormatted(String, String) */

  /**
   * Get the integer value of a field in this object
   *
   * @param fieldName
   *            Name of a field in this object
   * @return int The value of the field as a float
   * @throws PersistenceException
   *             if there is no such field or it's value cannot be converted
   *             to an integer.
   */
  public int getFieldInt(String fieldName) throws PersistenceException
  {
    assertField(fieldName);

    Object o = getFieldData(fieldName);

    if (o == null)
    {
      throw new PersistenceException("Field '" + fieldName + "' was null, cannot return as int");
    }

    String strVal = "";

    try
    {
      strVal = o.toString();

      if (strVal == null)
      {
        throw new PersistenceException("(" + getName() + ") Unable to get int value from field " + fieldName
                + ", value was '" + strVal + "'");
      }

      return Integer.parseInt(strVal);
    }
    catch (NumberFormatException ex)
    {
      throw new PersistenceException("(" + getName() + ") Unable to parse an integer value from field "
              + fieldName + " which contained '" + strVal + "'", ex);
    }
  } /* getFieldInt(String) */

  /**
   * Get the name of this object
   *
   * @return String The Persistent name, which may or may not bear any
   *         relationship to the table name
   */
  public String getName()
  {
    return myMetaData.getSchemaName() + "." + myMetaData.getName();
  } /* getName() */

  /**
   * Find the maximum of the values in the specified field of records
   * selected by the Persistent selection criteria.
   *
   * @param String
   *            Persistent fieldName to average
   * @return double Maximum of the records matching the criteria
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public double max(String fieldName) throws PersistenceException
  {
    return sqlAggrFunction("MAX", fieldName);
  } /* max() */

  /**
   * Find the minimum of the values in the specified field of records
   * selected by the Persistent selection criteria.
   *
   * @param String
   *            Persistent fieldName to average
   * @return double Minimum of the records matching the criteria
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public double min(String fieldName) throws PersistenceException
  {
    return sqlAggrFunction("MIN", fieldName);
  } /* min() */

  /**
   * Get a single record from the database into this Persistent's fields
   * Assumes that the key fields are set to the key of the object to be
   * retrieved
   *
   * @throws PersistenceException
   *             If the record could not be retrieved, or if the key fields
   *             were not set. If you are not sure if the record exists, use
   *             find() instead.
   * @see #find
   */
  public synchronized void retrieve() throws PersistenceException
  {
    /* If this instance is a RowSecurablePersistent, then we must */
    /* check for authorization *after* the row is retrieved */
    /* otherwise it is not fully populated, and we may not have */
    boolean doRowLevelCheck = false;

    if (! (this instanceof RowSecurablePersistent))
    {
      checkAllowed("L");
    }
    else
    {
      doRowLevelCheck = true;
    }

    boolean needComma = false;

    haveAllKeys(true, true);

    StringBuffer myStatement = new StringBuffer("SELECT ");
    String oneFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();

      if (needComma)
      {
        myStatement.append(", ");
      }

      myStatement.append(selectFieldString(oneFieldName));

      needComma = true;
    } /* for */
    myStatement.append(" FROM ");
    myStatement.append(myMetaData.getTableName());

    if (customWhereClause != null)
    {
      myStatement.append(customWhereClause);
      customWhereClause = null;
    }
    else
    {
      myStatement.append(buildWhereClauseBuffer(false));
    }

    Connection myConnection = null;
    Statement s = null;
    ResultSet rs = null;

    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      s = myConnection.createStatement();

      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + myStatement.toString());
      }

      rs = s.executeQuery(myStatement.toString());

      StringBuffer oneKeyString = new StringBuffer("");
      Object oneFieldValue = null;

      if (foundKeys == null)
      {
        foundKeys = new ArrayList();
      }

      if (rs.next())
      {
        int i = 1;

        for (Iterator it = myMetaData.getFieldNames().iterator(); it.hasNext();)
        {
          oneFieldName = (String) it.next();
          oneFieldValue = retrieveField(oneFieldName, rs, i);

          i++;
          setFieldData(oneFieldName, oneFieldValue);

          if (myMetaData.isKeyField(oneFieldName))
          {
            if (i > 1)
            {
              oneKeyString.append("/");
            }

            oneKeyString.append(oneFieldValue);
          }
        } /* for each field */
        foundKeys.add(oneKeyString.toString());
      }
      else
      {
        throw new PersistenceException("(" + getName() + ") No such record" + toString());
      }

      if (doRowLevelCheck)
      {
        checkAllowed("L");
      }

      setStatus(Persistent.CURRENT);
    }
    catch (PersistenceException de)
    {
      throw de;
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, s, rs, null);
    }
  } /* retrieve() */

  /**
   * Find a set of records of all of the objects that match the current
   * search critieria in the fields and retrieve the list of all records that
   * match this criteria NOTE: Criteria in 'text' type colums is ignored (SQL
   * Server limitation)
   *
   * @return A List of new database objects containing the results of the
   *         search
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public synchronized List<Persistent> query() throws PersistenceException
  {
    boolean checkRowSecurity = false;

    if (this instanceof RowSecurablePersistent)
    {
      checkRowSecurity = true;
    }
    else
    {
      checkAllowed("L");
    }

    currentErrors.clear();

    boolean needComma = false;
    ArrayList retrievedFieldList = new ArrayList();

    if (getHelper() != null)
    {
      getHelper().beforeQuery(this);
    }

    if (recordSet == null)
    {
      recordSet = new ArrayList();
    }
    else
    {
      recordSet.clear();
    }

    //myUpdates = null;
    SuperString myStatement = new SuperString(64);

    myStatement.append("SELECT ");

    String oneFieldName = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();

      if (needComma)
      {
        myStatement.append(", ");
      }

      retrievedFieldList.add(oneFieldName);
      myStatement.append(selectFieldString(oneFieldName));

      needComma = true;
    } /* for each field */
    myStatement.append(" FROM ");
    myStatement.append(myMetaData.getTableName());

    Connection myConnection = null;
    Statement s = null;
    ResultSet rs = null;

    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      if (myMetaData.getDatabaseType().getSubSetPosition() == DatabaseType.SUBSET_AFTER_TABLE
              && (offsetRecord > 0 || maxRecords > 0))
      {
        // Insert limitation stub after table nomination
        String limitStub = makeSubSetStub();

        myStatement.append(" ");
        myStatement.append(limitStub);
        myStatement.append(" ");
      }

      SuperString whereClause;

      if (customWhereClause != null)
      {
        whereClause = new SuperString(customWhereClause);
        myStatement.append(customWhereClause);
        customWhereClause = null;
      }
      else
      {
        whereClause = buildWhereClauseBuffer(true);
        myStatement.append(whereClause);
      }

      if (myMetaData.getDatabaseType().getSubSetPosition() == DatabaseType.SUBSET_AFTER_WHERE
              && (offsetRecord > 0 || maxRecords > 0))
      {
        // Insert limitation stub after table nomination
        String limitStub = makeSubSetStub();

        if (whereClause.length() > 0)
        {
          myStatement.append(" AND");
        }

        myStatement.append(" ");
        myStatement.append(limitStub);
        myStatement.append(" ");
      }

      /* Add the ORDER BY clause if any sortKeys are specified */
      if (sortKeys != null && sortKeys.size() > 0)
      {
        myStatement.append(" ORDER BY ");

        boolean needComma2 = false;

        for (Iterator i = sortKeys.iterator(); i.hasNext();)
        {
          if (needComma2)
          {
            myStatement.append(", ");
          }

          //                     myStatement.append(
          //                         myMetaData.getDBFieldName((String) i.next()));
          String fieldName = (String) i.next();
          int descIndex = fieldName.indexOf("DESC");
          int ascIndex = fieldName.indexOf("ASC");

          if (descIndex > 0)
          {
            fieldName = fieldName.substring(0, descIndex).trim();
            myStatement.append(myMetaData.getDBFieldName(fieldName));
            myStatement.append(" DESC");
          }
          else if (ascIndex > 0)
          {
            fieldName = fieldName.substring(0, ascIndex).trim();
            myStatement.append(myMetaData.getDBFieldName(fieldName));
            myStatement.append(" ASC");
          }
          else
          {
            myStatement.append(myMetaData.getDBFieldName(fieldName));
          }

          needComma2 = true;
        }

        if (myMetaData.getDatabaseType().getSubSetPosition() == DatabaseType.SUBSET_AFTER_ORDER_BY
                && (offsetRecord > 0 || maxRecords > 0))
        {
          myStatement.append(" ");
          myStatement.append(makeSubSetStub());
        }
      }

      s = myConnection.createStatement();

      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + myStatement.toString());
      }

      rs = s.executeQuery(myStatement.toString());

      int recordCount = 0;
      int retrieveCount = 0;

      while (rs.next())
      {
        recordCount++;
        retrieveCount++;

        //If there's limitation syntax on, then the first record will
        // be the
        //maximum record.
        if ((retrieveCount < offsetRecord)
                && (offsetRecord > 0)
                && (myMetaData.getDatabaseType().getSubSetPosition() == DatabaseType.SUBSET_NOT_SUPPORTED))
        {
          continue;
        }
        else if ((retrieveCount == offsetRecord)
                && (offsetRecord > 0)
                && (myMetaData.getDatabaseType().getSubSetPosition() == DatabaseType.SUBSET_NOT_SUPPORTED))
        {
          recordCount = 0;

          //Reset count for counting for max records.
          continue;
        }

        if ((recordCount > maxRecords) && (maxRecords > 0))
        {
          setAttribute("More Records", "Y");

          break;
        }

        //Only allocate if we're gonna load this record
        Persistent myObj = getFactory().create(getName());
        int i = 1;
        Object oneFieldValue = null;

        for (Iterator it = retrievedFieldList.iterator(); it.hasNext();)
        {
          oneFieldName = (String) it.next();

          oneFieldValue = retrieveField(oneFieldName, rs, i);
          i++;
          myObj.setField(oneFieldName, oneFieldValue);
        } /* for each retrieved field name */
        // TODO - how to tell the new object it's now current
        //myObj.setStatus(Persistent.CURRENT);
        if (checkRowSecurity)
        {
          if (myObj.allowed(QUERY))
          {
            recordSet.add(myObj);
          }
        }
        else
        {
          recordSet.add(myObj);
        }
      }
    }
    catch (PersistenceException de)
    {
      throw de;
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, s, rs, null);
    }

    if (getHelper() != null)
    {
      getHelper().afterQuery(this);
    }

    return recordSet;
  } /* query() */

  /**
   * Search and retrieve in a particular order
   *
   * @param sortKeyString
   *            A comma-delimited list of key fields to sort the returned set
   *            by
   * @return A List of new database objects retrieved by the search
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public synchronized List<Persistent> query(String sortKeyString) throws PersistenceException
  {
    if (sortKeys == null)
    {
      sortKeys = new LinkedHashSet();
    }
    else
    {
      sortKeys.clear();
    }

    StringTokenizer stk = new StringTokenizer(sortKeyString, ",");

    while (stk.hasMoreTokens())
    {
      sortKeys.add(stk.nextToken());
    }

    return query();
  } /* query(String) */

  /**
   * Set an attribute. Attributes are temporary (e.g. not stored in the DBMS)
   * values associated with this particular Persistent instance.
   *
   * @param attribName
   *            The name of the attribute being defined
   * @param attribValue
   *            The object to store under this attribute name
   */
  public synchronized void setAttribute(String attribName, Object attribValue)
  {
    if (attributes == null)
    {
      attributes = new HashMap();
    }

    attributes.put(attribName, attribValue);

    if (attribName.equalsIgnoreCase("checkzeroupdate"))
    {
      setCheckZeroUpdate(new Boolean(attribValue.toString()).booleanValue());
    }
  } /* setAttribute(String, Object) */

  /**
   * Specify a custom "where" clause for the SQL used to retrieve records for
   * this object. The where clause 'reset' after each call to query() or
   * other retrieval methods, so it must be set just before the call to
   * retrieve the records is made. If no custom where clause is specified by
   * this method, the where clause is built from the field values in the
   * object.
   *
   * @param newCustomWhere
   *            java.lang.String
   */
  public synchronized void setCustomWhereClause(String newCustomWhere)
  {
    customWhereClause = " WHERE " + newCustomWhere;
  } /* setCustomWhereClause(String) */

  /**
   * Specify a maximum number of records to be retrieved in any subsequent
   * query() call. Records will be retrieved (in the specified sort order)
   * until the specified maximum is reached, then the remainder of the result
   * set is discarded. Specifying zero indicates that all records are to be
   * retrieved.
   *
   * @param newMax
   *            The maximum number of records to retrieve.
   * @throws PersistenceException
   *             If the max number is less than 0
   */
  public synchronized void setMaxRecords(int newMax)
  {
    if (maxRecords < 0)
    {
      maxRecords = 0;
    }
    else
    {
      maxRecords = newMax;
    }
  } /* setMaxRecords(int) */

  /**
   * Specifies the number of records that should be skipped over before any
   * data from the <code>ResultSet</code> is retrieved in any subsequent
   * query() call. Records will be skipped over (in the specified sort order)
   * until the record counts is equal to or greater than the offset record.
   * Specifying zero indicates that no records should be skipped over and the
   * <code>ResultSet</code> immediately from the start.
   *
   * @param newMax
   *            The maximum number of records to retrieve.
   * @throws PersistenceException
   *             If the max number is less than 0
   *
   * @author Peter Pilgrim
   *         <peterp  at  xenonsoft dot demon dot co dot  uk>
   *         @date Tue Feb 05 23:06:38 GMT 2002
   */
  public synchronized void setOffsetRecord(int newOffset)
  {
    if (newOffset < 0)
    {
      offsetRecord = 0;
    }

    offsetRecord = newOffset;
  } /* setOffsetRecord(int) */

  /**
   * Find the sum of the values in the specified field of records selected by
   * the Persistent selection criteria.
   *
   * @param String
   *            Persistent fieldName to average
   * @return double Sum of the records matching the criteria
   * @throws PersistenceException
   *             If the search could not be completed
   */
  public double sum(String fieldName) throws PersistenceException
  {
    return sqlAggrFunction("SUM", fieldName);
  } /* sum() */

  /**
   * Update the database with the new info. Update affects only one record,
   * and uses the current field values to write to the DBMS. Referential
   * integrity is verified before the update is performed.
   *
   * @throws PersistenceException
   *             if the update to the database fails due to a database error
   */
  public synchronized void update() throws PersistenceException
  {
    checkAllowed("U");

    if (getHelper() != null)
    {
      getHelper().beforeUpdate(this);
    }

    currentErrors.clear();
    currentErrors.putAll(validateAll());

    if (currentErrors.size() > 0)
    {
      throw new PersistenceException("Field validation errors in persistent '" + myMetaData.getName() + "': ",
              getErrors());
    }

    boolean needComma = false;

    /* build an update statement - again we must call haveKeyFields */
    /* to be sure we have all of the keys */
    haveAllKeys(true, true);

    StringBuffer sqlCommand = new StringBuffer("UPDATE ");

    sqlCommand.append(myMetaData.getTableName());
    sqlCommand.append(" SET ");

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      String oneFieldName = (String) i.next();

      /* We skip any key fields in the update part (not allowed to */
      /* upate them). Also skip any virtual fields */
      if ((! myMetaData.isKeyField(oneFieldName)) && (! myMetaData.isAutoIncremented(oneFieldName)))
      {
        checkField(oneFieldName, getField(oneFieldName));

        if (needComma)
        {
          sqlCommand.append(", ");
        }

        if (! myMetaData.isKeyField(oneFieldName))
        {
          sqlCommand.append(myMetaData.getDBFieldName(oneFieldName));
          sqlCommand.append(" = ");
          //updated by Adam to do proper prep for update SQL
          //sqlCommand.append(quoteIfNeeded(oneFieldName, null));
          sqlCommand.append("?");
          sqlCommand.append(" ");
          needComma = true;
        }
      } /* if it's not a key field */
    }

    sqlCommand.append(buildWhereClauseBuffer(false));

    if (sqlCommand == null)
    {
      throw new PersistenceException("Internal error: (" + getName() + ") No SQL command built for this update");
    }

    PreparedStatement ps = null;
    Connection myConnection = null;

    //Statement s = null;
    try
    {
      if (currentTransaction != null)
      {
        myConnection = currentTransaction.getConnection();
      }
      else
      {
        myConnection = myDataSource.getConnection();
      }

      ps = myConnection.prepareStatement(sqlCommand.toString());

      //      populate the fields for the update
      int count = 1;
      String oneFieldName = null;

      for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
      {
        oneFieldName = (String) i.next();

        if ((! myMetaData.isKeyField(oneFieldName)) && (! myMetaData.isAutoIncremented(oneFieldName)))
        {
          setPreparedStatementObject(ps, count, oneFieldName);

          count++;
        }

        //}
      } /* for each field */
      //s = myConnection.createStatement();
      if (log.isDebugEnabled())
      {
        log.debug("Executing: " + sqlCommand.toString());
      }

      //int updateCount = s.executeUpdate(sqlCommand.toString());
      int updateCount = ps.executeUpdate();

      if ((updateCount == 0) && (getCheckZeroUpdate()))
      {
        log.error("No record updated for SQL '" + sqlCommand.toString() + "'");
        throw new PersistenceException("(" + getName() + ") No records updated for" + toString());
      }
    }
    catch (PersistenceException de)
    {
      throw de;
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, null, null, ps);
    }

    setStatus(Persistent.CURRENT);

    if (getHelper() != null)
    {
      getHelper().afterUpdate(this);
    }
  } /* update() */

  public void setTransaction(Transaction newTrx)
  {
    currentTransaction = newTrx;
  }

  public boolean allowed(int operation) throws PersistenceException
  {
    switch (operation)
    {
      case Persistent.ADD:
        return isAllowed("A");

      case Persistent.UPDATE:
        return isAllowed("U");

      case Persistent.DELETE:
        return isAllowed("D");

      case Persistent.QUERY:
        return isAllowed("L");

      default:
        throw new PersistenceException("Operation " + operation + " is not understood");
    }
  }

  public Map validateAll()
  {
    HashMap returnMap = new HashMap();
    String oneFieldName = null;
    String oneError = null;

    for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();

      try
      {
        oneError = validateField(oneFieldName, getField(oneFieldName));
      }
      catch (PersistenceException pe)
      {
        log.error("Error while validating field '" + oneFieldName + "'", pe);
        returnMap.put(oneFieldName, pe.getMessage());
      }

      if (oneError != null)
      {
        returnMap.put(oneFieldName, oneError);
      }
    }

    return returnMap;
  }

  public String validateField(String fieldName, Object oneFieldValue)
  {
    String returnValue = null;

    try
    {
      if ((oneFieldValue == null) || ("".equals(oneFieldValue)))
      {
        oneFieldValue = myMetaData.getDefaultValue(fieldName);

        if (((oneFieldValue == null) || ("".equals(oneFieldValue))) && (! myMetaData.allowsNull(fieldName))
                && (! myMetaData.isAutoIncremented(fieldName)))
        {
          return "Field '" + fieldName + "' may not be null";
        }
        else
        {
          return returnValue;
        }
      }
      else
      {
        validateType(fieldName);
      }

      if (myMetaData.isMultiValued(fieldName))
      {
        if (! getValidValues(fieldName).containsKey(oneFieldValue.toString()))
        {
          return "Value '" + oneFieldValue.toString() + "' is not a valid value for field '" + fieldName
                  + "'";
        }
      }
    }
    catch (PersistenceException pe)
    {
      returnValue = pe.getMessage();
    }

    return returnValue;
  }

  public Map getErrors()
  {
    return currentErrors;
  }

  /*
   * @see de.iritgo.aktera.persist.Persistent#lock()
   */
  public synchronized void lock() throws PersistenceException
  {
    String dbKeyName = getName();

    if (log.isDebugEnabled())
    {
      log.debug("Acquiring mutex lock for " + dbKeyName);
    }

    if (dbLocks == null)
    {
      // Lazy initialization of the mutex map
      if (log.isDebugEnabled())
      {
        log.debug("Creating mutex lock map...");
      }

      dbLocks = new HashMap(16);
    }

    // The map of mutexes is keyed by the name of this persistent.
    // Try to find it first, If not found, create a new mutex and
    // add it to the map for this and future use.
    Mutex dbMutex = null;

    if ((dbMutex = (Mutex) dbLocks.get(dbKeyName)) == null)
    {
      if (log.isDebugEnabled())
      {
        log.debug("Creating new mutex for " + dbKeyName);
      }

      dbMutex = new Mutex();
      dbLocks.put(dbKeyName, dbMutex);
    }

    //Got handle to mutex, now acquire it
    try
    {
      dbMutex.acquire();
    }
    catch (InterruptedException ie)
    {
      throw new PersistenceException(ie);
    }

    if (log.isDebugEnabled())
    {
      log.debug("Acquired mutex lock for " + dbKeyName);
    }
  }

  /*
   * @see de.iritgo.aktera.persist.Persistent#unlock()
   */
  public synchronized void unlock() throws PersistenceException
  {
    if (dbLocks == null)
    {
      throw new PersistenceException("$keelNoDBLockMap");
    }

    String dbKeyName = getName();
    Mutex dbMutex = null;

    if ((dbMutex = (Mutex) dbLocks.get(dbKeyName)) == null)
    {
      throw new PersistenceException("DB lock mutex not found for " + dbKeyName);
    }

    dbMutex.release();

    if (log.isDebugEnabled())
    {
      log.debug("Released mutex lock for " + dbKeyName);
    }
  }

  /**
   * This method has been overriden to print the table name and all
   * name/value pairs as a single string. This is useful for debugging.
   *
   * @see java.lang.Object#toString()
   */
  public String toString()
  {
    StringBuffer sb = new StringBuffer("");

    sb.append("factory:");

    if (myFactory == null)
    {
      sb.append("null");
    }
    else
    {
      sb.append(myFactory.getName());
    }

    sb.append(",name:");
    sb.append(getName());
    sb.append(",table_name:");
    sb.append(this.myMetaData.getTableName());
    sb.append(",field_values:");

    String oneFieldName = null;
    Object oneFieldValue = null;

    try
    {
      for (Iterator i = myMetaData.getFieldNames().iterator(); i.hasNext();)
      {
        oneFieldName = (String) i.next();
        oneFieldValue = getField(oneFieldName);
        sb.append(oneFieldName);
        sb.append("=");

        if (oneFieldValue != null)
        {
          sb.append(oneFieldValue.toString());
        }

        sb.append("|");
      }
    }
    catch (PersistenceException pe)
    {
      sb.append(pe.toString());
    }

    return sb.toString();
  }

  public List getDetails(String detailName) throws PersistenceException
  {
    PersistentMetaData pmd = getMetaData();
    Relation r = pmd.getRelation(detailName);

    if (r == null)
    {
      throw new PersistenceException("No such relation as '" + detailName + "'");
    }

    if (r.getType() != Relation.DETAIL)
    {
      throw new PersistenceException("Relation '" + detailName + "' is not a detail relation");
    }

    String toPersistent = r.getToPersistent();
    Persistent detList = myFactory.create(toPersistent);
    String oneFieldName = null;
    String oneFromFieldName = null;
    Iterator fromFields = r.getFromFields().iterator();

    for (Iterator i = r.getToFields().iterator(); i.hasNext();)
    {
      oneFieldName = (String) i.next();
      oneFromFieldName = (String) fromFields.next();
      detList.setField(oneFieldName, getField(oneFromFieldName));
    }

    return detList.query();
  }

  /**
   * Set the persistent fields from the fields of a bean. The bean is
   * retained internally, and can be accessed via "getBean()", but it is
   * important to note that getBean may return a different instance of the
   * bean than setBean stored.
   */
  public void setBean(Object newBean) throws PersistenceException
  {
    assert newBean != null;

    PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(newBean);

    if (props.length == 0)
    {
      throw new PersistenceException("Class '" + newBean.getClass().getName() + "' declares no fields");
    }

    PropertyDescriptor oneProp = null;
    String fieldName = null;

    for (int i = 0; i < props.length; i++)
    {
      oneProp = props[i];
      fieldName = getFieldNameIgnoreCase(oneProp.getName());

      if (myMetaData.getFieldNames().contains(fieldName))
      {
        Object oneValue = null;

        try
        {
          oneValue = PropertyUtils.getProperty(newBean, oneProp.getName());
        }
        catch (Exception ie)
        {
          throw new PersistenceException("Unable to retrieve property '" + oneProp.getName()
                  + "' from object of class '" + newBean.getClass().getName() + "'", ie);
        }

        setField(fieldName, oneValue);
      }
      else
      {
        if (log.isDebugEnabled())
        {
          /*
           * Every class has a "class" attribute - don't clutter up
           * the log
           */
          if (! fieldName.equals("class"))
          {
            log.debug("No field '" + fieldName + "' in persistent definition '" + myMetaData.getName()
                    + "', unable to persist");
          }
        }
      }
    }
  }

  public Helper getHelper() throws PersistenceException
  {
    Helper h = super.getHelper();

    if (h == null)
    {
      return null;
    }

    h.setPersistent(this);

    return h;
  }
} /* DefaultPersistent */ 
TOP

Related Classes of de.iritgo.aktera.persist.defaultpersist.DefaultPersistent

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.