/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Stuart E. Middleton
//      Created date:           2004/06/08
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.server;

import java.lang.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.rmi.RemoteException;

import uk.ac.soton.itinnovation.gemss.negotiation.auction.AuctionMessageBody;
import uk.ac.soton.itinnovation.gemss.negotiation.auction.AuctionMessageHeader;
import uk.ac.soton.itinnovation.gemss.negotiation.auction.AuctionMessage;
import uk.ac.soton.itinnovation.gemss.negotiation.auction.AuctionMessageHandler;
import uk.ac.soton.itinnovation.gemss.negotiation.auction.AuctionMessageHandlerFIPAImpl;

import uk.ac.soton.itinnovation.gemss.negotiation.wsla.WSLAQoSHandler;
import uk.ac.soton.itinnovation.gemss.negotiation.wsla.WSLAQoSHandlerImpl;
import uk.ac.soton.itinnovation.gemss.negotiation.owl.OWLIndividualHandler;
import uk.ac.soton.itinnovation.gemss.negotiation.owl.QoSOntologyImpl;

import uk.ac.soton.itinnovation.gemss.negotiation.AuctionDetailsXML;
import uk.ac.soton.itinnovation.gemss.negotiation.CallForProposalsXML;
import uk.ac.soton.itinnovation.gemss.negotiation.evaluator.QoSEvaluator;
import uk.ac.soton.itinnovation.gemss.business.PriceModel;
import uk.ac.soton.itinnovation.gemss.state.StateRepository;

import at.ac.univie.iss.service.qos.QoSHandler; // ISS's QoS component interface
import at.ac.univie.iss.service.global.ServiceInfo; // ISS's info component


/**
 * Server side module to provide the application service with methods that handle the server
 * side negotiation. RemoteException's are thrown so they can be passed on through by the
 * server. There will be one handler instance per service.
 */
public class NegotiationHandlerImpl implements NegotiationHandler {

      // server data
      private String mstrOntologyURI; // ontology URI (for FIPA header stuff)
      private String mstrPropStartTime; // start time property URI
      private String mstrPropEndTime; // end time property URI
      private String mstrPropPrice; // price property URI
      private long mnContractValidityOffset; // validity time after job end time (e.g. 1 week) when client can still access results
      private int mnMaxWSLAAdjustments; // max number of times the WSLA can be adjusted before we give up

      // TODO replace this with a state object, one PER service
      private String mstrWSLATemplateFilename;

      private QoSEvaluator mQoSEvaluator; // QoS evaluator to score WSLA
      private PriceModel mPriceModel; // business model to price WSLA
      private WSLAQoSHandler mwslaHandler; // WSLA handler for parsing etc
      private StateRepository mstateRepository; // state repository for this server
      private QoSHandler mqoshandler; // ISS's QoS management handler

      // state repository object tags
      private static final String mstaticObjectAccountID = "ServerAccountID";
      private static final String mstaticObjectNegID = "ServerNegID";
      private static final String mstaticObjectWSLATemplate = "ServerWSLATemplate";
      private static final String mstaticObjectLastBid = "ServerLastBid";
      private static final String mstaticObjectLastCall = "ServerLastCall";
      private static final String mstaticObjectRequestDesc = "ServerRequestDescFilename";
      private static final String mstaticObjectSignedContract = "ClientSignedContract";
      private static final String mstaticObjectAuctionDetails = "ServerAuctionDetails";

      // config tags
      private static final String mstaticConfigOntologyURI = "OntologyURI";
      private static final String mstaticConfigPropStartTime = "PropStartTime";
      private static final String mstaticConfigPropEndTime = "PropEndTime";
      private static final String mstaticConfigPropPrice = "PropPrice";
      private static final String mstaticConfigWSLATemplate = "WSLATemplate";
      private static final String mstaticConfigWSLAHandler = "WSLAHandlerConfigFile";
      private static final String mstaticConfigEvaluator = "EvaluatorConfigFile";
      private static final String mstaticConfigContractValidityOffset = "ContractValidityOffset";
      private static final String mstaticConfigMaxWSLAAdjustments = "MaxWSLAAdjustments";
      private static final String mstaticConfigStateRepository = "StateRepository";

      // logging
      private Logger mlogger;

      /**
       * default constructor, inits vars are reads the config file
       * @param strConfig negotiation handler configuration file
       * @param qoshandler ISS's QoS handler object, or null if this is not available
       */
      public NegotiationHandlerImpl()
      {
            mQoSEvaluator = null;
            mPriceModel = null;
            mwslaHandler = null;
            mstrOntologyURI = null;
            mstrPropStartTime = null;
            mstrPropEndTime = null;
            mstrPropPrice = null;
            mnContractValidityOffset = -1;
            mnMaxWSLAAdjustments = -1;
            mstateRepository = null;
            mqoshandler = null;

            mlogger = ServiceInfo.getLogger("negotiation"); // TODO sort out server side logging
            mlogger.log(Level.INFO, "Started.\n");
      }

      /**
       * initialize the handler
       * @param strConfig negotiation handler configuration file
       * @param qoshandler ISS's QoS handler object, or null if this is not available
       */
      public void init( String strConfig, QoSHandler qoshandler ) throws RemoteException
      {
            mqoshandler = qoshandler;

            // read config
            loadConfig( strConfig );
      }

