/*

 * 09/01/2003 - 21:29:27

 *

 * $RCSfile: KeyKeeper.java,v $ - JdbF Object Relational mapping system

 * Copyright (C) 2002 JDBF Development Team

 * 

 * http://jdbf.sourceforge.net

 *

 * This program is free software; you can redistribute it and/or

 * modify it under the terms of the GNU Lesser General Public License

 * as published by the Free Software Foundation; either version 2

 * of the License, or (at your option) any later version.

 *

 * This program is distributed in the hope that it will be useful,

 * but WITHOUT ANY WARRANTY; without even the implied warranty of

 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

 * GNU Lesser General Public License for more details.

 *

 * You should have received a copy of the GNU Lesser General Public License

 * along with this program; if not, write to the Free Software

 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 */



/*



 $Id: KeyKeeper.java,v 1.7 2004/05/20 22:40:02 gmartone Exp $



*/



package org.jdbf.engine.keygen;

   
import java.sql.*;
import java.util.logging.Logger;
import java.util.logging.Level;

import org.jdbf.castor.Messages;
import org.jdbf.engine.mapping.*;
import org.jdbf.engine.sql.SqlInterface;



/**

 *<code>KeyKeeper</code> provides generated keys
 *for HighLow keyGenerator

 *

 * The method is similar to the one called Key-Values by Scott Ambler

 * in <a href="http://www.Ambysoft.com/mappingObjects.pdf">Mapping

 * Objects to Relational Databases</a>.

 * With this method, there's a multi-row table which has three columns,

 * one for the identifier for the table name, one for the identifier for the key table

 * and another for the next key value for that table.

 *

 * @author Giovanni Martone

 * @version $Revision: 1.7 $

 * last changed by $Author: gmartone $

 *

 */

public class KeyKeeper {

    
	/**
	 * Class name
	 */
    private static final String CLASS_NAME = "org.jdbf.engine.keygen.KeyKeeper";



    /** 

     * Represents the sql type for high value 

     */

    private static int DATA_TYPE = Types.VARCHAR; 

    

    

    /** 

     * Represents the progressive to add the high forn new insert operation

     */

    private static int PROGRESSIVE = 1;

    

    

    /** 

     * Represents the tyoe of column of high value

     */

    private int columnType = -1;



    

    /**

     * The key is generated for this table.

     */

    private String tableName;



    

    /**

     * Indicates if KeyKeeper is used

     */

    private boolean isUsed = false;

        

    

    /**

     * The high value is combined with the low value to

     * produce the key.

     */

    private HighLowMap hiloMap;





    /**

     * SqlInterface object

     */

    private SqlInterface sqlInterface;



    

    /**

     * Logger object

     */

    private Logger logger;



                    

    /**

     * Creates the object given table name,given hiloMap
     * and current sqlInterface

     *

     * @param tableName

     * @param hiloMap
     * @param sqlInterface

     */

    public KeyKeeper (String tableName,HighLowMap hiloMap,
    				  SqlInterface sqlInterface){

		logger = Logger.getLogger(CLASS_NAME);
		this.tableName = tableName;
		this.hiloMap = hiloMap;
		this.sqlInterface = sqlInterface;

    }



        

    /**

     * Obtains a block of available keys 
     * from the key-values table.

     *

     * @param conn java.sql.Connection

     * @param vendor name of database vendor

     * @return HighLowMap the value beyond the block of available keys,

     * @exception KeyGenerationException if an error occurs

     */

    private HighLowMap fetchHigh(Connection conn,String vendor)

