Package de.iritgo.aktera.persist.defaultpersist

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

/**
* 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 de.iritgo.aktera.authentication.UserEnvironment;
import de.iritgo.aktera.authorization.AuthorizationException;
import de.iritgo.aktera.authorization.AuthorizationManager;
import de.iritgo.aktera.authorization.InstanceSecurable;
import de.iritgo.aktera.authorization.Operation;
import de.iritgo.aktera.authorization.defaultauth.DefaultOperation;
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.persist.base.JDBCDatabaseType;
import de.iritgo.aktera.util.string.SuperString;
import org.apache.avalon.excalibur.datasource.DataSourceComponent;
import org.apache.avalon.excalibur.datasource.ids.IdException;
import org.apache.avalon.excalibur.datasource.ids.IdGenerator;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.logger.Logger;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Matcher;
import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
* @version $Revision: 1.2 $ $Date: 2005/03/06 16:47:22 $
* @author Michael Nash
*/
public abstract class DefaultPersistentBase implements InstanceSecurable
{
  /**
   * The class that takes the actual Perl5 Compiled Pattern and matches it
   * against a string.
   */
  transient private static PatternMatcher patternMatcher = new Perl5Matcher();

  /**
   * This map holds a per-persistent mutex used for granting exclusive
   * read-write access to a DB-table Note: The lock()/unlock() is valid
   * across a single JVM only.
   */
  protected static Map dbLocks = null;

  /**
   * JavaBean object underlying this persistent. Defined by the "class"
   * attribute in configuration
   */
  protected Object myBean = null;

  protected AuthorizationManager myAuthManager = null;

  protected AuthorizationManager myBypassAuthManager = null;

  protected DefaultPersistentFactory myFactory = null;

  /**
   * Our private reference to the data source service handed to us by our
   * container
   */
  protected DataSourceComponent myDataSource = null;

  /**
   * Our private reference to the current transaction we are participating
   * in, or null if no such transaction
   */
  protected Transaction currentTransaction = null;

  /**
   * Persistents themselves do not contain the "meta data" or the definition
   * of what fields they contain and other information. Instead, a
   * PersistentMetaData object (one per TYPE of Persistent) contains this
   * info and is looked up as needed.
   */
  protected PersistentMetaData myMetaData = null;

  /**
   * A Persistent instance often refers to a single row, and this hash map
   * contains the row data for this instance
   */
  protected Map fieldData = null;

  /** The ArrayList of Persistents retrieved by the last query method */
  protected List recordSet = null;

  protected int currentStatus = Persistent.NEW;

  /**
   * The number of records we must skip over before we start reading the
   * <code>ResultSet</code> proper in a query. A value of 0 means no limit
   */
  protected int offsetRecord = 0;

  /**
   * The max number of records we retrieve in a query. 0 means no limit
   */
  protected int maxRecords = 0;

  /**
   * The list of fields by which this object should be sorted when records
   * are retrieved
   */
  protected Set sortKeys = null;

  protected List foundKeys = null;

  /**
   * If we are using a custom where clause for this query, it's stored here.
   * If null, then build the where clause
   */
  protected String customWhereClause = null;

  /** Attributes of this Persistent */
  protected Map attributes = null;

  /* Our logger */
  transient protected Logger log = null;

  protected boolean checkZeroUpdate = true;

  protected Helper myHelper = null;

  /**
   * This map holds a set of errors from the last operation, such as an add
   * or update. It is empty if the last operation was successful. The index
   * of this hash is a string, where the string is a field name if the error
   * was related to a specific field, or just a sequential number if the
   * error was in fact not related to one particular field
   */
  protected Map currentErrors = new HashMap();

  protected final void addError(Throwable t)
  {
    int nofErrors = currentErrors.size();

    currentErrors.put("" + (nofErrors + 1), t);
  }

  protected final void addError(String fieldName, Throwable t)
  {
    currentErrors.put(fieldName, t);
  }

  protected final void addError(String fieldName, String errorMsg)
  {
    currentErrors.put(fieldName, new PersistenceException(errorMsg));
  }

  /**
   * Build and return a string consisting of an SQL 'where' clause using the
   * current field values as criteria for the search. See
   * setCustomWhereClause for information on specifying a more complex where
   * clause.
   *
   * @param allFields
   *            True if all fields are to be used, false for only key fields
   * @throws PersistenceException
   * @deprecated Use buildWhereClauseBuffer instead
   * @return The where clause
   */
  protected String buildWhereClause(boolean useAllFields) throws PersistenceException
  {
    return buildWhereClauseBuffer(useAllFields).toString();
  } /* buildWhereClause(boolean) */

  /**
   * Build and return a SuperString consisting of an SQL 'where' clause using
   * the current field values as criteria for the search. See
   * setCustomWhereClause for information on specifying a more complex where
   * clause.
   *
   * @param allFields
   *            True if all fields are to be used, false for only key fields
   * @throws PersistenceException
   * @return A SuperString containing the "where" clause for the SQL
   *         statement
   */
  protected SuperString buildWhereClauseBuffer(boolean useAllFields) throws PersistenceException
  {
    Iterator fieldsToUse = null;
    SuperString myStatement = new SuperString(32);

    if (useAllFields)
    {
      fieldsToUse = myMetaData.getFieldNames().iterator();
    }
    else
    {
      fieldsToUse = myMetaData.getKeyFieldNames().iterator();
    }

    /* Now go thru each field - if it is non-empty, add it's criteria */
    /* to the where clause. If it is empty, just skip to the next one */
    boolean addWhere = true;
    boolean addAnd = false;
    String oneFieldName = null;
    String oneFieldValue = null;

    boolean skipText = myMetaData.getDatabaseType().allowTextQueries();

    boolean skipField = false;

    while (fieldsToUse.hasNext())
    {
      oneFieldName = (String) fieldsToUse.next();
      skipField = false;

      oneFieldValue = SuperString.notNull(getFieldString(oneFieldName));

      String rangeString = myMetaData.getDatabaseType().denotesRange(oneFieldValue);

      if (! oneFieldValue.equals(""))
      {
        //Changed by Adam. Do formatting here for date/time types, or
        // quote
        //if needed.
        //oneFieldValue = quoteIfNeeded(oneFieldName, rangeString);
        oneFieldValue = getValueForUpdate(oneFieldName, rangeString);
      }

      if (oneFieldValue == null)
      {
        skipField = true;
      }

      if (oneFieldValue.trim().equals("\'\'"))
      {
        skipField = true;
      }

      if (myMetaData.getType(oneFieldName).equalsIgnoreCase("text"))
      {
        if (skipText)
        {
          skipField = true;

          if (log.isDebugEnabled())
          {
            log.debug("Skipping criteria in text field '" + oneFieldName + "'");
          }
        }
        else
        {
          if (oneFieldValue.indexOf("\n") > 0)
          {
            oneFieldValue = SuperString.replace(oneFieldValue, "\n", "");
          }

          if (oneFieldValue.indexOf("\r") > 0)
          {
            oneFieldValue = SuperString.replace(oneFieldValue, "\r", "");
          }

          if (oneFieldValue.equals("\'\'"))
          {
            skipField = true;
          }
        }
      } /* if text field */
      if (oneFieldValue.trim().equals(""))
      {
        skipField = true;
      }

      if (! skipField)
      {
        if (addWhere)
        {
          myStatement.append(" WHERE ");
          addWhere = false;
        }

        if (addAnd)
        {
          myStatement.append(" AND ");
        }

        if (containsWildCards(oneFieldValue))
        {
          myStatement.append(myMetaData.getDBFieldName(oneFieldName));
          myStatement.append(" LIKE ");
          myStatement.append(oneFieldValue);
        }
        else if (rangeString != null)
        {
          myStatement.append(myMetaData.getDBFieldName(oneFieldName));
          myStatement.append(" " + rangeString + " ");
          myStatement.append(oneFieldValue);
        }
        else if ((oneFieldValue.trim().equalsIgnoreCase("is null"))
                || (oneFieldValue.trim().equalsIgnoreCase("is not null")))
        {
          myStatement.append(myMetaData.getDBFieldName(oneFieldName));
          myStatement.append(" ");
          myStatement.append(oneFieldValue.trim());
        }
        else
        {
          myStatement.append(myMetaData.getDBFieldName(oneFieldName));
          myStatement.append(" = ");
          myStatement.append(oneFieldValue);
        }

        addAnd = true;
      }

      /* if field is not skipped for some reason */
    }

    /* for each field */
    if (log.isDebugEnabled())
    {
      log.debug("Built where clause '" + myStatement.toString() + "'");
    }

    return myStatement;
  }

