/**
 * Copyright @ 2000 Peter Roßbach (pr@webapp.de)
 *
 * Source is only for non commercial and coaching usage.
 *
 * Not Warranty to use it.
 */
package de.ix.jspTutorial.database ;

import de.ix.jspTutorial.util.Null;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.util.*;

/**
 * Realisierung der Modikatoren
 * <UL>
 * <LI><em>create</em>-Anweisung zum Erzeugen eines Objektes-Objekt.
 * <LI><em>update</em>-Anweisung zum Ändern eines Objektes-Objekt.
 * <LI><em>delete</em>-Anweisung zum Löschen eines Objektes-Objekt.
 * </UL>
 * Leistungen des Services:
 * <UL>
 * <LI>Generierung der Modifkatoren Statement
 * <LI>Ausführen der Modifkatoren auf einer JDBC Connection
 * <LI>Test eines fehlerhaften Modifikation
 * </UL>
 * @author Peter Rossbach (<a href="mailto://pr@webapp.de">pr@webapp.de</a>)
 * @version $Id:$
 */
public class DatabaseGenericModifier
{
   /**
    * Version der Klasse
    */
   public static String vcid = "$Id:$";

   /**
    * Databank-Connection
    */
   private DatabaseConnection myConnection;

   /**
    *
    **/
   //############
   //Init
   //############
   public DatabaseGenericModifier()
   {
   }

   /**
    * Merke eine Verbindung zur Datenbank
    * @param connection die Verbindung des Stores.
    * @exception DatabaseException
    */
   public DatabaseGenericModifier(DatabaseConnection connection) throws DatabaseException
   {
      setConnection(connection);
   }

	  /** Diese Methode liefert den Wert des Attributes Connection.
      @return Wert der Variablen myConnection.
      */
  	public DatabaseConnection getConnection() { return myConnection; }

   /**
    * Setze JDBC verbindung
    * @param connection Kapsel der Verbindung zur DB
    */
   public void setConnection(DatabaseConnection connection)
   {
      	myConnection = connection;
			//Connection könnte mit Listener verbunden sein
   }

   //############
   //Statement Helper
   //############
   /**
    *	Erzeuge für ein JDBC- Statement eine entsprechenden Paramenterliste.
    * <P>
    * Es kommt ein Stringbuffer mit dem Inhalt <([?][, ?]*)> heraus.
    * Wenn nums kleiner 1 ist, wird <()> ergänzt.
    * @param statement der StringBuffer des Statements
    * @param nums			 die gewünschte Anzahl der Parameter
    * @return Die Anweisung mit der angehängten Parameterliste.
    */
   public StringBuffer addParameter(StringBuffer statement, int nums)
   {
      statement.append("(");
      for(int i = 0;i < nums;i++)
         if(i == 0)
            statement.append("?");
         else
            statement.append(", ?");
      statement.append(")");
      return statement;
   }

   /**
    *  Erzeuge für Create-Statement die Attributeliste.
    * <P>Beispiel
    * <xmp>
    * INSERT INTO <Tabelle> (Attr1, Attr2, ...) VALUES (WERTE)
    * </xmp>
    * @param statement der StringBuffer des Statements
    * @param attributes  die Liste der Attribute
    * @return StringBuffer der um die Parameter ergänzte Buffer
    */
   public StringBuffer generateAttributes(StringBuffer statement, I_DatabaseGenericObject aObject)
   {
      statement.append("(");
      for(Enumeration enum = aObject.keys();enum.hasMoreElements();)
      {
      	  String aKey = (String) enum.nextElement() ;
         statement.append(aKey);
         if(enum.hasMoreElements())
            statement.append(", ");
      }
      statement.append(")");
      return statement;
   }

   /**
    *	Erzeuge für ein JDBC- Update Statement die Paramenterliste.
    * <P>
    * Es kommt ein Stringbuffer mit dem Inhalt <([attribute = ?][, attribute = ?]*)> heraus.
    * @param statement der StringBuffer des Statements
    * @param attributes	die Liste der Attribute
    * @return StringBuffer der um die Parameter ergänzte Buffer
    */
   public StringBuffer addUpdateValueStatement(StringBuffer statement, I_DatabaseGenericObject aObject)
   {
      for(Enumeration enum = aObject.keys();enum.hasMoreElements();)
      {
      	  String aKey = (String) enum.nextElement() ;
         statement.append(aKey + " = ?");
         if(enum.hasMoreElements())
            statement.append(", ");
      }
      return statement;
   }