        throws KeyGenerationException{

	

		logger.log(Level.INFO,Messages.message("KeyKeeper.fetchHigh"));

		/*
	 	 * HighLowMap holds three things:  
	 	 * 1) name of the table used to store two columns. 
	 	 * 2) column name for identifier  for table name and 
	 	 * 3) column name for the next key for that table.
	 	 */
		StringBuffer condition = new StringBuffer(32);		
		condition.append(hiloMap.getTableColumn()).append("=?");

		String sql = sqlInterface.getSelectStatement(hiloMap.getTable(),
													 "*",
													 condition.toString()
										);

		logger.log(Level.FINEST,Messages.format("KeyKeeper.highLowQuery",
		                                sql,tableName));

	
		ResultSet rs = null;
		PreparedStatement pstmt = null;

		try{			  

			// First get column type of key.

			if (columnType == -1) {
				columnType = getColumnType(conn, 
										   hiloMap.getTable(),
						   				   hiloMap.getKeyColumn());

			}

    
			if (columnType != DATA_TYPE){					
				logger.throwing(CLASS_NAME,"fetchHigh()",
			    			new KeyGenerationException(
			    				Messages.format("mapping.keyGenWrongType", 
				   				new Integer(columnType),getClass())
		           ));
		           
				throw new KeyGenerationException(
						Messages.format("mapping.keyGenWrongType", 
						new Integer(columnType),getClass()));  
			}

			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1,tableName);

			rs = pstmt.executeQuery();

			//Get the high and low values and creates the key
			//then updates the low values adding to last low the 
			//value of PROGRESSIVE variable and update the table 
			//that contains the highlow
			if (rs.next()) {

				hiloMap.setHigh(rs.getString(2));
				hiloMap.setLow(rs.getObject(3));
			}
			else{
				logger.throwing(CLASS_NAME,"fetchHigh()",
			    		new KeyGenerationException("mapping.MissingDataKeyGen",
							hiloMap.getTable(),tableName)

		         );
		         
		         throw new KeyGenerationException("mapping.MissingDataKeyGen",
						hiloMap.getTable(),tableName);
			}
		}
		catch (SQLException excep) {

			logger.throwing(CLASS_NAME,"fetchHigh()",
			    new KeyGenerationException(excep));
			
			throw new KeyGenerationException(excep);
		}
		finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null) {
					pstmt.close();
				}
				pstmt = null;
			}
			catch (SQLException excep) {
				logger.throwing(CLASS_NAME,"fetchHigh()",
			    	new KeyGenerationException(excep));
			    	
				throw new KeyGenerationException(excep);
			}
		}
		logger.log(Level.FINEST,hiloMap.toString());
		logger.log(Level.INFO,Messages.message("KeyKeeper.fetchedHigh"));
		return hiloMap;
    }

	
    /**

     * Return column's type fo column specified in column parameter.

     *

     * @param conn

     * @param table name of table

     * @param column name of column

     * @return type of dataType specified in int

     * @throws SQLException if error occurs

     * @throws KeyGenerationException if error occurs

     *

     */

    private final int getColumnType(Connection conn, String table, String column)

		throws SQLException, KeyGenerationException{

		

		logger.log(Level.INFO,Messages.message("KeyKeeper.getColumnType"));
	
		ResultSet rs = null;
	
		int dataType = -1;
	
		try {
	
		    // Using meta data because many DBMS can not
	
		    // pre-compile statements.
	
		    DatabaseMetaData dmd = conn.getMetaData();
	
		    rs = dmd.getColumns(null , null, table, column);
	
	
	
		    synchronized (this) {
	
		        if (rs.next()) {
	
		   	    dataType =  rs.getInt("DATA_TYPE");
	
			    logger.log(Level.FINEST,Messages.message("dataType: " + dataType));
	
			    return dataType;
	
			}
	
			logger.throwing(CLASS_NAME,"getColumnType()",
	
				    new KeyGenerationException("mapping.checkKeyGen",
	
				        getClass().getName()
	
				    ));
			throw new KeyGenerationException("mapping.checkKeyGen",
											 getClass().getName());
		    }
	
		}	
		finally{
	
		    if (rs != null) {
	
				rs.close();
	
				rs = null;
	
		    }
	
		    logger.log(Level.INFO,Messages.message("KeyKeeper.getColumnTypeFinish"));
	
		    return dataType;
	
		}

    }

	

	

    /**

     * Returns the next generated key.

     *

     * @param conn database connections for the key-values table

     * @param vendor name of database vendor

     * @return Object the generated key

     * @exception KeyGenerationException if an error occurs

     */

    public synchronized Object nextKey(Connection conn,String vendor)
		throws KeyGenerationException{



		logger.log(Level.INFO,Messages.message("KeyKeeper.nextKey"));
	
		
	
		Object key = null;
	
		
	
		if (!isUsed){		
	
		    hiloMap = fetchHigh(conn,vendor);
	
		    isUsed = true;
	
	        }	    
	
		int  low = Integer.parseInt(hiloMap.getLow().toString());
	
		int iLow = low + PROGRESSIVE;
	
		String cKey = hiloMap.getHigh() + String.valueOf(iLow);
	
	        key = (cKey);
	
		
	
		logger.log(Level.FINEST,Messages.format("KeeyKeeper.highLowKey",key));
	
		
	
		Object sLow = (String.valueOf(iLow)); 
	
		hiloMap.setLow(sLow);
	
		updateLow(conn,hiloMap);
	
	
	
		logger.log(Level.INFO,Messages.message("KeyKeeper.nextKeyFinished"));
	
		return key;

    }





    /**

     * Updates in table where keyGenerators are mapped the new low value specified

     * in table.

     *

     * @param conn, 
     * @param hiloMap HighLowMap object.

     * @return row number of rows affected.

     * @throws KeyGenerationException if occurs

     */

    private int updateLow(Connection conn, HighLowMap hiloMap) throws KeyGenerationException{

        

		logger.log(Level.INFO,Messages.message("KeyKeeper.updateLow"));
	
		int row = 0;
	
		PreparedStatement pstmt = null; 
	
		SqlInterface sqlInterface = new SqlInterface();
	
		StringBuffer condition = new StringBuffer(32);
	
		condition.append(hiloMap.getTableColumn()).append("=?");
	
		condition.append(" " + SqlInterface.AND  + hiloMap.getKeyColumn() + "=?");
	
	
	
		String setNewValue = hiloMap.getNextColumn() + "=" + hiloMap.getLow().toString();
	
	
	
		//Updates value
	
		String sql = sqlInterface.getUpdateStatement(hiloMap.getTable(),setNewValue,
	
					      condition.toString());
	
		
	
		Object[] params = {sql,tableName,hiloMap.getHigh()};
	
		logger.log(Level.FINEST,Messages.format("KeeyKeeper.updateLowQuery",params));
	
	
	
		try{
	
		    pstmt = conn.prepareStatement(sql);
	
		    pstmt.setString(1,tableName);
	
		    pstmt.setString(2,hiloMap.getHigh());
	
		    row = pstmt.executeUpdate();
	
	        }
	
		catch(SQLException excep){
	
		    logger.throwing(CLASS_NAME,"updateLow()",
		    			    new KeyGenerationException(excep));
			throw new KeyGenerationException(excep);
		}
	
		finally{
	
		    try{
	
				if(pstmt != null)
	
			    	pstmt.close();	    
	
				pstmt = null;
	
		    }
		    catch(SQLException ex){
	
				logger.throwing(CLASS_NAME,"updateLow()",
				    		    new KeyGenerationException(ex));
				throw new KeyGenerationException(ex);
		    }
	
		}
	
		logger.log(Level.INFO,Messages.message("KeyKeeper.updatedLow"));
	
		return row;

    }

}



/*

  $Log: KeyKeeper.java,v $
  Revision 1.7  2004/05/20 22:40:02  gmartone
  Changed for task 99073 (Coverage Javadocs)

  Revision 1.6  2004/04/19 23:48:59  gmartone
  changed type of Exception thrown in all methods from MappingException to KeyGenerationException

  Revision 1.5  2004/04/05 22:06:31  gmartone
  *** empty log message ***




*/