  /**
   * Check that a given value is valid for a given field. This method is
   * overriden by specific Persistents to do their own field-level
   * validations - they should also call super in order to do the standard
   * stuff. Every field is automatically checked by this method before the
   * database is updated.
   *
   * @param fieldName
   *            Name of the field to verify
   * @param fieldValue
   *            Value of the field to be evaluated
   * @throws PersistenceException
   *             If the value is not valid
   */
  protected synchronized void checkField(String fieldName, Object fieldValue) throws PersistenceException
  {
    assertField(fieldName);

    // Allow autoinc fields through since they're added last minute and may
    // not
    // currently have a valid value
    if (myMetaData.isAutoIncremented(fieldName))
    {
      return;
    }

    if (! myMetaData.allowsNull(fieldName))
    {
      if (fieldValue == null)
      {
        throw new PersistenceException("$keelNullNotAllowed|" + myMetaData.getTableName() + "."
                + myMetaData.getDescription(fieldName));
      }
      else if (fieldValue.toString().equals(""))
      {
        throw new PersistenceException("$keelNullNotAllowed|" + myMetaData.getTableName() + "."
                + myMetaData.getDescription(fieldName));
      }
    }
    else
    { /* It's null and it's allowed to be, so do no further checks */

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

    Pattern mask = myMetaData.getPattern(fieldName);

    if (mask != null)
    {
      if (! patternMatcher.matches(fieldValue.toString(), mask))
      {
        throw new PersistenceException("$keelFieldNotMatch|" + fieldName + "|" + mask.getPattern());
      }
    }

    /*
     * For multi-valued fields, the value specified must be one of the
     * valid values Note: I have changed this behavior slightly from
     * previous versions. If a field has been marked as "allowsNull", it
     * should pass the check below, even if the field has also been marked
     * as "multiValued". In other words, a mutlivalued, allowsnull field
     * should pass the checkField method if it has a null value. - Adam
     */
    if (myMetaData.isMultiValued(fieldName) && (! fieldValue.equals("")))
    {
      //Added this line to fix object comparison problems - ACR
      String stringFieldValue = fieldValue.toString();
      Map values = getValidValues(fieldName);

      if (values == null)
      {
        throw new PersistenceException("(" + getName() + ") '" + myMetaData.getDescription(fieldName)
                + "' is set as multi-valued, but there are no defined " + "valid values" + toString());
      }

      Object oneValue = null;

      for (Iterator e = values.keySet().iterator(); e.hasNext();)
      {
        oneValue = (String) e.next();

        //Changed this line to fix object comparison issues. - ACR
        if (stringFieldValue.equals(oneValue))
        {
          return;
        }
      } /* for each valid value */
      throw new PersistenceException("(" + getName() + ") '" + fieldValue + "' is not a valid value for field '"
              + fieldName + "(" + myMetaData.getDescription(fieldName) + ")' " + toString());
    } /* if field is multi-valued */
    if (isDateType(myMetaData.getType(fieldName)))
    {
      String stringDate = fieldValue.toString();

      String format = "yyyy-MM-dd hh:mm:ss";
      String type = myMetaData.getType(fieldName);

      if (type.equalsIgnoreCase("date"))
      {
        format = "yyyy-MM-dd";
      }
      else if (type.equalsIgnoreCase("time"))
      {
        format = "hh:mm:ss";
      }

      //--- quikdraw: This is doing exactly what?
      try
      {
        new SimpleDateFormat(format).parse(stringDate);
      }
      catch (ParseException pe)
      {
        throw new PersistenceException("Value '" + stringDate + "' for field '"
                + myMetaData.getDescription(fieldName) + "' cannot be parsed as a date");
      }
    }
  } /* checkField(String, String) */

  /**
   * See if this field value contains wild cards (pattern matching criteria
   * for the database). The wild cards can be configured via the properties
   * file.
   *
   * @param fieldValue
   *            The field value to check for wild cards
   * @return True if the string does contain wild cards, False if it does not
   * @throws PersistenceException
   *             If there is an error accessing meta-data
   */
  protected synchronized boolean containsWildCards(String fieldValue) throws PersistenceException
  {
    String fieldVal = fieldValue;

    if (fieldValue == null)
    {
      fieldVal = "";
    }

    for (Iterator it = myMetaData.getDatabaseType().getWildCards().iterator(); it.hasNext();)
    {
      if (fieldVal.indexOf((String) it.next()) > 0)
      {
        return true;
      }
    }

    return false;
  } /* containsWildCards(String) */

  /**
   * If this Persistent has associated detail objects, locate the appropriate
   * related detail records and delete them as well. This is a "cascading
   * delete" process.
   */
  protected void deleteDetails() throws PersistenceException
  {
    String oneDet = null;

    for (Iterator ee = myMetaData.getDetailNames().iterator(); ee.hasNext();)
    {
      oneDet = (String) ee.next();

      Relation oneDetRelation = myMetaData.getRelation(oneDet);
      Persistent detailObj = null;

      try
      {
        detailObj = getFactory().create(oneDetRelation.getToPersistent());
      }
      catch (Exception e)
      {
        throw new PersistenceException("Unable to instantiate " + "detail db object '"
                + oneDetRelation.getToPersistent() + "'", e);
      }

      if (currentTransaction != null)
      {
        detailObj.setTransaction(currentTransaction);
      }

      if (! detailObj.allowed(Persistent.DELETE))
      {
        throw new PersistenceException("Delete of '" + oneDetRelation.getToPersistent() + "' not allowed");
      }

      Iterator stkLocal = oneDetRelation.getFromFields().iterator();
      Iterator stkForeign = oneDetRelation.getToFields().iterator();

      while (stkLocal.hasNext())
      {
        String localField = (String) stkLocal.next();
        String foreignField = (String) stkForeign.next();

        detailObj.setField(foreignField, getField(localField));
      }

      Persistent oneDetailObj = null;

      for (Iterator di = detailObj.query().iterator(); di.hasNext();)
      {
        oneDetailObj = (Persistent) di.next();
        oneDetailObj.setTransaction(currentTransaction);
        oneDetailObj.delete(true);
      }
    } /* for each detail */
  } /* deleteDetails */

  /**
   * Method getValueForDelete. This method prepares a field value for
   * addition to the database. Date formatting is taken care of, as well as
   * quoting as needed.
   *
   * @param fieldName
   * @return String - a string ready for use in an INSERT SQL statement.
   * @throws PersistenceException
   */
  protected String getValueForDelete(String fieldName, String rangeString) throws PersistenceException
  {
    String returnString = null;

    if (getField(fieldName) == null)
    {
      return null;
    }

    if (getField(fieldName).equals(""))
    {
      return null;
    }

    String myType = myMetaData.getType(fieldName);

    if (myType.equalsIgnoreCase("date") || myType.equalsIgnoreCase("datetime") || myType.equalsIgnoreCase("time")
            || myType.equalsIgnoreCase("timestamp"))
    {
      returnString = formatDateTime(fieldName);
    }
    else
    {
      returnString = quoteIfNeeded(fieldName, rangeString);
    }

    return returnString;
  }

  /**
   * Method getValueForUpdate. This method prepares a field value for use in
   * a SQL UPDATE statement. Date formatting is done, and the value is quoted
   * as needed.
   *
   * @param fieldName
   * @return String
   * @throws PersistenceException
   */
  protected String getValueForUpdate(String fieldName, String rangeString) throws PersistenceException
  {
    return getValueForDelete(fieldName, rangeString);
  }

  /**
   * Return the current object's character se.t
   *
   * @return The String representation of the current object's character set.
   */
  protected synchronized String getCharset() throws PersistenceException
  {
    return myMetaData.getCharset();
  } /* getCharset() */

  /**
   * Return the default value for a field, if any. Return null if no default
   * value specified.
   */
  protected String getDefaultValue(String fieldName) throws PersistenceException
  {
    return myMetaData.getDefaultValue(fieldName);
  }

  /**
   * Access the "raw" field data for the named field.
   */
  protected Object getFieldData(String fieldName)
  {
    if (fieldData == null)
    {
      fieldData = new HashMap();
    }

    return fieldData.get(fieldName);
  }

  protected void assertField(String fieldName) throws PersistenceException
  {
    if (! myMetaData.hasField(fieldName))
    {
      throw new PersistenceException("Persistent '" + getName() + "' contains no such field as " + fieldName);
    }
  }

  /**
   * Get a string consisting of the values of each key field for this object
   * appended together with a | between them.
   *
   * @return Value of all keys appended with a | between
   * @throws PersistenceException
   *             If the key list cannot be built.
   */
  protected String getMyKeys() throws PersistenceException
  {
    StringBuffer myKeys = new StringBuffer();
    boolean needPipe = false;

    for (Iterator i = myMetaData.getKeyFieldNames().iterator(); i.hasNext();)
    {
      if (needPipe)
      {
        myKeys.append("|");
      }

      myKeys.append(getFieldString((String) i.next()));
      needPipe = true;
    }

    return myKeys.toString();
  } /* getMyKeys() */

  /**
   * 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() */

  /**
   * This function is called whenever the Persistent is about to be written
   * to the database. It may do additional processing such as encryption
   * depending on the field attributes.
   *
   * @throws PersistenceException
   * @return the value to write to the data source.
   */
  protected String getSerializedForm(String fieldName) throws PersistenceException
  {
    String returnValue = null;

    try
    {
      if (! myMetaData.isEncrypted(fieldName))
      {
        Object o = getFieldData(fieldName);

        if (o != null)
        {
          returnValue = o.toString();
        }
      }
    }
    catch (Exception e)
    {
      throw new PersistenceException(e);
    }

    return returnValue;
  } /* getSerializedForm() */

  /**
   * See if we have a value for each of the key fields
   *
   * @param withAuto
   *            True if we must have values for even auto-incremented keys,
   *            if false, then auto-incremented key fields are allowed to be
   *            blank or null
   * @param throwException
   *            Throw a PersistenceException if all keys are not present, as
   *            opposed to simply returning false.
   * @return True if all key fields have a value, false if not
   * @throws PersistenceException
   */
  protected boolean haveAllKeys(boolean withAuto, boolean throwException) throws PersistenceException
  {
    Object fieldValue = null;
    String strVal = null;
    String oneFieldName = null;

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

      if (fieldValue == null)
      {
        strVal = "";
      }
      else
      {
        strVal = fieldValue.toString();
      }

      if (strVal.equals(""))
      {
        if (withAuto)
        {
          if (throwException)
          {
            throw new PersistenceException("$keelNullNotAllowed|" + myMetaData.getDescription(oneFieldName));
          }

          return false;
        }

        if (! myMetaData.isAutoIncremented(oneFieldName))
        {
          if (throwException)
          {
            throw new PersistenceException("$keelNullNotAllowed|" + myMetaData.getDescription(oneFieldName));
          }

          return false;
        }
      }
    }

    return true;
  } /* haveAllKeys() */