   /**
    * Erzeuge Schlüsselqualifier
    * Heraus kommt
    * <xmp>
    * WHERE Key = ?
    * </xmp>
    * @param statement der StringBuffer des Statements
    * @param aKey attribute
    * @return StringBuffer der um die Parameter ergänzte Buffer
    */
   public StringBuffer addKeyQualifier(StringBuffer statement, String aKey)
   {
   		if(aKey != null) {
	   			statement.append(" WHERE ") ;
	      	statement.append(aKey + " = ?");
   		}
     return statement;
   }

   /**
    * Setze Statement-Value und berücksichtige, wenn der Wert <em>Null</em> ist.
    * @param statement	das Statement
    * @param value			der Wert
    * @param index			die Position des Wertes im Statement
    * @exception SQLException falsche Position im Statement
    */
   public void setStatementValue(PreparedStatement statement, Object value, int index) throws SQLException
   {
      // setze Wert
      if(value != null && value != Null.Null())
      {
         statement.setObject(index,value);
      }
      else
      {
         // bei null speziellen Funktion
         statement.setNull(index,java.sql.Types.OTHER);
      }
   }

   /**
    * Fülle die Werte eines Objekts in das vorbereitete Statement.
    * <P>
    * Das Objekt wird nach seinen Attributen gefragt. Die Attribute können
    * die Werte aus einem Objekt direkt bestimmen. Falls ein Wert <em>null</em> ist, wird dies
    * für den speziellen Typ mit setNull(index,Typ) gesetzt, sonst wird setObject(index,Object) verwendet.
    * Die Konvertierung des Datentyps auf die DB über nimmt der JDBC-Driver.
    * @param statement das JDBC-Statement
    * @param aObject das Objektes-Object
    * @return PerparedStatement das vorbereitete Statement
    * @exception DatabaseException
    */
   public PreparedStatement fillValues(
   	 PreparedStatement statement,
   	 I_DatabaseGenericObject aObject)
   	 throws DatabaseException
   {
      try
      {
         // Hole Liste der Attribute
      	  int i = 0 ;
		      for(Enumeration enum = aObject.keys();enum.hasMoreElements();)
		      {
		      	  String aAttribute = (String) enum.nextElement() ;
		      	  Object value;
            // Hole Wert des Attribute
            value = aObject.getValue(aAttribute);
            setStatementValue(statement,value,i + 1);
		         i++ ;
		      }
		      return statement;
		    }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command fillValues -- Object " + aObject + " failed",sqe);
      }
   }

   //############
   //Statements für die Änderung von Objekten
   //############
   /**
    * Gebe ein Create-Statement an.
    * Beispiel
    * <xmp>
    * INSERT INTO ENTITY (ATTRIBUTE) VALUES (WERTE)
    * </xmp>
    * @param aType	der gewünschte Typ
    * @return eine Create-Anweisung
    * @exception DatabaseException
    */
   public PreparedStatement createStatement(I_DatabaseGenericObject aObject,String aEntity)
   	 throws DatabaseException
   {
      PreparedStatement stst = null;
      // Aufbau des Statments
      StringBuffer statement = new StringBuffer("INSERT INTO " + aEntity + " ");
      generateAttributes(statement,aObject);
      statement.append(" VALUES ");

      addParameter(statement,aObject.size());
      System.out.println("Statement " + statement);
      try
      {
         // Übergabe der Werte für das Statement
         stst = myConnection.prepareStatement(new String(statement));
      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Insert -- aTyp " + aEntity + " failed ",sqe);
      }
      return stst;
   }

   /**
    * Gebe ein Update-Statement an.
    * Beispiel
    * <xmp>
    * UPDATE ENTITY set ATTRIBUTE=VALUE, ...
    * </xmp>
    * @param aType	der gewünschte Typ
    * @return eine Update-Anweisung
    * @exception DatabaseException
    */
   public PreparedStatement updateStatement(I_DatabaseGenericObject aObject, String aEntity, String aKey)
   		throws DatabaseException
   {
      PreparedStatement stst = null;
      // Aufbau des Statments
      StringBuffer statement = new StringBuffer("UPDATE " + aEntity + " SET ");
      addUpdateValueStatement(statement,aObject);
      addKeyQualifier(statement,aKey);
      System.out.println("Statement " + statement);
      try
      {
         // Übergabe der Werte für das Statement
         stst = myConnection.prepareStatement(new String(statement));
      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Update -- aTyp " + aEntity + " failed ",sqe);
      }
      return stst;
   }

   /**
    * Gebe ein Delete-Statement an.
    * Beispiel
    * <xmp>
    * DELETE FROM ENTITY WHERE ObjectIdentifier = x AND ObjectVersion = y
    * </xmp>
    * @param aType	der gewünschte Typ
    * @return eine Delete-Anweisung
    * @exception DatabaseException
    */
   public PreparedStatement deleteStatement(I_DatabaseGenericObject aObject,String aEntity, String aKey) throws DatabaseException
   {
      PreparedStatement stst = null;
      // Aufbau des Statments
      StringBuffer statement = new StringBuffer("DELETE FROM " + aEntity);
      addKeyQualifier(statement,aKey);
      System.out.println("Statement " + statement);
      try
      {
         // Übergabe der Werte für das Statement
         stst = myConnection.prepareStatement(new String(statement));
      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Delete -- aTyp " + aEntity + " failed ",sqe);
      }
      return stst;
   }

   /**
    * Einfügen eines Datensatz mit einem vorgefertigten Statement.
    * @param statement eine Create-Anweisung
    * @param aObject das Objektes-Objekt
    * @return true, wenn der Datensatz eingefügt wurde, sonst false
    * @exception DatabaseException
    */
   public boolean create(PreparedStatement statement, I_DatabaseGenericObject aObject) throws DatabaseException
   {
      try
      {
         fillValues(statement,aObject);
         System.out.println("Statement create " + statement.toString() + " / " + aObject);
         return statement.executeUpdate() == 1 || statement.getUpdateCount() != -1;
      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Create --  Object " + aObject + " failed ",sqe);
      }
   }

   /**
    * Ändern eines Datensatz mit einem vorgefertigten Statement.
    * <P> Die Werte kommen aus dem Object aObject.
    * Im Qualifier werden noch zusätzlich die ObjectID und der Version
    * zur Bestimmung des Objektes angenommen.
    * @param statement eine Update-Anweisung
    * @param aObject das Objektes-Objekt
    * @return true, wenn der Datensatz geändert wurde, sonst false
    * @exception DatabaseException
    */
   public boolean update(PreparedStatement statement, I_DatabaseGenericObject aObject,String aKey)
   		throws DatabaseException
   {
      try
      {
         // Fülle Objektwerte
         fillValues(statement,aObject);
         setStatementValue(statement,aObject.getValue(aKey),aObject.size()+ 1);
         System.out.println("Statement update " + statement + " / " + aObject);
         return statement.executeUpdate() == 1 || statement.getUpdateCount() != -1;

      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Update -- Object " + aObject + " failed ",sqe);
      }
   }

   /**
    * Löschen eines Datensatz mit einem vorgefertigten Statement.
    * <P> Die Werte kommen aus dem Object aObject. Nur wenn, ObjectID
    * und ObjectVersion korrekt sind wird das Objekt wirklich gelöscht
    * @param statement eine Delete-Anweisung
    * @param aObject das Objektes-Objekt
    * @return true, wenn der Datensatz gelöscht wurde, sonst false
    * @exception DatabaseException
    */
   public boolean delete(PreparedStatement statement, I_DatabaseGenericObject aObject,String aKey) throws DatabaseException
   {
      try
      {
       	 setStatementValue(statement,aObject.getValue(aKey),1);
         System.out.println("Statement delete " + statement + " / " + aObject);
         return statement.executeUpdate() == 1 || statement.getUpdateCount() != -1;
      }
      catch(SQLException sqe)
      {
         throw new DatabaseException("Command Delete -- Object " + aObject + " failed ",sqe);
      }
   }

   /**
    * Erzeuge eine Objektes auf der Datenbank
    * @param aObject das Objektes das auf der DB gespeichert werden soll.
    * @return Erfolg oder nicht
    * @exception DatabaseException
    */
   public boolean create(I_DatabaseGenericObject aObject,String aEntity) throws DatabaseException
   {
   		PreparedStatement theStatement = null ;
   		try {
   			theStatement = createStatement(aObject, aEntity) ;
   			if(theStatement!= null)
	      	return create(theStatement,aObject);
  			else
  				return false ;
   		} finally {
   			if(theStatement!= null) {
   				try {
   					theStatement.close() ;
   				} catch (SQLException se) {}
   			}
   		}
   }

   /**
    * Änderung eines Objektes auf der Datenbank speichen
    * @param aObject das Objektes das auf der DB gespeichert werden soll.
    * @return Erfolg oder nicht
    * @exception DatabaseException
    */
   public boolean update(I_DatabaseGenericObject aObject,String aEntity,String aKey) throws DatabaseException
   {
   		PreparedStatement theStatement = null ;
   		try {
   			theStatement = updateStatement(aObject,aEntity,aKey) ;
   			if(theStatement!= null)
	      	return update(theStatement,aObject,aKey);
  			else
  				return false ;
   		} finally {
   			if(theStatement!= null) {
   				try {
   					theStatement.close() ;
   				} catch (SQLException se) {}
   			}
   		}
   }

   /**
    * Löschen eines Objektes auf der Datenbank
    * @param aObject das Objektes das auf der DB gelöscht werden soll.
    * @return Erfolg oder nicht
    * @exception DatabaseException
    */
   public boolean delete(I_DatabaseGenericObject aObject,String aEntity,String aKey) throws DatabaseException
   {
   		PreparedStatement theStatement = null ;
   		try {
   			theStatement = deleteStatement(aObject,aEntity,aKey) ;
   			if(theStatement!= null)
	      	return delete(theStatement,aObject,aKey);
  			else
  				return false ;
   		} finally {
   			if(theStatement!= null) {
   				try {
   					theStatement.close() ;
   				} catch (SQLException se) {}
   			}
   		}
   }

   /**
    * Erzeuge die nächste Sequenznummer.
    * @exception SQLException
    */
   public long getNextSequenceNumber()
				throws SQLException
	 {
					return getNextSequenceNumber(null) ;
	 }

   /**
    * Erzeuge eine Sequenznummer. Diese Opperation muss immer in
    * einem Transaktionsrahmen auf der Verbindung stattfinden.
    * @param aSequence Entity der Sequence
    * @exception SQLException
    */
   public long getNextSequenceNumber(String aSequence)
				throws SQLException {

					if(aSequence == null)
						aSequence = "sequence" ;

        long seqNum = 0;
        Statement theStatement = null ;
        ResultSet theResultSet = null ;

        try {
	        theStatement = myConnection.createStatement();

	        //theResultSet = theStatement.executeQuery("SELECT seqnum FROM " + aSequence  + " FOR UPDATE");
	        theResultSet = theStatement.executeQuery("SELECT seqnum FROM " + aSequence );
	        if(theResultSet.next())
	            seqNum = theResultSet.getLong(1);
	        else
	           throw new RuntimeException ("no Sequence " + aSequence + "in database " );
	        //int resultCount = theStatement.executeUpdate("UPDATE " + aSequence + " SET seqnum = seqnum + 1");
	        int resultCount = theStatement.executeUpdate("UPDATE " + aSequence + " SET seqnum = "+ (seqNum +1));
	        if ( resultCount != 1 )
	            throw new SQLException("can not update " + aSequence );
        } finally {
        		if(theResultSet != null)
        			theResultSet.close() ;
        		if(theStatement != null)
	        			theStatement.close();
        }

        return (seqNum);
    }

}

// end of class


//
// History:
//
// $Log:$
//
//