      /**
       * getJobCID handler for the getJobCID method
       * @param strNegConvID negotiation conversation ID
       * @return job conversation ID (or "" if job ID not available in this conversation)
       */
      public String getJobCID( String strNegConvID ) throws RemoteException
      {
            String strID, strLastBid;

            try {
                  // make ID
                  strID = "jobID"+System.currentTimeMillis()+"--"+strNegConvID;

                  // add to state repository (new key will be created)
                  // AND get the agreed WSLA (accepted contract)
                  // TODO use conv auth to make ID's
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strID,mstaticObjectNegID,strNegConvID ); // record parent (forces state entry)
                  strLastBid = (String) mstateRepository.getObject( strNegConvID, mstaticObjectLastBid );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  // tell the QoS management module what the new job ID is, so it can associate
                  // it with the previously agreed WSLA
                  if( mqoshandler != null ) {
                        if( strLastBid != null ) {
                              mqoshandler.setJobConvId( strLastBid, strID );
                        }
                        else mlogger.log( Level.INFO,"NegHandler: No agreed WSLA so not calling qoshandler.setJobConvID()" );
                  }

                  mlogger.log( Level.INFO,"NegHandler: New conversation (job) = "+strID );
                  return strID;
            }
            catch( RemoteException rex ) {
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw rex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to getJobCID",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "getJobCID failed",ex );
            }
      }

      /**
       * getNegCID handler for the getJobCID method
       * @param strAccountConvID negotiation conversation ID
       * @return negoitation conversation ID (or "" if neg ID not available in this conversation)
       */
      public String getNegCID( String strAccountConvID ) throws RemoteException
      {
            String strID;

            try {
                  // make ID
                  strID = "negID"+System.currentTimeMillis()+"--"+strAccountConvID;

                  // add to state repository (new key will be created)
                  // TODO use conv auth to make ID's
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strID,mstaticObjectAccountID,strAccountConvID ); // record parent (forces state entry)
                  mstateRepository.save();
                  mstateRepository.unlock();

                  mlogger.log( Level.INFO,"NegHandler: New conversation (neg) = "+strID );
                  return strID;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to getNegCID",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "getNegCID failed",ex );
            }
      }

      /**
       * remove conversation from the state repository permanently. This should only be called when an auction has been
       * timed out or the acknowledge results operation has been called.
       * TODO This needs more work for final release, with overall timeouts etc.
       * @param strNegConvID negotiation conversation ID
       */
      public void deleteConversation( String strNegConvID ) throws RemoteException
      {
            try {
                  // find and delete conversation within the state repository
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.deleteKey( strNegConvID );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  mlogger.log( Level.INFO,"NegHandler:  Conversation deleted = "+strNegConvID );
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to deleteConversation",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "deleteConversation failed",ex );
            }
      }

      /**
       * check to see if a conversation ID is authorized
       * for now this simply checks the conversation exists
       * TODO use the conv auth system
       * @param strConvID conversation ID
       * @return true of the conversation is authorized
       */
      public boolean queryAuthorization( String strConvID ) throws RemoteException
      {
            boolean bOk;

            try {
                  // find conversation within the state repository
                  mstateRepository.lock();
                  mstateRepository.load();
                  bOk = mstateRepository.checkKey( strConvID );
                  mstateRepository.unlock();

                  mlogger.log( Level.INFO,"NegHandler:  Conversation "+strConvID+" authorization = "+bOk );
                  return bOk;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to queryAuthorization",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "queryAuthorization failed",ex );
            }
      }

      /**
       * Return true if a WSLA contract has been agreed, or false if not for whatever reason.
       * @param strConvID conversation ID
       * @return true of a contract has been agreed
       */
      public boolean queryContractAgreed( String strJobConvID ) throws RemoteException
	  {
		  String strContract, strNegID;

		  try
		  {
			  // get the Neg ID from this job ID
			  // TODO replace this with a call to Auth or something else
			  if( strJobConvID.length() < 1 ) return false;
			  if( strJobConvID.indexOf("--") == -1 || strJobConvID.indexOf("--") == strJobConvID.length() ) return false;
			  strNegID = strJobConvID.substring( strJobConvID.indexOf("--")+2 );
			  mlogger.log( Level.INFO,"Checking contract for neg "+strNegID+", derived from job "+strJobConvID );

			  // get contract if there is one
			  mstateRepository.lock();
			  mstateRepository.load();
			  strContract = (String) mstateRepository.getObject( strNegID, mstaticObjectSignedContract );
			  mstateRepository.unlock();

			  // return false if no agreed contract
			  if( strContract == null ) {
				  mlogger.log( Level.INFO,"No previous contract agreed - app handler will be used");
				  return false;
			  }

			  // ok - we have a contract
			  mlogger.log( Level.INFO,"Previous contract agreed - qos handler will be used");
			  return true;
		  }
		  catch( Exception ex )
		  {
                  mlogger.log( Level.SEVERE,"Failed to queryContractAgreed",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "queryContractAgreed failed",ex );
		  }
      }


      /**
       * getWSLA handler for the getWSLA method
       * @param strNegConvID negotiation conversation ID
       * @param strServiceName service name
       * @return WSLA string in its XML format
       */
      public String getWSLA( String strNegConvID, String strServiceName ) throws RemoteException
      {
            String strWSLATemplate, strLine;
            BufferedReader reader;

            try {
                  // read WSLA file to string
                  mlogger.log( Level.INFO,"NegHandler: reading template from "+mstrWSLATemplateFilename);
                  reader = new BufferedReader( new FileReader( mstrWSLATemplateFilename ) );
                  strLine = "";
                  strWSLATemplate = "";
                  while( strLine != null ) {
                        strLine = reader.readLine();
                        if( strLine != null ) strWSLATemplate += strLine+"\n";
                  }
                  reader.close();
                  mlogger.log( Level.INFO,"NegHandler:  WSLA uploaded ok");

                  // record WSLA template into state repository
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strNegConvID, mstaticObjectWSLATemplate, strWSLATemplate );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  // all done
                  return strWSLATemplate;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to getWSLA",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "getWSLA failed",ex );
            }
      }

      /**
       * uploadRequestDesc
       * @param strNegConvID negotiation conversation ID
       * @param strRequestFilename
       */
      public void uploadRequestDesc( String strNegConvID, String strRequestFilename ) throws RemoteException
      {
            String strRequestDesc, strLine;
            BufferedReader reader;

            try {
                  // read all the request descriptor from file into a string (XML document)
                  reader = new BufferedReader( new FileReader( strRequestFilename ) );
                  strLine = "";
                  strRequestDesc = "";
                  while( strLine != null ) {
                        strLine = reader.readLine();
                        if( strLine != null ) strRequestDesc += strLine + "\n";
                  }

                  // TODO check to see if AXIS is deleting these RequestDesc files as Darren thinks it should

                  // keep request desc string in the neg state database
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strNegConvID, mstaticObjectRequestDesc, strRequestDesc );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  mlogger.log( Level.INFO,"NegHandler:  Request desc uploaded ok" );
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to uploadRequestDesc",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "uploadRequestDesc failed",ex );
            }
      }

      /**
       * auctionInform
       * @param strNegConvID negotiation conversation ID
       * @param strMessage auction XML message
       */
      public void auctionInform( String strNegConvID, String strMessage ) throws RemoteException
      {
            AuctionDetailsXML details;
            String strXML;
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;

            try {
                  // extract content from FIPA message
                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();
                  message = new AuctionMessage( strMessage );

                  // check type
                  if( messageHandler.parseMessageType( message ) != AuctionMessageHandler.mstaticInformType ) {
                        mlogger.log( Level.INFO,"NegHandler:  Invalid message type - not inform");
                        return;
                  }

                  // get header
                  header = messageHandler.parseMessageHeader( message );

                  // parse body
                  body = messageHandler.parseMessageBody( message );

                  strXML = body.toString();

                  // check AuctionDetails XML
                  // parse XML
                  details = new AuctionDetailsXML();
                  if( !details.parseAuctionDetails( strXML ) ) {
                        mlogger.log( Level.INFO,"NegHandler:  Failed to parse auction details");
                        return;
                  }

                  // log info
                  mlogger.log( Level.INFO, "NegHandler:  Max rounds = "+details.getMaxRounds() );
                  mlogger.log( Level.INFO, "NegHandler:  Start time = "+details.getStartTime() );
                  mlogger.log( Level.INFO, "NegHandler:  Max duration = "+details.getMaxDuration() );

                  // add to state repository
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strNegConvID, mstaticObjectAuctionDetails, strXML );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  // all done
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionInform",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionInform failed",ex );
            }
      }

      /**
       * auctionCFP
       * @param strNegConvID negotiation conversation ID
       * @param strMessage auction CFP message
       */
      public void auctionCFP( String strNegConvID, String strMessage ) throws RemoteException
      {
            String strCFP;
            int i;
            Vector vector;
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            CallForProposalsXML cfpXML;

            try {
                  // extract content from FIPA message
                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();
                  message = new AuctionMessage( strMessage );

                  // check type
                  if( messageHandler.parseMessageType( message ) != AuctionMessageHandler.mstaticCFPType ) {
                        mlogger.log( Level.INFO,"NegHandler:  Invalid message type - not cfp");
                        return;
                  }

                  // get header
                  header = messageHandler.parseMessageHeader( message );

                  // parse body
                  body = messageHandler.parseMessageBody( message );

                  strCFP = body.toString();

                  // create a CFP object to parse this string
                  cfpXML = new CallForProposalsXML();
                  if( !cfpXML.parseCFP( strCFP ) ) {
                        mlogger.log( Level.SEVERE,"CFP parse failed - this CFP will do nothing" );
                        return;
                  }

                  // output its values
                  mlogger.log( Level.INFO, "NegHandler:  Bidding threshold = "+cfpXML.getBidThreshold()+" for neg "+strNegConvID );

                  for( i=0;i<cfpXML.getSize();i++ ) {
                        // (prop name, weight, min, max, constraint)
                        vector = cfpXML.getProperty(i);
                        if( vector.size() != 5 ) {
                              mlogger.log( Level.INFO,"NegHandler:  cfp property vector size "+vector.size()+" != 5");
                              return;
                        }
                        mlogger.log( Level.INFO,"NegHandler:  \tcfp prop = "+((String)vector.elementAt(0)) );
                        mlogger.log( Level.INFO,"NegHandler:  \tw = "+((String)vector.elementAt(1))+
                                     ", min = "+((String)vector.elementAt(2))+
                                     ", max = "+((String)vector.elementAt(3))+
                                     ", c = "+((String)vector.elementAt(4)) );
                  } // next prop

                  // keep last CFP in the neg state database
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strNegConvID, mstaticObjectLastCall, strCFP );
                  mstateRepository.save();
                  mstateRepository.unlock();

                  // TODO launch a thread to start the proposal forumation steps. This will avoid blocking on the auctionPropose step while a bid is made

                  // all done
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionCFP",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionCFP failed",ex );
            }
      }

      /**
       * auctionPropose propose a bid, based on a temporary reservation with the QoS module/scheduler,
       * that has a value greater than the min bid threshold value in the last cfp.
       * when asking for a new reservation from the QoS management module the last reservation will be released. It is
       * therefore possible that if no new reservation can be made, the old reservation will also no longer be available.
       * If this happens thios method will return a no bid, and the ast bid will become invalid. The client will find this
       * out when the auctionRequest method is called and the service provider fails to accept the last bid.
       * @param strNegConvID negotiation conversation ID
       * @return auction propose message, with body of the WSLA proposal
       */
      public String auctionPropose( String strNegConvID ) throws RemoteException
      {
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            String strMessage, strProposal, strXML, strTemplate, strRequestDesc, strLastBid;
            CallForProposalsXML call;

            try {
                  // need a qos handler for all this
                  if( mqoshandler != null ) {
                        // get last cfp from the neg state database
                        mstateRepository.lock();
                        mstateRepository.load();
                        strXML = (String) mstateRepository.getObject( strNegConvID,mstaticObjectLastCall );
                        call = new CallForProposalsXML();
                        if( !call.parseCFP( strXML ) ) {
                              mstateRepository.unlock();
                              throw new Exception("CFP parse failed" );
                        }

                        // get template from the state database
                        strTemplate = (String) mstateRepository.getObject( strNegConvID, mstaticObjectWSLATemplate );
                        if( strTemplate == null ) {
                              mstateRepository.unlock();
                              throw new Exception("null WSLA template");
                        }

                        // get the request desc
                        strRequestDesc = (String) mstateRepository.getObject( strNegConvID, mstaticObjectRequestDesc );
                        if( strRequestDesc == null ) {
                              mstateRepository.unlock();
                              throw new Exception("null request desc");
                        }

                        // create a new proposal based on CFP that comes in with a value > bid threshold
                        // empty string means "no bid"
                        mlogger.log( Level.INFO,"NegHandler: Proposing for "+strNegConvID);

                        // get the last successful bid
                        strLastBid = (String) mstateRepository.getObject( strNegConvID, mstaticObjectLastBid );

                        // cancel it (if we have a last bid)
                        if( strLastBid != null ) {
                              mqoshandler.cancelQoSDescriptor( strLastBid );
                              mstateRepository.addObject( strNegConvID, mstaticObjectLastBid, "" );
                              mstateRepository.save();
                        }

                        // TODO move makeNewProposal code to a seperate thread, to be launched by auctionCFP. Done this way for initial proof of concept, which can be upgraded once working ok

                        // try to make a new proposal
                        strProposal = makeNewProposal( call, strTemplate, strRequestDesc );
                        if( !strProposal.equals("") ) {
                              // add last bid (that was not "") to the state repository
                              mstateRepository.addObject( strNegConvID, mstaticObjectLastBid, strProposal );
                              mstateRepository.save();
                              mlogger.log( Level.INFO,"NegHandler: New bid made ok" );
                        }
                        else {
                              // if we have a no bid then try to get the last successful bid again
                              // since it might still be ok. Always a chance we are unlucky and someone gets into the
                              // space we just released (!)
                              if( strLastBid != null ) {
                                    strProposal = mqoshandler.requestQoSDescriptor( strLastBid,strRequestDesc );
                                    if( strProposal == null ) {
                                          // bad luck - someone has grabbed our reservation :(
                                          // client will think its ok but auction request will fail since we no longer have
                                          // the reservation available
                                          mlogger.log( Level.INFO,"NegHandler: Old reservation lost while trying to make a new proposal [acceptProposal will now fail since reservation lost]" );

                                          mstateRepository.addObject( strNegConvID, mstaticObjectLastBid, "" );
                                          mstateRepository.save();
                                          strProposal = "";
                                    }
                                    else {
                                          mstateRepository.addObject( strNegConvID, mstaticObjectLastBid, strProposal );
                                          mstateRepository.save();
                                          mlogger.log( Level.INFO,"NegHandler: Last good bid resurrected and is now ok again" );
                                    }
                              }
                              else {
                                    strProposal = "";
                                    mlogger.log( Level.INFO,"NegHandler: No last good bid - no bid will result" );
                              }

                              // return the bid. If this bid does not in fact meet the threshold criteria, then the
                              // client will remove this service provider from the auction bidding rounds. This bid may
                              // still get accepted, if the other better bidding service providers do not actually follow through
                              // exchange contracts on thier better bids.
                        }

                        // unlock repository (does not matter if we saved or not)
                        mstateRepository.unlock();
                  } // qoshandler null check
                  else {
                        mlogger.log( Level.WARNING,"QoS null, proposing a no bid" );
                        strProposal = ""; // no bid is default for non qos enabled providers
                  }

                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();

                  // TODO get DN's from somewhere
                  // make a FIPA message for the return
                  header = new AuctionMessageHeader( strNegConvID,
                                                     "XML",
                                                     mstrOntologyURI,
                                                     "ServiceProvider-DN",
                                                     "Client-DN" );
                  body = new AuctionMessageBody( strProposal );
                  message = messageHandler.formatProposeMessage( header, body );

                  // all done
                  return message.toString();
            }
            catch( RemoteException rex ) {
                  mlogger.log( Level.SEVERE,"RemoteException whilst doing auctionPropose",rex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw rex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionPropose",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionPropose failed",ex );
            }
      }

      /**
       * auctionAcceptProposal record the plain text WSLA that the client accepts. The next message should be a
       * request to exchange contracts, which will be compared to this accepted WSLA. There is no return since a bid made
       * by a service provider is assumed to be acceptable to that service provider. Nothing is agreed until the request
       * message is successfully processed however.
       * @param strNegConvID negotiation conversation ID
       * @param strMessage auction accept proposal message
       */
      public void auctionAcceptProposal( String strNegConvID, String strMessage ) throws RemoteException
      {
            String strWSLA, strLastBid;
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;

            try {
                  // extract content from FIPA message
                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();
                  message = new AuctionMessage( strMessage );

                  // check type
                  if( messageHandler.parseMessageType( message ) != AuctionMessageHandler.mstaticAcceptProposalType ) {
                        mlogger.log( Level.INFO,"NegHandler:  Invalid message type - not accept proposal");
                        return;
                  }

                  // get header
                  header = messageHandler.parseMessageHeader( message );

                  // parse body
                  body = messageHandler.parseMessageBody( message );
                  strWSLA = body.toString();

                  // get the last successful bid
                  mstateRepository.lock();
                  mstateRepository.load();
                  strLastBid = (String) mstateRepository.getObject( strNegConvID, mstaticObjectLastBid );
				  mstateRepository.unlock();

				  // check it matches EXACTLY the original proposal
                  if( !strLastBid.equals( strWSLA ) ) {
                        mlogger.log( Level.WARNING,"Invalid WSLA (accept proposal) - accept WSLA not same as previously proposed WSLA" );
                        return;
                  }

                  mlogger.log( Level.INFO,"NegHandler: Proposal accepted - conversation "+strNegConvID );

                  // all done
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionAcceptProposal",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionAcceptProposal failed",ex );
            }
      }

      /**
       * auctionRejectProposal will tell QoSManagement module to release the last WSLA reservation and will release
       * any state associated with this neg conversation. The conversation is dead as far as the service provider is
       * concerned as soon as it received this message.
       * @param strNegConvID negotiation conversation ID
       * @param strMessage auction accept proposal message
       */
      public void auctionRejectProposal( String strNegConvID, String strMessage ) throws RemoteException
      {
            String strWSLA, strLastBid;
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;

            try {
                  // extract content from FIPA message
                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();
                  message = new AuctionMessage( strMessage );

                  // check type
                  if( messageHandler.parseMessageType( message ) != AuctionMessageHandler.mstaticRejectProposalType ) {
                        mlogger.log( Level.INFO,"NegHandler:  Invalid message type - not reject proposal");
                        return;
                  }

                  // get header
                  header = messageHandler.parseMessageHeader( message );

                  // parse body (will ignore)
                  body = messageHandler.parseMessageBody( message );
                  strWSLA = body.toString();

                  // get the last successful bid
                  mstateRepository.lock();
                  mstateRepository.load();
                  strLastBid = (String) mstateRepository.getObject( strNegConvID, mstaticObjectLastBid );
                  mstateRepository.unlock();

                  // cancel temp reservation
                  if( mqoshandler != null && strLastBid != null ) mqoshandler.cancelQoSDescriptor( strLastBid );

                  mlogger.log( Level.INFO,"NegHandler: Proposal rejected in conversation "+strNegConvID );

                  // all done
                  return;
            }
            catch( RemoteException rex ) {
                  mlogger.log( Level.SEVERE,"RemoteException whilst doing auctionRejectProposal",rex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw rex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionRejectProposal",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionRejectProposal failed",ex );
            }
      }

      /**
       * auctionRequest takes a input a signed contract by the client and returns a signed contract by the
       * service provider. The contract contents can be checked with the accepted plain text contract sent in an earlier
       * transaction.
       * @param strNegConvID negotiation conversation ID
       * @param strMessage auction request message, with body of a signed contract
       * @return auction inform message, with body of a signed contract
       */
      public String auctionRequest( String strNegConvID, String strMessage ) throws RemoteException
      {
            //DJM DEBUG
			mlogger.log(Level.INFO,"Reached auctionRequest method");
			//DJM DEBUG
			String strClientSignedWSLA, strServiceProviderSignedWSLA, strWSLA, strLastBid;
            AuctionMessageHandler messageHandler;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;

            try {
                  // extract content from FIPA message
                  // get handler
                  messageHandler = new AuctionMessageHandlerFIPAImpl();
                  message = new AuctionMessage( strMessage );

                  // check type
                  if( messageHandler.parseMessageType( message ) != AuctionMessageHandler.mstaticRequestType ) {
                        throw new Exception("invalid message type - not request");
                  }

                  // get header
                  header = messageHandler.parseMessageHeader( message );

                  // parse body
                  body = messageHandler.parseMessageBody( message );
                  strClientSignedWSLA = body.toString();

                  // save client signed WSLA to state repositiry
                  // TODO send to auditable database
                  mstateRepository.lock();
                  mstateRepository.load();
                  mstateRepository.addObject( strNegConvID, mstaticObjectSignedContract, strClientSignedWSLA );
                  mstateRepository.save();

                  // get proposal from the server side state database
                  strLastBid = (String) mstateRepository.getObject( strNegConvID, mstaticObjectLastBid );
                  mstateRepository.unlock();

                  // TODO unsign contract using clients public key
                  strWSLA = strClientSignedWSLA;

                  // compare signed WSLA with the WSLA in the database (should be identical) using a string compare
                  if( !strLastBid.equals( strWSLA ) ) {
                        throw new Exception("Invalid WSLA (request) - signed request WSLA not same as previously proposed WSLA");
                  }

                  // confirm WSLA (throws a remote exception if cannot confirm)
                  if( mqoshandler != null ) mqoshandler.confirmQoSDescriptor( strLastBid );

                  // TODO sign plain text WSLA with private key
                  strServiceProviderSignedWSLA = strClientSignedWSLA;

                  // make a FIPA message for the return
                  header = new AuctionMessageHeader( strNegConvID,
                                                     "XML",
                                                     mstrOntologyURI,
                                                     "ServiceProvider-DN",
                                                     "Client-DN" );
                  body = new AuctionMessageBody( strServiceProviderSignedWSLA );
                  message = messageHandler.formatInformMessage( header, body );
                  mlogger.log( Level.INFO,"NegHandler:  Contracts exchanged - conversation "+strNegConvID);

                  // all done
                  return message.toString();
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to auctionAcceptProposal",ex);
				  if( mstateRepository!= null ) mstateRepository.unlock();
                  throw new RemoteException( "auctionAcceptProposal failed",ex );
            }
      }

      /**
       * load a config file and setup the config variables. Will also setup price model and QoS evaluator objects.
       * @param strConfig config filename
       */
      private void loadConfig( String strConfig ) throws RemoteException
      {
            Properties props;
            Vector vector;
            String strValue, strEvaluatorConfigFilename, strWSLAHandlerConfigFilename, strWSLATemplateFilename, strStateRepository;
            int i;

            try {
                  // check param
                  if( strConfig == null ) throw new IOException("null config filename");

                  // read the configuration options from the config file
                  props = new Properties();
                  props.load( new FileInputStream( strConfig ) );

                  // read state repository from file (if it exists)
                  strStateRepository = props.getProperty( mstaticConfigStateRepository );
                  mstateRepository = new StateRepository( strStateRepository );

                  // read config properties
                  mstrOntologyURI = props.getProperty( mstaticConfigOntologyURI );
                  mstrPropStartTime = props.getProperty( mstaticConfigPropStartTime );
                  mstrPropEndTime = props.getProperty( mstaticConfigPropEndTime );
                  mstrPropPrice = props.getProperty( mstaticConfigPropPrice );
                  strValue = props.getProperty( mstaticConfigContractValidityOffset );
                  mnContractValidityOffset = Long.parseLong( strValue );
                  strValue = props.getProperty( mstaticConfigMaxWSLAAdjustments );
                  mnMaxWSLAAdjustments = Integer.parseInt( strValue );

                  // TODO add one template per service and remove global in favour of a state entry
                  mstrWSLATemplateFilename = props.getProperty( mstaticConfigWSLATemplate );

                  strWSLAHandlerConfigFilename = props.getProperty( mstaticConfigWSLAHandler );
                  strEvaluatorConfigFilename = props.getProperty( mstaticConfigEvaluator );

                  // make handlers
                  mwslaHandler = new WSLAQoSHandlerImpl( strWSLAHandlerConfigFilename );
                  mPriceModel = new uk.ac.soton.itinnovation.gemss.business.DynamicPriceModel( strWSLAHandlerConfigFilename, mstrPropStartTime, mstrPropEndTime, mstrPropPrice );
                  mQoSEvaluator = new uk.ac.soton.itinnovation.gemss.negotiation.evaluator.QoSEvaluatorDotProduct( mwslaHandler, mPriceModel, strEvaluatorConfigFilename );
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to load config",ex);
                  throw new RemoteException( "loadConfig failed",ex );
            }
      }

      /**
       * make a new proposal and return a set of values that can be used to make a new WSLA. This will involve using the CFP to make
       * an initial attempt at a WSLA, then iteratively calling the QoSManagement module to actually make the resevation. There will
       * only be a single "active" reservation at any one time.
       * @param cfpXML call for proposals, which identified the limit values and property names
       * @param strWSLATemplate WSLA template used to get the hash of property values
       * @return WSLA string containing the new proposal, or an empty string for no bid
       */
      private String makeNewProposal( CallForProposalsXML cfpXML, String strWSLATemplate, String strRequestDesc ) throws Exception
      {
            int i, nCount;
            long nStart, nEnd, nOffset;
            double nThreshold, nValue, nPrice;
            boolean bOk;
            Vector vectorProp, vectorValidity;
            Hashtable hashValues;
            Enumeration enum;
            String strNewWSLA, strStartTime, strEndTime, strInterval, strInitialWSLA, strLastWSLA;
            SimpleDateFormat dateFormat;
            Calendar calendar;
            Date date2000GMT;

            try {
                  // check params
                  if( cfpXML == null || strWSLATemplate == null ) throw new Exception("null params");

                  // we must have a qos handler for this
                  if( mqoshandler == null ) {
                        mlogger.log( Level.INFO,"NegHandler: qoshandler is null so returning no bid");
                        return "";
                  }

                  // get threshold for this round of bidding
                  nThreshold = cfpXML.getBidThreshold();

                  // get a starting set of values using the QoS evaluator, which can be used as a first draft sent to the
                  // QoS Management module
                  strInitialWSLA = mQoSEvaluator.satisfyThreshold( strWSLATemplate, cfpXML, nThreshold );
                  if( strInitialWSLA == null ) {
                        mlogger.log( Level.INFO,"NegHandler: Unable to attain call's bid threshold by changing flexible properties - no bid");
                        return "";
                  }

                  // fill in WSLA validity fields (in the obligations section)
                  // parse WSLA, hash [prop name,(unit, type, value)]
                  hashValues = mwslaHandler.parseSLAParameters( strInitialWSLA );
                  if( hashValues == null ) throw new Exception("WSLA parse failed");

                  // get start time (job start time)
                  vectorProp = (Vector) hashValues.get( mstrPropStartTime );
                  if( vectorProp == null ) throw new Exception("Start time not found in new WSLA");
                  strStartTime = (String) vectorProp.elementAt(2);

                  // get end time (job end time)
                  vectorProp = (Vector) hashValues.get( mstrPropEndTime );
                  if( vectorProp == null ) throw new Exception("End time not found in new WSLA");
                  strEndTime = (String) vectorProp.elementAt(2);

                  // get millisecond offset values for these times
                  nStart = Long.parseLong( strStartTime );
                  nEnd = Long.parseLong( strEndTime );

                  // add the end offset to the end date to make the contract validity end date
                  nEnd += mnContractValidityOffset;

                  // format dates for WSLA validity fields
                  // Need XML type xsd:dateTime which is yyyy-mm-ddThh:mm:ss e.g. 2001-10-26T21:32:52

                  // get date 2000.1.1 GMT and it's offset
                  calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
                  calendar.set( 2000,0,1,0,0,0 );
                  date2000GMT = calendar.getTime();
                  nOffset = date2000GMT.getTime();

                  // make dates in the xsd:dateTime format required by the WSLA specification for PeriodType|Start and PeriodType|End
                  dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" );
                  strStartTime = dateFormat.format( new Date( nStart + nOffset ) );
                  strEndTime = dateFormat.format( new Date( nEnd + nOffset ) );

                  // set validity dates
                  vectorValidity = new Vector();
                  vectorValidity.addElement( strStartTime );
                  vectorValidity.addElement( strEndTime );
                  strInitialWSLA = mwslaHandler.setValidity( strInitialWSLA, vectorValidity );
                  if( strInitialWSLA == null ) throw new Exception("Failed to set validity dates for WSLA");

                  // get (and set) a price for this new proposal by asking the business module
                  nPrice = mPriceModel.calculatePrice( strInitialWSLA );
                  hashValues = mwslaHandler.parseSLAParameters( strInitialWSLA ); // get existing values
                  strInitialWSLA = mwslaHandler.setSLAValues( strInitialWSLA, hashValues );
                  if( strInitialWSLA == null ) throw new Exception("Failed to set price for WSLA");

                  // TODO if reservation value no good increase flexible property values - or give up and return no bid
                  // value = QoSEvaluator.calculateProposalValue( strWSLA )
                  // if value ok return these values to client, else change values (???) and try again
/*
// TODO test code to be removed
String strProp, strValue;
long l,l2;
Date date1,date2;

for(i=0;i<cfpXML.getSize();i++) {
      // vector of strings (prop name, weight, min, max, constraint)
      vectorProp = cfpXML.getProperty(i);
      strProp = (String)vectorProp.elementAt(0);
      if( mstrPropEndTime.equals( strProp ) || mstrPropStartTime.equals( strProp ) ) {
            // min
            strValue = (String)vectorProp.elementAt(2);
            l = Long.parseLong( strValue );
            date1 = new Date( nOffset + l );
            // max
            strValue = (String)vectorProp.elementAt(3);
            l2 = Long.parseLong( strValue );
            date2 = new Date( nOffset + l2 );

            mlogger.log( Level.INFO,"NegHandler: " +
                         "pre qos cfp min "+date1 + " ["+l+"]"+
                         " max "+date2 +  " ["+l2+"]"+
                         " prop "+ strProp );
      }
      else {
            mlogger.log( Level.INFO,"NegHandler: " +
                         "pre qos cfp min "+((String)vectorProp.elementAt(2)) +
                         " max "+((String)vectorProp.elementAt(3)) +
                         " prop "+ strProp );
      }

}
enum = hashValues.keys();
while( enum.hasMoreElements() ) {
      // hashtable containing (propery URI, vector). The vector contains (unit, type, value) strings.
      strProp = (String) enum.nextElement();
      vectorProp = (Vector) hashValues.get( strProp );
      if( mstrPropEndTime.equals( strProp ) || mstrPropStartTime.equals( strProp ) ) {
            // min
            strValue = (String)vectorProp.elementAt(2);
            l = Long.parseLong( strValue );
            date1 = new Date( nOffset + l );
            mlogger.log( Level.INFO,"NegHandler: " +
                         "pre qos wsla value "+date1 + " ["+l+"]"+
                         " prop "+strProp );
      }
      else {
            mlogger.log( Level.INFO,"NegHandler: " +
                         "pre qos wsla value "+((String)vectorProp.elementAt(2)) +
                         " prop "+strProp );
      }
}
// end of test code
*/

                  // loop until we get a reservation (or give up) from QoSManagement module
                  bOk = false;
                  strNewWSLA = strInitialWSLA;
                  nCount = 0;
                  while( !bOk ) {
                        // ask for a reservation
                        strNewWSLA = mqoshandler.requestQoSDescriptor( strNewWSLA,strRequestDesc );
                        if( strNewWSLA == null ) {
                              mlogger.log( Level.INFO,"QoSManagement returned null after request - no bid possible");
                              return "";
                        }

                        // parse WSLA
                        hashValues = mwslaHandler.parseSLAParameters( strNewWSLA );
                        if( hashValues == null ) throw new Exception("WSLA parse failed");

                        // get new reservations value
                        nValue = mQoSEvaluator.calculateScore( hashValues, cfpXML );
                        if( nValue >= nThreshold ) {
                              // ok - we have a winner :)
                             bOk = true;
                              mlogger.log( Level.INFO,"NegHandler: Winner with value "+nValue );
                        }
                        else {
                              // info
                              mlogger.log( Level.INFO,"NegHandler: QoSHandler's WSLA score "+nValue+" is < threshold "+nThreshold+" - cancelling and adjusting values for a retry");
/*
// TODO test code to be removed
//String strProp, strValue;
//long l;
//Date date1,date2;

for(i=0;i<cfpXML.getSize();i++) {
      // vector of strings (prop name, weight, min, max, constraint)
      vectorProp = cfpXML.getProperty(i);
      strProp = (String)vectorProp.elementAt(0);
      if( mstrPropEndTime.equals( strProp ) || mstrPropStartTime.equals( strProp ) ) {
            // min
            strValue = (String)vectorProp.elementAt(2);
            l = Long.parseLong( strValue );
            date1 = new Date( nOffset + l );
            // max
            strValue = (String)vectorProp.elementAt(3);
            l2 = Long.parseLong( strValue );
            date2 = new Date( nOffset + l2 );

            mlogger.log( Level.INFO,"NegHandler: " +
                         "post qos cfp min "+date1 +  " ["+l+"]"+
                         " max "+date2 +  " ["+2+"]"+
                         " prop "+ strProp );
      }
      else {
            mlogger.log( Level.INFO,"NegHandler: " +
                         "post qos cfp min "+((String)vectorProp.elementAt(2)) +
                         " max "+((String)vectorProp.elementAt(3)) +
                         " prop "+ strProp );
      }

}
enum = hashValues.keys();
while( enum.hasMoreElements() ) {
      // hashtable containing (propery URI, vector). The vector contains (unit, type, value) strings.
      strProp = (String) enum.nextElement();
      vectorProp = (Vector) hashValues.get( strProp );
      if( mstrPropEndTime.equals( strProp ) || mstrPropStartTime.equals( strProp ) ) {
            // min
            strValue = (String)vectorProp.elementAt(2);
            l = Long.parseLong( strValue );
            date1 = new Date( nOffset + l );
            mlogger.log( Level.INFO,"NegHandler: " +
                         "post qos wsla value "+date1 + " ["+l+"]"+
                         " prop "+strProp );
      }
      else {
            mlogger.log( Level.INFO,"NegHandler: " +
                         "post qos wsla value "+((String)vectorProp.elementAt(2)) +
                         " prop "+strProp );
      }
}
//mlogger.log( Level.INFO,"NegHandler: post qos WSLA = "+strNewWSLA );

// end of test code
*/
                              // reservation is not good enough, so cancel it
                              mqoshandler.cancelQoSDescriptor( strNewWSLA );

                              // adjust WSLA
                              strNewWSLA = adjustProposal( strNewWSLA, cfpXML );
                              nCount++; // increase the counter

                              // abort with a no bid if cannot adjust further, or timeout with the counter
                              if( strNewWSLA == null || strNewWSLA.equals("") || nCount >= mnMaxWSLAAdjustments ) {
                                    mlogger.log( Level.INFO,"NegHandler: Value adjustment gave up on iteration "+nCount );
                                    strNewWSLA = ""; // no bid
                                    bOk = true;
                              }
                        }
                  } // loop until we have a WSLA or we give up with a no bid

                  // return new proposal
                  return strNewWSLA;

            }
            catch( RemoteException rex ) {
                  mlogger.log( Level.SEVERE,"QoSHandler exception thrown:",rex);
                  throw rex; // pass remote exceptions on through back to the ApplicationExecuter
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to makeNewProposal",ex);
                  return ""; // no bid on error
            }
      }

      /**
       * adjust a WSLA so there can be another attempt at making a good QoS reservation.
       * @param strWSLA WSLA string that was last attempted (and is no good)
       * @param cfpXML call for proposals, which identified the limit values and property names
       * @return WSLA string containing the new proposal, or an empty string for no bid
       */
      private String adjustProposal( String strWSLA, CallForProposalsXML cfpXML ) throws Exception
      {
            // TODO write adjust code to modify a failed WSLA, within the criteria set by the CFP, and make a new proposal that might work a bit better
            mlogger.log( Level.INFO,"WSLA value adjustment not implemented yet (we will decide if we need it or not for phase I4)");
            return "";
      }

} // end of NegotiationHandlerImpl