  /**
   * Return true if the given type is a date/time type of field
   *
   * @param theType
   * @return
   */
  protected static boolean isDateType(String theType)
  {
    boolean returnValue = false;

    if (theType.equalsIgnoreCase("date") || theType.equalsIgnoreCase("time")
            || theType.equalsIgnoreCase("datetime") || theType.equalsIgnoreCase("timestamp"))
    {
      returnValue = true;
    }

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

  /**
   * Return true if every field in this object is empty or null
   *
   * @return boolean: True if the record is "empty" (all fields blank), False
   *         if not.
   * @throws PersistenceException
   *             If the list of fields cannot be traversed
   */
  protected synchronized boolean isEmpty() throws PersistenceException
  {
    String oneName = null;

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

      if (! getFieldString(oneName).equalsIgnoreCase(""))
      {
        return false;
      }
    } /* for each field */
    return true;
  } /* isEmpty() */

  /**
   * Creates the limitation syntax optimisation stub to embed inside the SQL
   * command that performs search and retrieve.
   *
   * <p>
   * This method takes the limitation syntax string and performs a string
   * replacement on the following tokens
   *
   * <ul>
   *
   * <li><b>%offset%</b>
   * <li><br>the number of rows in the <code>ResultSet</code> to skip
   * before reading the data.
   *
   * <li><b>%maxrecord%</b>
   * <li><br>the maximum number of rows to read from the <code>ResultSet</code>.
   * Also known as the <i>rowlength</i>.
   *
   * <li><b>%endrecord%</b>
   * <li><br>the last record of in the <code>ResultSet</code> that the
   * search and retrieved should retrieve. The end record number is equal to
   * <code>( %offset% + %maxrecord% - 1 )</code>
   *
   * </ul>
   *
   * </p>
   *
   * @see de.iritgo.aktera.persist.DatabaseType#getSubSetSyntax()
   * @see #query()
   * @see #setOffsetRecord( int )
   * @see #setMaxRecords( int )
   */
  protected String makeSubSetStub()
  {
    String limit = myMetaData.getDatabaseType().getSubSetSyntax();
    int endrec = offsetRecord + maxRecords - 1;

    limit = SuperString.replace(limit, "%offset%", Integer.toString(offsetRecord));
    limit = SuperString.replace(limit, "%maxrecords%", Integer.toString(maxRecords));
    // limit = SuperString.replace( limit, "%length%", Integer.toString(
    // maxRecords ) );
    limit = SuperString.replace(limit, "%endrecord%", Integer.toString(endrec));

    return limit;
  } /* makeSubSetStub() */

  /**
   * Return the value of this field, placing double quotes around it if the
   * field's datatype requires it.
   *
   * @param fieldName
   *            The name of the field to be used
   * @return A string, quoted if necessary, to be used in building an SQL
   *         statement
   * @throws PersistenceException
   *             If there is no such field or it's value cannot be determined
   */
  protected String quoteIfNeeded(String fieldName, String rangeString) throws PersistenceException
  {
    String fieldValue = null;

    assertField(fieldName);

    fieldValue = getSerializedForm(fieldName);

    /* if the field is null, we don't need to worry about quotes */
    if (fieldValue == null)
    {
      return "null";
    }

    if (rangeString != null)
    {
      fieldValue = fieldValue.substring(rangeString.length());
    }

    if (myMetaData.getDatabaseType().isNumericType(myMetaData.getType(fieldName)))
    {
      if (fieldValue.equals("") || fieldValue.equals(" "))
      {
        return "0";
      }

      return fieldValue.trim();
    } /* if a numeric type */
    if (myMetaData.getDatabaseType().isQuotedType(myMetaData.getType(fieldName)))
    {
      if ((fieldValue.indexOf("\'") != - 1) || (fieldValue.indexOf("\"") != - 1))
      {
        return "\'" + SuperString.noQuotes(fieldValue.trim()) + "\'";
      }
      else
      {
        return "\'" + fieldValue.trim() + "\'";
      }
    } /* if a quoted type */
    if (myMetaData.getDatabaseType().isDateType(myMetaData.getType(fieldName)))
    {
      return "\'" + fieldValue + "\'";
    }

    return fieldValue.trim();
  } /* quoteIfNeeded(String) */

  /**
   * Build an appropriate String for use in the select part of an SQL
   * statement by doing the
   *
   * @return The portion of the select clause with the appropriate function
   *         wrapped around it
   * @param fieldName
   *            The name of the field to be handled
   * @throws PersistenceException
   */
  protected String selectFieldString(String fieldName) throws PersistenceException
  {
    String dbFieldName = myMetaData.getDBFieldName(fieldName);
    DatabaseType myDBType = myMetaData.getDatabaseType();

    String fieldType = myMetaData.getType(fieldName);

    if (fieldType.equalsIgnoreCase("date"))
    {
      if (! SuperString.notNull(myDBType.getDateSelectFunction()).equals(""))
      {
        return (SuperString.replace(myDBType.getDateSelectFunction(), "%s", dbFieldName));
      }
    }

    if (fieldType.equalsIgnoreCase("time"))
    {
      if (! SuperString.notNull(myDBType.getTimeSelectFunction()).equals(""))
      {
        return (SuperString.replace(myDBType.getTimeSelectFunction(), "%s", dbFieldName));
      }
    }

    if (fieldType.equalsIgnoreCase("datetime"))
    {
      if (! SuperString.notNull(myDBType.getDateTimeSelectFunction()).equals(""))
      {
        return (SuperString.replace(myDBType.getDateTimeSelectFunction(), "%s", dbFieldName));
      }
    }

    return dbFieldName;
  } /* selectFieldString(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) */

  /**
   * Gets the check zero update flags for this Persistent.
   *
   * @return boolean value that denotes if check zero update is on or off.
   *
   */
  protected synchronized boolean getCheckZeroUpdate()
  {
    return checkZeroUpdate;
  } /* setCheckZeroUpdate(boolean) */

  /**
   * Turn on or off the facility to verify that when an update is made that
   * at least one record got updated. If this flag is on, and no records get
   * updated, the update() method throws an Exception. Note that for some
   * databases, if the existing record is not changed (e.g. it was already
   * identical to what was being updated) this counts "no update" (notably,
   * mySQL does this).
   *
   * @param newFlag
   *            True to turn on checking, false to turn it off
   */
  protected synchronized void setCheckZeroUpdate(boolean newFlag)
  {
    checkZeroUpdate = newFlag;
  } /* setCheckZeroUpdate(boolean) */

  protected synchronized void setFieldData(String fieldName, Object fieldValue) throws PersistenceException
  {
    if (fieldData == null)
    {
      fieldData = new HashMap();
    }

    fieldData.put(fieldName, fieldValue);
    setBeanField(fieldName, fieldValue);
  }

  protected void setPreparedStatementObject(PreparedStatement ps, int index, String fieldName)
    throws PersistenceException, SQLException
  {
    int typeToUse = JDBCDatabaseType.stringToType(myMetaData.getType(fieldName));

    if (log.isDebugEnabled())
    {
      log.debug("Setting Field #/FieldName/Value" + index + '/' + fieldName + '/' + this.getFieldData(fieldName));
      log.debug("FieldType: " + typeToUse);
    }

    Object o = this.getFieldData(fieldName);

    if (o == null)
    {
      ps.setNull(index, typeToUse);

      return;
    }

    try
    {
      switch (typeToUse)
      {
        case java.sql.Types.VARCHAR:
          log.debug("FieldTypeName: VARCHAR");
          ps.setString(index, this.getFieldString(fieldName));

          break;

        case java.sql.Types.ARRAY:
          log.debug("FieldTypeName: ARRAY");
          ps.setArray(index, (Array) this.getFieldData(fieldName));

          break;

        case java.sql.Types.BIGINT: // No getBigInt?
          log.debug("FieldTypeName: BIGINT");
          ps.setLong(index, this.getFieldLong(fieldName));

          break;

        case java.sql.Types.BIT:
          log.debug("FieldTypeName: BIT");
          ps.setBoolean(index, this.getFieldBoolean(fieldName));

          break;

        //really not sure here....
        case java.sql.Types.BLOB:
          log.debug("FieldTypeName: BLOB");
          ps.setBlob(index, (Blob) this.getFieldData(fieldName));

          break;

        case java.sql.Types.BOOLEAN:
          log.debug("FieldTypeName: BOOLEAN");
          ps.setBoolean(index, this.getFieldBoolean(fieldName));

          break;

        case java.sql.Types.CHAR:
          log.debug("FieldTypeName: CHAR");
          ps.setString(index, this.getFieldString(fieldName));

          break;

        case java.sql.Types.CLOB:
          log.debug("FieldTypeName: CLOB");
          ps.setClob(index, (Clob) this.getFieldData(fieldName));

          break;

        case java.sql.Types.DATE:
          log.debug("FieldTypeName: DATE");
          ps.setDate(index, getFieldDate(fieldName));

          break;

        case java.sql.Types.DECIMAL:
          log.debug("FieldTypeName: DECIMAL");
          ps.setBigDecimal(index, new BigDecimal(getFieldString(fieldName)));

          break;

        //case java.sql.Types.DISTINCT :
        //  return rs.getObject(index);
        case java.sql.Types.DOUBLE:
          log.debug("FieldTypeName: DOUBLE");
          ps.setDouble(index, new Double(getFieldString(fieldName)).doubleValue());

          break;

        case java.sql.Types.FLOAT:
          log.debug("FieldTypeName: FLOAT");
          ps.setFloat(index, new Float(getFieldString(fieldName)).floatValue());

          break;

        case java.sql.Types.INTEGER:
          log.debug("FieldTypeName: INTEGER");

          try
          {
            ps.setInt(index, new Integer(getFieldString(fieldName)).intValue());
          }
          catch (NumberFormatException x)
          {
            ps.setNull(index, java.sql.Types.INTEGER);
          }

          break;

        case java.sql.Types.JAVA_OBJECT:
          log.debug("FieldTypeName: JAVA_OBJECT");
          ps.setObject(index, getFieldData(fieldName));

          break;

        case java.sql.Types.LONGVARBINARY:
          log.debug("FieldTypeName: LONGVARBINARY");
          ps.setObject(index, getFieldData(fieldName), java.sql.Types.LONGVARBINARY);

          break;

        case java.sql.Types.LONGVARCHAR:
          log.debug("FieldTypeName: LONGVARCHAR");
          ps.setString(index, this.getFieldString(fieldName));

          break;

        case java.sql.Types.NULL:
          log.debug("FieldTypeName: NULL");
          ps.setNull(index, java.sql.Types.NULL);

          break;

        case java.sql.Types.NUMERIC:
          log.debug("FieldTypeName: NUMBERIC");
          ps.setLong(index, this.getFieldLong(fieldName));

          break;

        case java.sql.Types.OTHER:
          log.debug("FieldTypeName: OTHER");
          ps.setObject(index, getFieldData(fieldName), java.sql.Types.OTHER);

          break;

        case java.sql.Types.REAL:
          log.debug("FieldTypeName: REAL");
          ps.setFloat(index, new Float(getFieldString(fieldName)).floatValue());

          break;

        case java.sql.Types.REF:
          log.debug("FieldTypeName: REF");
          ps.setObject(index, getFieldData(fieldName), java.sql.Types.REF);

          break;

        case java.sql.Types.SMALLINT:
          log.debug("FieldTypeName: SMALLINT");
          ps.setShort(index, new Short(getFieldString(fieldName)).shortValue());

          break;

        case java.sql.Types.STRUCT:
          log.debug("FieldTypeName: STRUCT");
          ps.setObject(index, getFieldData(fieldName), java.sql.Types.STRUCT);

          break;

        case java.sql.Types.TIME:
          log.debug("FieldTypeName: TIME");
          ps.setTime(index, new java.sql.Time(getFieldDate(fieldName).getTime()));

          break;

        case java.sql.Types.TIMESTAMP:
          log.debug("FieldTypeName: TIMESTAMP");

          java.util.Date fieldDate = this.getFieldDate(fieldName);
          java.sql.Timestamp ts = null;

          if (fieldDate != null)
          {
            ts = new Timestamp(fieldDate.getTime());
            ps.setTimestamp(index, ts);
          }
          else
          {
            ps.setNull(index, Types.TIMESTAMP);
          }

          break;

        case java.sql.Types.TINYINT:
          log.debug("FieldTypeName: TINYINT");
          ps.setShort(index, new Short(getFieldString(fieldName)).shortValue());

          break;

        case java.sql.Types.VARBINARY:
          log.debug("FieldTypeName: VARBINARY");
          ps.setBlob(index, (Blob) this.getFieldData(fieldName));

          break;

        default:
          log.debug("FieldTypeName: DEFAULT");
          ps.setObject(index, this.getFieldData(fieldName));

          break;
      }
    }
    catch (ClassCastException cce)
    {
      String msg = "Error on field " + fieldName + ".  Cannot cast data of type "
              + this.getFieldData(fieldName).getClass().getName()
              + " to the data type used by java.sql.Types " + myMetaData.getType(fieldName) + ".";

      cce.printStackTrace();
      //Screen is not always logged, esepcially on MS, so record in
      // application log for later analysis.
      log.error(msg, cce);
      throw new PersistenceException(msg, cce);
    }
    catch (NumberFormatException ne)
    {
      String msg = "Error on field " + fieldName + ". Cannot parse value '" + getFieldString(fieldName) + "'"
              + " into type " + myMetaData.getType(fieldName);

      throw new PersistenceException(msg, ne);
    }

    if (log.isDebugEnabled())
    {
      log.debug("Set Field #/FieldName/Value" + index + '/' + fieldName + '/' + this.getFieldData(fieldName));
    }
  }

  /**
   * Set the current status of a record
   *
   * @param newStatus
   *            <p>
   *            NEW: Record is new
   *            </p>
   *            <p>
   *            CURRENT: Record is synchronized with the database
   *            </p>
   *            <p>
   *            DELETED: Record has been deleted
   *            </p>
   *            <p>
   *            MODIFIED: Record has been updated since it was last written
   *            to the database
   *            </p>
   */
  protected void setStatus(int newStatus)
  {
    if ((newStatus == Persistent.NEW) || (newStatus == Persistent.CURRENT) || (newStatus == Persistent.MODIFIED)
            || (newStatus == Persistent.DELETED))
    {
      currentStatus = newStatus;
    }
    else
    {
      throw new IllegalArgumentException("Unknown status '" + newStatus + "'");
    }
  } /* setStatus(int) */

  /**
   * This allows the invocation of the SQL AVG, MIN, MAX and SUM aggregate
   * functions on one of the DB's columns (the Persistent fieldname is
   * supplied).
   *
   * This function is not meant to be used directly, but is here as the
   * common/shared code used by the following methods in Persistent:
   * avg(String), min(String), max(String), sum(String).
   *
   * @param String
   *            The SQL aggregate function - must be one of AVG, MIN, MAX,
   *            SUM
   * @param String
   *            The Persistent fieldName to use for the aggregate function
   * @return double The result of the SQL function invocation
   * @throws PersistenceException
   *             If the function could not be completed
   */
  protected synchronized double sqlAggrFunction(String func, String fieldName) throws PersistenceException
  {
    checkAllowed("L");

    SuperString myStatement = new SuperString(48);

    myStatement.append("SELECT " + func);
    myStatement.append("(" + fieldName + ") FROM ");
    myStatement.append(myMetaData.getTableName());

    myStatement.append(buildWhereClauseBuffer(true));

    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());

      if (rs.next())
      {
        return rs.getDouble(1);
      }
    }
    catch (SQLException se)
    {
      throw new PersistenceException(se);
    }
    finally
    {
      cleanUp(myConnection, s, rs, null);
    }

    return 0.0;
  } /* sqlAggrFunction() */

  /**
   * Our PersistentMetaData hands us a data source service. We use this data
   * source to get connections *if* we are not participating in a
   * transaction. If we are in a transaction, we use the Connection from the
   * transaction instead
   */
  protected void setDataSource(DataSourceComponent newDataSource)
  {
    myDataSource = newDataSource;
  }

  public void setLogger(Logger newLog)
  {
    assert newLog != null;
    log = newLog;
  }

  /**
   * Called by the factory to pass this persistent it's meta data. We take
   * this opportunity to initialize our underlying bean, if one is defined.
   */
  public void setMetaData(PersistentMetaData newMeta)
  {
    myMetaData = newMeta;
    setDataSource(myMetaData.getDataSource());

    try
    {
      createBean();
    }
    catch (PersistenceException pe)
    {
      log.error("Unable to create bean:", pe);
      throw new IllegalArgumentException(pe.getMessage());
    }
  }

  protected DefaultPersistentFactory getFactory() throws PersistenceException
  {
    return myFactory;
  }

  protected void setAutoIncrement(String fieldName) throws PersistenceException
  {
    IdGenerator myIdGenerator = myMetaData.getIdGenerator(fieldName);

    try
    {
      setField(fieldName, myIdGenerator.getNextIntegerId());
    }
    catch (IdException ie)
    {
      log.error("Unable to auto-increment field", ie);
      throw new PersistenceException("$keelFieldNoAutoInc|" + myMetaData.getDescription(fieldName), ie);
    }
  }

  protected Object retrieveField(String fieldName, ResultSet rs, int index) throws SQLException, PersistenceException
  {
    int typeToUse = JDBCDatabaseType.stringToType(myMetaData.getType(fieldName));
    Object returnValue = null;

    switch (typeToUse)
    {
      case java.sql.Types.ARRAY:
        returnValue = rs.getArray(index);

        break;

      case java.sql.Types.BIGINT: // No getBigInt?
        returnValue = rs.getBigDecimal(index);

        break;

      case java.sql.Types.BINARY:
        returnValue = rs.getBinaryStream(index);

        break;

      case java.sql.Types.BIT:
        returnValue = new Boolean(rs.getBoolean(index));

        break;

      case java.sql.Types.BLOB:
        returnValue = rs.getBlob(index);

        break;

      case java.sql.Types.BOOLEAN:
        returnValue = new Boolean(rs.getBoolean(index));

        break;

      case java.sql.Types.CHAR:
        returnValue = rs.getString(index);

        break;

      case java.sql.Types.CLOB:
        returnValue = rs.getClob(index);

        break;

      // BELOW FOR JDK 1.4+ only
      //case java.sql.Types.DATALINK:
      //  returnValue = "DATALINK";
      //  break;
      case java.sql.Types.DATE:
        returnValue = rs.getDate(index);

        break;

      case java.sql.Types.DECIMAL:
        returnValue = rs.getBigDecimal(index);

        break;

      case java.sql.Types.DISTINCT:
        returnValue = rs.getObject(index);

        break;

      case java.sql.Types.DOUBLE:
        returnValue = new Double(rs.getDouble(index));

        break;

      case java.sql.Types.FLOAT:
        returnValue = new Float(rs.getFloat(index));

        break;

      case java.sql.Types.INTEGER:
        returnValue = new Integer(rs.getInt(index));

        break;

      case java.sql.Types.JAVA_OBJECT:
        returnValue = rs.getString(index);

        break;

      case java.sql.Types.LONGVARBINARY:
        returnValue = rs.getBlob(index);

        break;

      case java.sql.Types.LONGVARCHAR:
        returnValue = rs.getString(index);

        break;

      case java.sql.Types.NULL:
        returnValue = rs.getObject(index);

        break;

      case java.sql.Types.NUMERIC:
        returnValue = new Long(rs.getLong(index));

        break;

      case java.sql.Types.OTHER:
        returnValue = rs.getObject(index);

        break;

      case java.sql.Types.REAL:
        returnValue = new Float(rs.getFloat(index));

        break;

      case java.sql.Types.REF:
        returnValue = rs.getObject(index);

        break;

      case java.sql.Types.SMALLINT:
        returnValue = new Short(rs.getShort(index));

        break;

      case java.sql.Types.STRUCT:
        returnValue = rs.getObject(index);

        break;

      case java.sql.Types.TIME:
        returnValue = rs.getTime(index);

        break;

      case java.sql.Types.TIMESTAMP:
        returnValue = rs.getTimestamp(index);

        break;

      case java.sql.Types.TINYINT:
        returnValue = new Short(rs.getShort(index));

        break;

      case java.sql.Types.VARBINARY:
        returnValue = rs.getBlob(index);

        break;

      case java.sql.Types.VARCHAR:
        returnValue = rs.getString(index);

        break;

      default:
        throw new PersistenceException("Unknown type " + myMetaData.getType(fieldName));
    } // switch

    return returnValue;
  }

  void setFactory(DefaultPersistentFactory newFactory)
  {
    myFactory = newFactory;
  }

  protected boolean isAllowed(String operationCode) throws SecurityException
  {
    if (! getMetaData().isSecurable())
    {
      if (log.isDebugEnabled())
      {
        log.debug("Persistent " + getName() + " is not securable - operation allowed");
      }

      return true;
    }

    Operation o = new DefaultOperation();

    o.setOperationCode(operationCode);

    o.setService(this);

    try
    {
      DefaultPersistentFactory dpf = getFactory();
      Context c = null;
      AuthorizationManager am = myAuthManager;

      if (myBypassAuthManager != null)
      {
        am = myBypassAuthManager;
      }
      else
      {
        c = dpf.getKeelContext();

        if (c == null)
        {
          throw new SecurityException(
                  "No context available and no bypass auth manager specified - unable to access secure persistent object "
                          + getInstanceIdentifier());
        }
      }

      if (am == null)
      {
        String msg = "Authmanager was not set for " + getName() + ", unable to verify authorization";

        log.error(msg);
        throw new SecurityException(msg);
      }

      return am.allowed(o, c);
    }
    catch (PersistenceException pe)
    {
      log.error("DB Error, unable to verify authorization", pe);
      throw new SecurityException("DB error, unable to verify authorization");
    }
    catch (AuthorizationException e)
    {
      log.error("Unable to verify authorization", e);
      throw new SecurityException("Unable to verify authorization");
    }
  }

  public void setAuthorizationManager(AuthorizationManager am)
  {
    myAuthManager = am;
  }

  public String getInstanceIdentifier()
  {
    return myMetaData.getSchemaName() + "." + myMetaData.getName();
  }

  protected void checkAllowed(String operation) throws SecurityException
  {
    if (log.isDebugEnabled())
    {
      log.debug("Checking access to " + this.toString());
    }

    if (isAllowed(operation))
    {
      return;
    }

    String failedUserName = "unknown";
    String failedUserId = "unknown";

    try
    {
      DefaultPersistentFactory dpf = getFactory();
      Context c = dpf.getKeelContext();

      if (c != null)
      {
        UserEnvironment ue = (UserEnvironment) c.get(UserEnvironment.CONTEXT_KEY);

        if (ue == null)
        {
          failedUserName = "(No user logged in)";
        }
        else
        {
          failedUserName = ue.getLoginName();
          failedUserId = "" + ue.getUid();
        }
      }
    }
    catch (Exception pe)
    {
      log.error("Unable to determine id of rejected user", pe);
    }

    StringBuffer failMessage = new StringBuffer();

    if (operation.equals("A"))
    {
      failMessage.append("Add operation");
    }
    else if (operation.equals("L"))
    {
      failMessage.append("Select operation");
    }
    else if (operation.equals("U"))
    {
      failMessage.append("Update operation");
    }
    else if (operation.equals("D"))
    {
      failMessage.append("Delete operation");
    }

    failMessage.append(" not authorized for user " + failedUserName + " (uid=" + failedUserId + ") for persistent "
            + toString());
    throw new SecurityException(failMessage.toString());
  }

  /**
   * Create the underlying bean - bean may be replaced with a setBean call
   */
  protected void createBean() throws PersistenceException
  {
    String className = myMetaData.getClassName();

    if (className == null)
    {
      className = "";
    }

    if (! className.equals(""))
    {
      /* Use an instance of class name supplied */
      try
      {
        Class c = Class.forName(className);

        myBean = c.newInstance();
      }
      catch (Exception e)
      {
        throw new PersistenceException("Unable to create bean '" + className + "'", e);
      }
    }
    else
    {
      log.warn("No bean class defined for '" + getName() + "'");
    }
  }

  protected void setBeanField(String fieldName, Object fieldValue) throws PersistenceException
  {
    if (myBean == null)
    {
      return;
    }

    String propertyName = getPropertyNameIgnoreCase(fieldName);

    PropertyDescriptor oneProp = null;

    try
    {
      oneProp = PropertyUtils.getPropertyDescriptor(myBean, propertyName);
    }
    catch (Exception ne)
    {
      log.warn("Bean for '" + getName() + "' does not declare field '" + propertyName + "'", ne);

      return;
    }

    if (oneProp == null)
    {
      log.warn("Bean for '" + getName() + "' does not declare field '" + propertyName + "'");

      return;
    }

    if (oneProp.getWriteMethod() == null)
    {
      log.warn("Property '" + propertyName + "' for bean of '" + getName() + "' is not writable");

      return;
    }

    Class oneType = oneProp.getPropertyType();
    String typeString = oneType.getName();
    String convertedType = "";
    String origValue = null;
    String strVal = null;

    if (oneProp.getName() == null)
    {
      throw new PersistenceException("Null property name for field '" + fieldName + "'");
    }

    try
    {
      Object oneValue = fieldValue;

      if (oneValue != null)
      {
        origValue = oneValue.toString();
        strVal = ConvertUtils.convert(oneValue);

        Object converted = ConvertUtils.convert(strVal, oneType);

        if (converted != null)
        {
          convertedType = converted.getClass().getName();
        }

        PropertyUtils.setProperty(myBean, oneProp.getName(), converted);
      }
      else
      {
        PropertyUtils.setProperty(myBean, oneProp.getName(), null);
      }
    }
    catch (Exception ie)
    {
      addError(fieldName, "Unable to set property '" + oneProp.getName() + "' (of type " + typeString
              + ") original value '" + origValue + "' in object of class '" + myBean.getClass().getName()
              + "'. Converted to string '" + strVal + "', Converted value of type " + convertedType + ":"
              + ie.getMessage());
    }
  }

  protected String getFieldNameIgnoreCase(String fieldName)
  {
    String oneFieldName = null;

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

      if (oneFieldName.equalsIgnoreCase(fieldName))
      {
        return oneFieldName;
      }
    }

    return fieldName;
  }

  protected String getPropertyNameIgnoreCase(String fieldName) throws PersistenceException
  {
    PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(myBean);

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

    PropertyDescriptor oneProp = null;

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

      if (oneProp.getName().equalsIgnoreCase(fieldName))
      {
        return oneProp.getName();
      }
    }

    return fieldName;
  }

  protected void cleanUp(Connection c, Statement s, ResultSet rs, PreparedStatement ps) throws PersistenceException
  {
    if (rs != null)
    {
      try
      {
        rs.close();
        rs = null;
      }
      catch (SQLException se)
      {
        throw new PersistenceException("Unable to close result set", se);
      }
    }

    if (ps != null)
    {
      try
      {
        ps.close();
        ps = null;
      }
      catch (SQLException se)
      {
        throw new PersistenceException("Unable to close prepared statement", se);
      }
    }

    if (s != null)
    {
      try
      {
        s.close();
        s = null;
      }
      catch (SQLException se)
      {
        throw new PersistenceException("Unable to close statement", se);
      }
    }

    if ((c != null) && (currentTransaction == null))
    {
      try
      {
        c.close();
        c = null;
      }
      catch (SQLException se)
      {
        throw new PersistenceException("Unable to close connection", se);
      }
    }
  }

  /**
   * Given the value of a date/time or date/time field, return the value
   * formatted as appropriate for the current DBMS. Can be configured using
   * property file values.
   *
   * @return java.lang.String The formatted date time, ready for use in the
   *         DBMS
   * @param fieldValue
   *            java.lang.String The value for the date/time field.
   * @throws PersistenceException
   */
  protected String formatDateTime(String fieldName) throws PersistenceException
  {
    Date originalDate = getFieldDate(fieldName);

    String convertFormat = new String("");
    String convertFunction = new String("");

    if (! myMetaData.hasField(fieldName))
    {
      throw new PersistenceException("(" + getName() + ") No such field as " + fieldName);
    }

    if (getField(fieldName) == null)
    {
      return null;
    }

    if (getField(fieldName).equals(""))
    {
      return null;
    }

    String myType = myMetaData.getType(fieldName);

    if (myType.equalsIgnoreCase("date"))
    {
      convertFormat = myMetaData.getDatabaseType().getDateUpdateFormat();
      convertFunction = myMetaData.getDatabaseType().getDateUpdateFunction();
    }
    else if (myType.equalsIgnoreCase("datetime") || myType.equalsIgnoreCase("timestamp"))
    {
      convertFormat = myMetaData.getDatabaseType().getDateTimeUpdateFormat();
      convertFunction = myMetaData.getDatabaseType().getDateTimeUpdateFunction();
    }
    else if (myType.equalsIgnoreCase("time"))
    {
      convertFormat = myMetaData.getDatabaseType().getTimeUpdateFormat();
      convertFunction = myMetaData.getDatabaseType().getTimeUpdateFunction();
    }
    else
    {
      throw new PersistenceException("Field '" + fieldName + "' is not a date, datetime or time - it is a "
              + myType + ", which cannot be formatted " + "as a Date/Time type");
    }

    convertFormat = SuperString.notNull(convertFormat);
    convertFunction = SuperString.notNull(convertFunction);

    /* If no format was specified, don't change the existing field */
    if (convertFormat.equals(""))
    {
      if (! convertFunction.equals(""))
      {
        return SuperString.replace(convertFunction, "%s", "'" + getField(fieldName) + "'");
      }
      else
      {
        return "'" + getField(fieldName) + "'";
      }
    }

    String returnValue = null;

    SimpleDateFormat formatter = new SimpleDateFormat(convertFormat);

    returnValue = "'" + formatter.format(originalDate) + "'";

    if (returnValue == null)
    {
      throw new PersistenceException("(" + getName() + ") Unable to format date value from field " + fieldName
              + ", value was " + getField(fieldName));
    }

    if (! convertFunction.equals(""))
    {
      return SuperString.replace(convertFunction, "%s", returnValue);
    }

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

  /**
   * Get the long value of a field in this object
   *
   * @param fieldName
   *            Name of a field in this object
   * @return long 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 long.
   */
  public long getFieldLong(String fieldName) throws PersistenceException
  {
    Object o = getFieldData(fieldName);

    if (o == null)
    {
      return Long.parseLong("0");
    }

    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 Long.parseLong(strVal);
    }
    catch (NumberFormatException ex)
    {
      throw new PersistenceException("(" + getName() + ") Unable to parse an integer value from field "
              + fieldName + " which contained '" + strVal, ex);
    }
  } /* getFieldLong(String) */

  /**
   * Boolean typesafe version of setField
   */
  public synchronized void setField(String fieldName, boolean fieldValue) throws PersistenceException
  {
    if (fieldValue == true)
    {
      setField(fieldName, "true");
    }
    else
    {
      setField(fieldName, "false");
    }
  }

  /**
   * Integer Typesafe version of setField
   */
  public synchronized void setField(String fieldName, int fieldValue) throws PersistenceException
  {
    setField(fieldName, Integer.toString(fieldValue));
  }

  public PersistentMetaData getMetaData()
  {
    return myMetaData;
  }

  /**
   * Set the given field to a given value. Call checkField before setting the
   * value to verify that it is allowed for this field The subclass must
   * override checkField as required
   *
   * @param fieldName
   *            The name of the field
   * @param fieldValue
   *            The value to set the field
   * @throws PersistenceException
   *             If the given field does not exist in this object or the
   *             value supplied is not allowed for this field
   */
  public synchronized void setField(String fieldName, Object fieldValue) throws PersistenceException
  {
    Object useValue = fieldValue;

    SuperString.assertNotBlank(fieldName, "Field name may not be blank");
    assertField(fieldName);

    if (getHelper() != null)
    {
      getHelper().beforeSetField(fieldName, getField(fieldName), useValue);
    }

    if (getStatus() == Persistent.CURRENT)
    {
      setStatus(Persistent.MODIFIED);
    }

    /*
     * if it's not a text field and it has newlines or returns embedded,
     * take 'em out
     */
    if (useValue != null)
    {
      String thisType = myMetaData.getType(fieldName);

      if (thisType.equalsIgnoreCase("varchar") || thisType.equalsIgnoreCase("char"))
      {
        useValue = SuperString.noNewLine(useValue.toString());
      } /* if not a text field */
      /* Check for returns or newlines in key fields - remove if found */
      if (myMetaData.getType(fieldName).equalsIgnoreCase("text") && myMetaData.isKeyField(fieldName))
      {
        useValue = SuperString.noNewLine(useValue.toString());
      }

      //Used to help mesh boolean values with checkboxes in html
      //fields
      if (myMetaData.getType(fieldName).equalsIgnoreCase("boolean"))
      {
        if (useValue.toString().equalsIgnoreCase("Y"))
        {
          useValue = "true";
        }
        else if (useValue.toString().equalsIgnoreCase("N"))
        {
          useValue = "false";
        }
      }

      /*
       * If it's a date, and the value was an empty string, use null
       * instead
       */
      if (isDateType(myMetaData.getType(fieldName)))
      {
        if (useValue.toString().trim().equals(""))
        {
          useValue = null;
        }
      }
    } /* if field was not null */
    setFieldData(fieldName, useValue);

    if (getHelper() != null)
    {
      getHelper().afterSetField(fieldName, getField(fieldName), useValue);
    }
  } /* setField(String, String) */

  protected Helper getHelper() throws PersistenceException
  {
    if (myHelper == null)
    {
      if ((getMetaData().getClassName() != null) && (getMetaData().getClassName().equals("")))
      {
        if (getBean() instanceof Helper)
        {
          return (Helper) getBean();
        }
      }

      if (SuperString.notNull(myMetaData.getHelperClassName()).equals(""))
      {
        return null;
      }

      /* create the class and return it */
      try
      {
        Class c = Class.forName(myMetaData.getHelperClassName());

        myHelper = (Helper) c.newInstance();
      }
      catch (Exception e)
      {
        throw new PersistenceException(e);
      }
    }

    return myHelper;
  }

  /**
   * If this persistent has a "class" defined for it populate it with the
   * persistent data, then return it to the caller.
   */
  public Object getBean() throws PersistenceException
  {
    final boolean debugging = log.isDebugEnabled();

    /* If the bean has not already been created, do so now */
    if (myBean == null)
    {
      if (debugging)
      {
        log.debug("No bean present, creating bean");
      }

      createBean();
    }

    if (myBean == null)
    {
      throw new PersistenceException("No bean class defined for " + getName());
    }

    String oneFieldName = null;

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

      if (debugging)
      {
        log.debug("Setting Bean key/value: " + oneFieldName + '/' + getField(oneFieldName));
      }

      setBeanField(oneFieldName, getField(oneFieldName));
    }

    return myBean;
  }

  /**
   * Get the value of a field in this object
   *
   * @param fieldname
   *            Name of the field to fetch
   * @return The value of the given field as a string - if the field is null,
   *         an empty string is returned.
   * @throws PersistenceException
   *             If there is no such field or it's value cannot be accessed
   */
  public synchronized Object getField(String fieldName) throws PersistenceException
  {
    assert fieldName != null;

    assertField(fieldName);

    Object returnValue = getFieldData(fieldName);

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

  /**
   * @see de.iritgo.aktera.persist.Persistent#setBypassAuthorizationManager(de.iritgo.aktera.authorization.AuthorizationManager)
   */
  public void setBypassAuthorizationManager(AuthorizationManager bypassAm) throws PersistenceException
  {
    if (myMetaData.isAuthManagerBypassAllowed())
    {
      myBypassAuthManager = bypassAm;
    }
    else
    {
      throw new PersistenceException("Bypass of AuthorizationManager not allowed for " + getName());
    }
  }

  /**
   * A field may have a list of valid values specified for it through three
   * different methods: i. through static validvalue declarations in the
   * schema XML file, ex:
   * <field db-name="UseSortOrder" descrip="Use Sort Order?" length="1"
   * name="UseSortOrder" null-allowed="false" type="char"> <valid-values>
   * <valid-value value="N" descrip="No"/>
   * <valid-value value="Y" descrip="Yes"/></valid-values></field>
   *
   * ii. by specifying a lookup object, which will read all values from the
   * object and use them as valid values, ex:
   *
   * I am not sure...Anyone using this?
   *
   * iii. by specifying a KeelList of valid values. The KeelList is part of
   * the keel schema, and is stored in KeelListHeader and KeelListItems.
   * These two tables can store an unlimited number of lists, grouped
   * together by ListName. Fields defined in this manner must be integer
   * types (or an equivalent numeric). This can be used automatically by
   * using the following entry in the schema XML file:
   *
   * <field db-name="SOME_FIELD_NAME" descrip="Just a field"
   * name="SOME_FIELD_NAME" multi-valued="true" null-allowed="false"
   * type="integer">
   * <list-valid-values name="SOME_LIST_NAME" /></field>
   *
   * @param fieldName
   *            The name of the field for which a valid value Map is
   *            requested
   * @return A Map of ValidValue objects
   * @throws PersistenceException
   */
  public synchronized Map getValidValues(String fieldName) throws PersistenceException
  {
    if (! myMetaData.isMultiValued(fieldName))
    {
      throw new PersistenceException("Field '" + fieldName + "' in object '" + getName()
              + "' is not specified as multi-valued, so you cannot "
              + "call getValidValues for this field");
    }

    //First look for static values. These take precedence.
    Map vvMap = myMetaData.getStaticValidValues(fieldName);

    if (vvMap.size() > 0)
    {
      return vvMap;
    }

    //Now look for ListValidValues (from the database tables).
    if (myMetaData.hasListValidValues(fieldName))
    {
      vvMap = myMetaData.getListValidValues(fieldName);

      if (vvMap.size() > 0)
      {
        return vvMap;
      }
    }

    //finally we try the lookup method.
    String lookupObjName = myMetaData.getLookupObject(fieldName);

    if (log.isDebugEnabled())
    {
      log.debug("Lookup is " + lookupObjName);
    }

    if (SuperString.notNull(lookupObjName).equals(""))
    {
      //return an empty map.
      return (Map) new HashMap();
    }

    //Ok, look for the lookup.
    Persistent lookupObj = getFactory().create(myMetaData.getSchemaName() + "." + lookupObjName);

    //Added by Santanu Dutt to build validValues Map from lookup object
    String lookUpField = myMetaData.getLookupField(fieldName);
    String lookUpFieldDisplay = myMetaData.getLookupFieldDisplay(fieldName);

    List lookups = lookupObj.query();

    vvMap = new HashMap();

    for (Iterator i = lookups.iterator(); i.hasNext();)
    {
      Persistent oneLookUp = (Persistent) i.next();

      vvMap.put(oneLookUp.getFieldString(lookUpField), oneLookUp.getFieldString(lookUpFieldDisplay));
    }

    return vvMap;
  } /* getValidValues(String) */

  /**
   * Get the status of this record.
   *
   * @return <p>
   *         NEW: Database object just created, not read from database
   *         </p>
   *
   * <p>
   * CURRENT: Database object in sync with database (just read, add or
   * updated)
   * </p>
   * <p>
   * MODIFIED: Database object has had changed made since it was last read,
   * added, or updated
   * </p>
   *
   * <p>
   * DELETED: Record has been removed from the database
   * </p>
   *
   * @return String
   */
  public int getStatus()
  {
    return currentStatus;
  } /* getStatus() */

  public String getFieldString(String fieldName) throws PersistenceException
  {
    Object o = getField(fieldName);

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

    String returnString = null;
    int typeToUse = JDBCDatabaseType.stringToType(myMetaData.getType(fieldName));

    if (Types.CLOB == typeToUse)
    {
      try
      {
        Clob fieldClob = (Clob) o;
        long myLong = 1;
        int len = new Long(fieldClob.length()).intValue();

        returnString = fieldClob.getSubString(myLong, len);
      }
      catch (SQLException sqe)
      {
        returnString = "CLOB field unavailable from database";
      }
    }
    else
    {
      returnString = getField(fieldName).toString();
    }

    return returnString;
  }

  /**
   * Return the value of a field as a Date object
   *
   * @param fieldName
   *            The field to be retrieved
   * @return The java.sql.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
   */
  public Date getFieldDate(String fieldName) throws PersistenceException
  {
    assertField(fieldName);

    Object o = getFieldData(fieldName);

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

    if (o instanceof java.sql.Date)
    {
      return (java.sql.Date) o;
    }
    else if (o instanceof java.util.Date)
    {
      java.util.Date myDate = (java.util.Date) o;

      return new Date(myDate.getTime());
    }
    else if (o instanceof java.sql.Timestamp)
    {
      return (java.sql.Date) o;
    }

    /** If it's not one of the above types, try to get it from the string */
    String strVal = o.toString();

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

    try
    {
      //DateFormat df = DateFormat.getDateTimeInstance();
      //changed by Adam...this was not working for timestamps...
      String type = myMetaData.getType(fieldName);
      String format = "yyyy-MM-dd hh:mm:ss";

      if (type.equalsIgnoreCase("date"))
      {
        format = "yyyy-MM-dd";
      }
      else if (type.equalsIgnoreCase("time"))
      {
        format = "hh:mm:ss";
      }

      SimpleDateFormat formatter = new SimpleDateFormat(format);
      java.util.Date myDate = formatter.parse(o.toString());

      return new Date(myDate.getTime());
    }
    catch (Exception de)
    {
      throw new PersistenceException("Could not extract a date value from " + o.getClass().getName() + " '"
              + o.toString() + "' for field '" + fieldName + "'", de);
    }
  } /* getFieldDate(String) */

  /**
   * Boolean typesafe getField
   */
  public boolean getFieldBoolean(String fieldName) throws PersistenceException
  {
    if (! myMetaData.hasField(fieldName))
    {
      throw new PersistenceException("(" + getName() + ") No such field as " + fieldName);
    }

    Object o = getFieldData(fieldName);

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

    if (o instanceof Boolean)
    {
      Boolean b = (Boolean) o;

      return b.booleanValue();
    }

    String strVal = o.toString();

    return SuperString.toBoolean(strVal);
  }

  /**
   * @see de.iritgo.aktera.authorization.Securable#getAuthorizationManager()
   */
  public AuthorizationManager getAuthorizationManager()
  {
    return myAuthManager;
  }

  /**
   * Do very basic data-type validation on field values
   *
   * @param fieldName
   *            Name of the field to validate
   * @throws PersistenceException
   *             If the field is not of the correct type, or a value which
   *             can be converted to the correct type.
   */
  protected void validateType(String fieldName) throws PersistenceException
  {
    int typeToUse = JDBCDatabaseType.stringToType(myMetaData.getType(fieldName));

    String value = getFieldString(fieldName);

    switch (typeToUse)
    {
      case java.sql.Types.BIGINT:
      case java.sql.Types.INTEGER:
      case java.sql.Types.SMALLINT:
      case java.sql.Types.TINYINT:

        if (! SuperString.isNumber(value))
        {
          throw new PersistenceException("Value '" + value + "' is not a number");
        }

        break;

      case java.sql.Types.BIT:
      case java.sql.Types.BOOLEAN:

        try
        {
          SuperString.assertBoolean(value, "Value '" + value + "' is not boolean");
        }
        catch (IllegalArgumentException ie)
        {
          throw new PersistenceException(ie.getMessage());
        }

        break;

      case java.sql.Types.DATE:
      case java.sql.Types.TIME:
      case java.sql.Types.TIMESTAMP:

        try
        {
          //                     SuperString ss = new SuperString(value);
          SuperString ss = new SuperString(getFieldDate(fieldName).toString());

          ss.toDate();
        }
        catch (IllegalArgumentException ie)
        {
          throw new PersistenceException(ie.getMessage());
        }

        break;

      case java.sql.Types.DECIMAL:
      case java.sql.Types.DOUBLE:
      case java.sql.Types.FLOAT:
      case java.sql.Types.NUMERIC:
      case java.sql.Types.REAL:

        if (! SuperString.isRealNumber(value))
        {
          throw new PersistenceException("Value '" + value + "' is not a number");
        }

        break;

      default:

        /* Default case is one of the "String" types, so any value */
        /* is acceptable */
        break;
    }
  }
} /* DefaultPersistentBase */ 
TOP

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

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.