/////////////////////////////////////////////////////////////////////////
//
// © 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/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

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

import java.lang.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.*;
import java.io.*;

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.ProposalHistoryImpl;
import uk.ac.soton.itinnovation.gemss.negotiation.QoSRequirementsImpl;
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.negotiation.evaluator.QoSEvaluatorDotProduct;

import eu.gemss.components.negotiation.QoSNegotiation;
import eu.gemss.components.negotiation.ProposalHistory;
import eu.gemss.components.negotiation.QoSRequirements;
import eu.gemss.components.proxies.GEMSSProxy;
import eu.gemss.components.Terminable;
import eu.gemss.components.ComponentManager;
import eu.gemss.signals.negotiation.GetQoSParametersRequest;
import eu.gemss.signals.negotiation.AcceptContractRequest;
import eu.gemss.signals.Signal;
import eu.gemss.GEMSS;
import eu.gemss.GridException;

import eu.gemss.components.transport.NetworkException;
import eu.gemss.components.negotiation.NegotiationException;
import eu.gemss.components.negotiation.ParticipantsNotReadyException;
import eu.gemss.components.negotiation.NoQoSReqsAvailableException;
import eu.gemss.components.negotiation.NoProposalsAvailableException;
import eu.gemss.components.negotiation.NegotiationShutdownException;
import eu.gemss.components.negotiation.FailedToStartAuctionException;
import eu.gemss.components.negotiation.NoCFPsSentException;
import eu.gemss.components.negotiation.NoProposalsAcceptedException;
import eu.gemss.components.negotiation.AuctionTimeoutException;

/**
 * This class is an implementation for a GEMSS QoSNegotiation component. This component is a client-side
 * component to drive a quality of service auction with several service providers. There must be a server side
 * module to respond to the invocations this module will generate, using the transport component.
 */
public class QoSNegotiationImpl implements Terminable, QoSNegotiation {

      // mamber variables
      private int mnMaxAuctionRounds;
      private long mnRoundDuration;
      private long mnAuctionMaxDuration;
      private long mnInvocationTimeout;
      private double mnBidThresholdIncrease; // fractional multiplier
      private double mnBidThresholdStart;
      private String mstrOntologyURI;
      private String mstrOntologyAlternativeURI;
      private String mstrOntologyPropLabel;
      private String mstrOntologyUnitLabel;
      private String mstrOntologyConstraint;
      private String mstrOntologyConstraintMax;
      private String mstrOntologyConstraintMin;
      private String mstrOntologyConstraintEqual;
      private boolean mbStop;
      private Hashtable mhashTypeMappings; // (String[URI],Integer[type])
      private WSLAQoSHandler mwslaHandler;
      private AuctionMessageHandler mauctionHandler;
      private OWLIndividualHandler mowlHandler;
      private QoSEvaluator mevaluator;

      // config file property names
      private static final String mstaticConfigMaxRounds = "MaxRounds";
      private static final String mstaticConfigRoundDuration = "RoundDuration";
      private static final String mstaticConfigAuctionMaxDuration = "AuctionMaxDuration";
      private static final String mstaticConfigInvocationTimeout = "InvocationTimeout";
      private static final String mstaticConfigBidThresholdIncrease = "BidThresholdIncrease";
      private static final String mstaticConfigBidThresholdStart = "BidThresholdStart";
      private static final String mstaticConfigOntologyURI = "OntologyURI";
      private static final String mstaticConfigOntologyAlternativeURI = "OntologyAlternativeURI";
      private static final String mstaticConfigOntologyPropLabel = "OntologyPropLabel";
      private static final String mstaticConfigOntologyUnitLabel = "OntologyUnitLabel";
      private static final String mstaticConfigOntologyConstraint = "OntologyConstraint";
      private static final String mstaticConfigOntologyConstraintMax = "OntologyConstraintMax";
      private static final String mstaticConfigOntologyConstraintMin = "OntologyConstraintMin";
      private static final String mstaticConfigOntologyConstraintEqual = "OntologyConstraintEqual";
      private static final String mstaticConfigWSLAHandlerConfigFile = "WSLAHandlerConfigFile";
      private static final String mstaticConfigEvaluatorConfigFile = "EvaluatorConfigFile";
      private static final String mstaticConfigTypeMappingLong = "TypeMappingLong";
      private static final String mstaticConfigTypeMappingDouble = "TypeMappingDouble";
      private static final String mstaticConfigTypeMappingTimeOffset = "TypeMappingTimeOffset";
      private static final String mstaticConfigTypeMappingBoolean = "TypeMappingBoolean";

      // constants
      private static String mstaticLanguage = "XML"; // format of auction message body
      private static int mstaticInvocationCheckPeriod = 100; // check invocation thead every 1/10 second

      // logging
      private static Logger mlogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.negotiation.qosnegotiationimpl");

      /**
       * default constructor, does nothing (call init to initialize component)
       */
      public QoSNegotiationImpl()
      {
            // default values
            mnMaxAuctionRounds = -1;
            mnRoundDuration = -1;
            mnAuctionMaxDuration = -1;
            mnInvocationTimeout = -1;
            mnBidThresholdIncrease = -1;
            mnBidThresholdStart = -1;
            mstrOntologyURI = null;
            mstrOntologyAlternativeURI = null;
            mstrOntologyPropLabel = null;
            mstrOntologyUnitLabel = null;
            mstrOntologyConstraint = null;
            mstrOntologyConstraintMax = null;
            mstrOntologyConstraintMin = null;
            mstrOntologyConstraintEqual = null;
            mhashTypeMappings = new Hashtable();
            mwslaHandler = null;
            mauctionHandler = null;
            mowlHandler = null;
      }

      /**
       * Init this component using a config file
       * @param strConfigFile config filename with init parameters
       */
      public void init( String strConfigFile ) throws Exception
      {
            Properties props;
            String strMapping;
            String strWSLAHandlerConfigFile;
            String strOntologyAlternativeURI;
            String strEvaluatorConfig;

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

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

                  // get config vars
                  mnMaxAuctionRounds = Integer.parseInt( props.getProperty( mstaticConfigMaxRounds ) );
                  mnRoundDuration = Long.parseLong( props.getProperty( mstaticConfigRoundDuration ) );
                  mnAuctionMaxDuration = Long.parseLong( props.getProperty( mstaticConfigAuctionMaxDuration ) );
                  mnInvocationTimeout = Long.parseLong( props.getProperty( mstaticConfigInvocationTimeout ) );
                  mnBidThresholdIncrease = Double.parseDouble( props.getProperty( mstaticConfigBidThresholdIncrease ) );
                  mnBidThresholdStart = Double.parseDouble( props.getProperty( mstaticConfigBidThresholdStart ) );
                  mstrOntologyURI = props.getProperty( mstaticConfigOntologyURI );
                  mstrOntologyPropLabel = props.getProperty( mstaticConfigOntologyPropLabel );
                  mstrOntologyUnitLabel = props.getProperty( mstaticConfigOntologyUnitLabel );
                  mstrOntologyConstraint = props.getProperty( mstaticConfigOntologyConstraint );
                  mstrOntologyConstraintMax = props.getProperty( mstaticConfigOntologyConstraintMax );
                  mstrOntologyConstraintMin = props.getProperty( mstaticConfigOntologyConstraintMin );
                  mstrOntologyConstraintEqual = props.getProperty( mstaticConfigOntologyConstraintEqual );
                  strEvaluatorConfig = props.getProperty( mstaticConfigEvaluatorConfigFile );

                  strWSLAHandlerConfigFile = props.getProperty( mstaticConfigWSLAHandlerConfigFile );
                  strOntologyAlternativeURI = props.getProperty( mstaticConfigOntologyAlternativeURI,"" ); // default is empty string

                  // read all available mappings (if any)
                  // long
                  strMapping = props.getProperty( mstaticConfigTypeMappingLong,"" );
                  if( strMapping.equals("") ) mlogger.log( Level.INFO,"No long type mapping in config file (ok if type not used in WSLA)");
                  else mhashTypeMappings.put( strMapping,new Integer( GetQoSParametersRequest.mstaticLongUnitFlag ) );
                  // double
                  strMapping = props.getProperty( mstaticConfigTypeMappingDouble,"" );
                  if( strMapping.equals("") ) mlogger.log( Level.INFO,"No double type mapping in config file (ok if type not used in WSLA)");
                  else mhashTypeMappings.put( strMapping,new Integer( GetQoSParametersRequest.mstaticDoubleUnitFlag ) );
                  // boolean
                  strMapping = props.getProperty( mstaticConfigTypeMappingBoolean,"" );
                  if( strMapping.equals("") ) mlogger.log( Level.INFO,"No boolean type mapping in config file (ok if type not used in WSLA)");
                  else mhashTypeMappings.put( strMapping,new Integer( GetQoSParametersRequest.mstaticBooleanUnitFlag ) );
                  // time offset (long)
                  strMapping = props.getProperty( mstaticConfigTypeMappingTimeOffset,"" );
                  if( strMapping.equals("") ) mlogger.log( Level.INFO,"No time offset type mapping in config file (ok if type not used in WSLA)");
                  else mhashTypeMappings.put( strMapping,new Integer( GetQoSParametersRequest.mstaticGMT2000DateOffsetUnitFlag ) );

                  // make handlers
                  mwslaHandler = new WSLAQoSHandlerImpl( strWSLAHandlerConfigFile );
                  mauctionHandler = new AuctionMessageHandlerFIPAImpl();
                  mowlHandler = new QoSOntologyImpl();
                  if( strOntologyAlternativeURI.equals("") ) {
                        mowlHandler.loadOntology( mstrOntologyURI );
                  }
                  else {
                        mowlHandler.loadOntology( mstrOntologyURI,strOntologyAlternativeURI );
                  }
                  mevaluator = new QoSEvaluatorDotProduct( mwslaHandler, null, strEvaluatorConfig );

                  // all done
                  return;
            }
            catch( IOException ioex ) {
                  mlogger.log( Level.SEVERE,"Failed to read config file",ioex);
                  throw new Exception("init failed loading config file");
            }
            catch( NumberFormatException nex ) {
                  mlogger.log( Level.SEVERE,"Failed to parse config file",nex);
                  throw new Exception("init failed to parse config file");
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to init",ex);
                  throw new Exception("init failed");
            }
      }

      /**
       * terminate method - does nothing since this component has no state
       */
      synchronized public void terminate()
      {
            // register stop request
            mbStop = true;
      }

      /**
       * Informs a set of service providers (represented by GEMSSProxy objects) that an auction is starting. The
       * service providers will respond by getting ready to receive a call for proposal. Explicitly this method
       * calls the proxy functions getNegID, getWSLA, uploadRequestDesc and finally auctionStart.
       * Throws a ParticipantsNotReadyException if invocations to the service providers fail
       * for some reason.
       * @param strRequestDescFilename filename to the request desc file for the job under negotiation
       * @param vectorProxies vector of GEMSSProxy objects with which to negotiate
       * @return Proposal history, which will initially contain only the particiant list since no bids have been made yet.
       */
      public ProposalHistory startAuction( String strRequestDescFilename, Vector vectorProxies ) throws GridException
      {
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            ParticipantsNotReadyException pex;
            GEMSSProxy proxy;
            NegotiationTimeoutThread thread;
            Vector vectorNotReady;
            ProposalHistory history;
            int i;
            long nStartTime;
            String strAuctionDetails, strID, strWSLA;
            Date date2000GMT;
            GregorianCalendar calendar;

            try {
                  // check params
                  if( vectorProxies == null || strRequestDescFilename == null ) throw new FailedToStartAuctionException("The participant list is null, or the request desc filename is null. This is an infrastructure problem - please check logs for details.","null params");

                  // make vector to record participants that are not ready (if any)
                  // and message handler
                  vectorNotReady = new Vector();

                  // make a new proposal history
                  history = new ProposalHistoryImpl();
                  history.setBiddingThreshold( -1 ); // initial "no value" value

                  // make the auction details, based on the current time offset from 2000.1.1 GMT
                  calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
                  calendar.set( 2000,0,1,0,0,0 );
                  date2000GMT = calendar.getTime();
                  nStartTime = System.currentTimeMillis() - date2000GMT.getTime();

                  // set overall auction deadline if there is one
                  if( mnAuctionMaxDuration != -1 ) history.setAuctionDeadline( nStartTime + mnAuctionMaxDuration );
                  else history.setAuctionDeadline( -1 );

                  // make XML aucton details
                  strAuctionDetails = formatAuctionDetails( mnMaxAuctionRounds, nStartTime, mnAuctionMaxDuration );
                  if( strAuctionDetails == null ) throw new FailedToStartAuctionException("Failed to format auction details as an XML message. This is an infrastructure error - please check logs for details.","Failed to make XML auction details");

                  // loop on all GEMSSProxies
                  for( i=0;i<vectorProxies.size();i++ ) {
                        // get proxy
                        proxy = (GEMSSProxy) vectorProxies.elementAt(i);
                        if( proxy == null ) throw new FailedToStartAuctionException("The participant list contained a null proxy. This is an infrastructure error - please check logs for details.","null proxy instance in proxy array");

                        // add to list of participants
                        history.addParty( proxy );

                        // do the service provider auction prep steps
                        try {
                              // get Neg ID
                              thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticGetNegIDFlag, mnInvocationTimeout ); // getNegID
                              thread.start();
                              while( thread.isWaiting() ) {
                                    // wait a short time
                                    try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                    // check for stop flag (set by terminate method)
                                    if( mbStop ) {
                                          thread.flagStop( "QoS negotiation component termination" );
                                          throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                    }
                              }

                              // re-throw any exceptions if thread failed
                              if( thread.getException() != null ) {
                                    throw thread.getException();
                              }

                              // check neg ID is ok
                              if( thread.getNegID() == null ) throw new FailedToStartAuctionException("The server returned a null negotiation ID. Please check you are authorized to request negotiation conversations with your account conversation ID.","null neg conv ID");

                              // get the WSLA object using the timeout thread and wait until
                              // finished or a time out / error occurs
                              thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticGetWSLAFlag, mnInvocationTimeout ); // getWSLA
                              thread.start();
                              while( thread.isWaiting() ) {
                                    // wait a short time
                                    try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                    // check for stop flag (set by terminate method)
                                    if( mbStop ) {
                                          thread.flagStop( "QoS negotiation component termination" );
                                          throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                    }
                              }

                              // re-throw any exceptions if thread failed
                              if( thread.getException() != null ) {
                                    throw thread.getException();
                              }

                              // add the WSLA object to the proposal history
                              strWSLA = thread.getResultWSLA();
                              if( strWSLA == null ) throw new FailedToStartAuctionException("The server returned a null WSLA template. Please check the server has an appropriate WSLA template for (and supports) your service.","null WSLA returned");

                              // validate the WSLA by parsing it and ignoring the results
                              if( mwslaHandler.parseSLAParameters( strWSLA ) == null ) throw new FailedToStartAuctionException("The server returned a WSLA which did not validate correctly. Please check the server has an appropriate WSLA template for (and supports) your service.","WSLA template validation error");

                              // add WSLA template to the history list
                              history.addWSLATemplate( proxy, strWSLA );

                              // upload the RequestDesc file using the timeout thread and wait until
                              // finished or a time out / error occurs
                              thread = new NegotiationTimeoutThread( proxy, strRequestDescFilename, mnInvocationTimeout ); // uploadRequestDesc
                              thread.start();
                              while( thread.isWaiting() ) {
                                    // wait a short time
                                    try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                    // check for stop flag (set by terminate method)
                                    if( mbStop ) {
                                          thread.flagStop( "QoS negotiation component termination" );
                                          throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                    }
                              }

                              // re-throw any exceptions if thread failed
                              if( thread.getException() != null ) {
                                    throw thread.getException();
                              }

                              // make a FIPA inform message for the start of the auction
                              // make header
                              // TODO get a distinguished name from somewhere
                              strID = proxy.queryNegConversationID();
                              header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
                                                                 getDistinguishedName( null ),
                                                                 getDistinguishedName( proxy ) );

                              // make body (XML data construct)
                              // include auction overall end time, max number of rounds
                              // any hints from the client about priorities
                              body = new AuctionMessageBody( strAuctionDetails );

                              // make message
                              message = mauctionHandler.formatInformMessage( header,body );

                              // invoke auctionInform using the timeout thread and wait until
                              // finished or a time out / error occurs
                              thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticAuctionInformFlag, message, mnInvocationTimeout );
                              thread.start();
                              while( thread.isWaiting() ) {
                                    // wait a short time
                                    try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                    // check for stop flag (set by terminate method)
                                    if( mbStop ) {
                                          thread.flagStop( "QoS negotiation component termination" );
                                          throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                    }
                              }

                              // re-throw any exceptions if thread failed
                              if( thread.getException() != null ) {
                                    throw thread.getException();
                              }

                              // no result for startAuction so just continue to loop
                        }
                        catch( GridException gex ) {
                              // record those participants whom are not ready
                              vectorNotReady.addElement( proxy );

                              // log reason too
                              mlogger.log( Level.WARNING,"Service provider not ready to start auction",gex );
                        }
                        catch( InterruptedException iex ) {
                              // record those participants whom are not ready (timeout)
                              vectorNotReady.addElement( proxy );

                              // log reason too
                              mlogger.log( Level.WARNING,"Service provider timed-out during start auction",iex );
                        }
                  } // next proxy in array

                  // throw ParticipantsNotReadyException if some proxies not ready
                  if( vectorNotReady.size() > 0 ) {
                        throw new ParticipantsNotReadyException( "Not all service providers were ready to start the auction","Some GEMSSProxy instances threw GridException's when starting auction",vectorNotReady );
                  }

                  // useful logging
                  mlogger.log( Level.INFO,"Probe: startAuction\t"+mnMaxAuctionRounds+"\t"+nStartTime+"\t"+mnAuctionMaxDuration+"\t"+vectorProxies.size() );

                  // all done
                  return history;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during startAuction",ex);
                  throw new FailedToStartAuctionException( "QoS negotiation error starting auction - please check logs for details","startAuction failed - check logs" );
            }
      }

      /**
       * Get, by throwing a signal, a set of QoS requirements for this job. The requirements include QoS parameter
       * weights and minimum acceptable values.
       * @param strServiceName name of the service for which these requirements apply
       * @param strJobDescription job description meaningful to the signal handler (will be passed via signal to the handler)
       * @param history Proposal histroy which includes details of the participants in the auction
       * @return a set of QoS requirements (or null if no handlers responded to request for values) - hash of [prop name,(unit type, weight, min, max)]
       */
      public QoSRequirements getQoSRequirements( String strServiceName, String strJobDescription, ProposalHistory history ) throws GridException
      {
            QoSRequirements reqs;
            GetQoSParametersRequest request;
            ComponentManager compMan;
            Signal [] arraySignals;
            GEMSSProxy proxy;
            int i;
            Integer nUnitType;
            Hashtable hashProperties;
            Enumeration enum;
            String strWSLA, strPropertyName, strDesc, strType, strUnit, strConstraint;
            Vector vector, vectorProperty;

            try {
                  // check params
                  if( strServiceName == null || strJobDescription == null || history == null ) throw new Exception("null params");

                  // parse the set of WSLA objects, recording the set of QoS properties. It would be normal
                  // for all WSLA objects to hold the SAME set of QoS properties, but this code allows for service providers
                  // to support some but not all properties. If properties are unsupported yet weighted important to the
                  // user then the service provider not supporting these properties will never have their bids accepted.

                  // make request object to hold the set of all qos properties, WSLA handler and read ontology
                  // from either the namespace URI or an alternative URI (file probably)
                  request = new GetQoSParametersRequest( this );
                  request.setOverallDescription( strJobDescription );

                  // get the parties vector
                  vector = history.getParties();
                  if( vector == null ) throw new NoQoSReqsAvailableException("Proposal history contains a null participant vector. This is an infrastructure error - check logs for details","parties vector null");

                  // loop on parties WSLA's and add QoS properties to a list
                  for( i=0;i<vector.size();i++ ) {
                        // get the proxies WSLA template
                        proxy = (GEMSSProxy) vector.elementAt(i);
                        if( proxy == null ) throw new NoQoSReqsAvailableException("Proposal history contains a null participant. This is an infrastructure error - check logs for details","null proxy");
                        strWSLA = history.getWSLATemplate( proxy );
                        if( strWSLA == null ) throw new NoQoSReqsAvailableException("Proposal history contains a null WSLA template for one of the participants. This is an infrastructure error - check logs for details","null WSLA");

                        // extract the properties from this WSLA template
                        hashProperties = mwslaHandler.parseSLAParameters( strWSLA );
                        if( hashProperties == null ) throw new NoQoSReqsAvailableException("Failed to parse the WSLA template, and hence extract the QoS properties from it. This is probably an error with the server side WSLA template - check logs for details","WSLA parse error");

                        // loop on WSLA QoS properties
                        enum = hashProperties.keys();
                        while( enum.hasMoreElements() ) {
                              // get prop name
                              strPropertyName = (String) enum.nextElement();

                              // is prop already in request list?
                              if( request.getPropertyByName( strPropertyName ) == null ) {
                                    // get property vector (unit uri, type uri, value)
                                    vectorProperty = (Vector) hashProperties.get( strPropertyName );
                                    if( vectorProperty == null ) throw new NoQoSReqsAvailableException("QoS property list contained a null entry. This is probably an error with the server side WSLA template - check logs for details","null vector for property "+strPropertyName);

                                    // look up desc from ontology instance label property
                                    strDesc = mowlHandler.getPropertyValue( strPropertyName, mstrOntologyPropLabel );
                                    if( strDesc == null ) throw new NoQoSReqsAvailableException("QoS property not found within OWL ontology. The client side OWL ontology file is missing a property that appears within the server side WSLA template - check logs for details","null for ontology property label for "+strPropertyName);

                                    // get unit
                                    if( vectorProperty.elementAt(0) == null ) throw new NoQoSReqsAvailableException("QoS property unit not found for property. This is probably an error with the server side WSLA template - check logs for details","null unit URI for "+strPropertyName);
                                    strUnit = mowlHandler.getPropertyValue( (String) vectorProperty.elementAt(0), mstrOntologyUnitLabel );
                                    if( strUnit == null ) throw new Exception("null for ontology unit label for "+((String) vectorProperty.elementAt(0)));

                                    // match type URI with config file mapping
                                    strType = (String) vectorProperty.elementAt(1);
                                    nUnitType = (Integer) mhashTypeMappings.get( strType );
                                    if( nUnitType == null ) {
                                          mlogger.log( Level.WARNING, "WSLA type handler not present (will remove property) for type "+strType+", property "+strPropertyName);
                                    }
                                    else {
                                          // add property to request object with no values for weight, min or max
                                          request.addProperty( strPropertyName, strDesc, nUnitType, strUnit, "","","" );
                                    } // unit mapping null check
                              } // added already check
                        } // next WSLA property
                  } // next proxy & WSLA template


                  // probe
                  mlogger.log( Level.INFO,"Probe: getQoSRequirements" );

                  // get comp man
                  compMan = GEMSS.getInstance();
                  if( compMan == null ) throw new NoQoSReqsAvailableException("Failed to load the component manager. This is an infrastructure error, but might be caused by invalid config entries - check logs for details","null component manager");

                  // send signal and wait for response (blocking)
                  arraySignals = compMan.generateSignal( request );
                  if( arraySignals.length == 0 ) {
                        mlogger.log( Level.INFO, "No handlers responded to GetQoSRequirementRequest signal (abort might have been clicked in UI)" );
                        return null;
                  }

                  // check for termination of component
                  if( mbStop ) throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");

                  if( !(arraySignals[0] instanceof GetQoSParametersRequest) ) throw new NoQoSReqsAvailableException("The QoS requirements signal was processed by the wrong handler. The wrong signal handler has been registered by the app, or the signal handler is written incorrectly - check logs for details","Handler returned an incorrect signal response type - aborting");
                  if( arraySignals.length > 1 ) mlogger.log( Level.WARNING,"Multiple handlers responded to GetQoSRequirementRequest - first response used only, others are ignored");

                  // get first response
                  request = (GetQoSParametersRequest) arraySignals[0];
                  if( !request.wasProcessed() ) throw new NoQoSReqsAvailableException("No signal handlers processed the get QoS requirements signal. This could be because no signal handlers were registered by the application - check logs for details","GetQoSParameterRequest signal not processed - aborting");
                  if( request.getFailed() ) throw new NoQoSReqsAvailableException("A signal handler processed the QoS requirements signal, but reported a failure. This is because the user hit the abort button or an error occured during processing (check logs).","GetQoSParameterRequest signal handler processed signal but failed - aborting");

                  // make requirements
                  reqs = new QoSRequirementsImpl();
                  for( i=0;i<request.getSize();i++ ) {
                        // get value name from request object
                        // property vector (name, desc, unit type, unit desc, weight, min, max) all strings
                        vector = request.getPropertyByIndex( i );
                        if( vector == null ) throw new NoQoSReqsAvailableException("An invalid response was returned by the get QoS requirements signal handler. This is a signal handler error - check logs for details.","null vector returned by request");
                        if( vector.size() != 7 ) throw new NoQoSReqsAvailableException("An invalid response was returned by the get QoS requirements signal handler. This is a signal handler error - check logs for details.","vector returned does not have 7 elements");

                        // get property name
                        strPropertyName = (String) vector.elementAt(0);
                        if( strPropertyName == null ) throw new NoQoSReqsAvailableException("An invalid response was returned by the get QoS requirements signal handler. This is a signal handler error - check logs for details.","null property name");

                        // make a property vector for reqs object (unit type, weight, min, max, constraint)
                        vectorProperty = new Vector();
                        vectorProperty.addElement( vector.elementAt(2) ); // from now on we use unit types (not the unit URI) Integer
                        vectorProperty.addElement( vector.elementAt(4) ); // weight string
                        vectorProperty.addElement( vector.elementAt(5) ); // min string
                        vectorProperty.addElement( vector.elementAt(6) ); // max string

                        // get constraint for this property from OWL ontology
                        strConstraint = mowlHandler.getPropertyValue( strPropertyName, mstrOntologyConstraint );
                        if( strConstraint == null ) throw new NoQoSReqsAvailableException("A QoS property constraint value did not appear within the ontology. The client side OWL ontology might be out of date, or the server side WSLA template incorrect in some way.","null for ontology constraint");
                        vectorProperty.addElement( strConstraint ); // constraint string value = max | min | equal

                        // useful log info
                        mlogger.log( Level.INFO,"QoS property req added : name = "+strPropertyName+", weight = "+(String)vector.elementAt(4)+", min = "+(String)vector.elementAt(5)+", max = "+(String)vector.elementAt(6)+", constraint = "+strConstraint );

                        // add to the requirements object
                        reqs.addQoSProperty( strPropertyName, vectorProperty );

                        mlogger.log( Level.INFO,"Probe: qos-requirement-returned\t"+strPropertyName+"\t"+((Integer) vector.elementAt(2))+"\t"+((String) vector.elementAt(4))+"\t"+((String) vector.elementAt(5))+"\t"+((String) vector.elementAt(6)) );

                  } // next QoS property

                  // ** return requirements which now contains [name,(unit type, weight, min, max, constraint)] **
                  return reqs;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during getQoSRequirements",ex);
                  throw new NoQoSReqsAvailableException( "QoS negotiation error getting QoS requirements - please check logs for details","getQoSRequirements failed - check logs" );
            }
      }

      /**
       * Sends a call for proposals message to each of the participants in the auction.
       * @param requirements QoS requirements that will make up the detail of the cfp
       * @param history Proposal histroy which includes details of the participants in the auction
       * @return Updated proposal history containing the new cfp
       */
      public ProposalHistory sendCFPs( QoSRequirements requirements, ProposalHistory history ) throws GridException
      {
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            GEMSSProxy proxy;
            NegotiationTimeoutThread thread;
            Vector vectorParties;
            int i;
            double nMinBidValue;
            long nDeadline;
            String strWSLA, strCFP, strID;
            Date date2000GMT, date;
            GregorianCalendar calendar;

            try {
                  // check params
                  if( requirements == null || history == null ) throw new NoCFPsSentException("Invalid requirements or history object. This is an infrastructure error - please check logs for details.","null params");

                  // get parties
                  vectorParties = history.getParties();

                  // check for an overall auction timout
                  nDeadline = history.getAuctionDeadline();
                  if( nDeadline != -1 ) {
                        calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
                        calendar.set( 2000,0,1,0,0,0 );
                        date2000GMT = calendar.getTime();
                        if( nDeadline - (System.currentTimeMillis() - date2000GMT.getTime()) < 1 ) {
                              date = new Date( date2000GMT.getTime() + nDeadline );
                              throw new AuctionTimeoutException("Overall auction deadline of "+date+" has expired","auction timeout "+date);
                        }
                  }

                  // if first bid then come up with an initial bid threshold
                  // bid value roughly equates to the fractional similarity value a bid has with the ideal bid
                  // from the clients point of view
                  if( history.getBiddingThreshold() == -1 ) {
                        // start value specified in the config
                        nMinBidValue = mnBidThresholdStart;
                  }
                  else {
                        // get bidding threshold and add N to formulate the new bidding threshold
                        nMinBidValue = history.getBiddingThreshold() + mnBidThresholdIncrease;
                  }
                  history.setBiddingThreshold( nMinBidValue );

                  // get the deadline for bid collection, based on the current time offset from 2000.1.1 GMT
                  calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
                  calendar.set( 2000,0,1,0,0,0 );
                  date2000GMT = calendar.getTime();
                  nDeadline = (System.currentTimeMillis() + mnRoundDuration) - date2000GMT.getTime();

                  // record cfp deadline
                  history.setProposalDeadline( nDeadline );

                  // loop on all parties
                  for( i=0;i<vectorParties.size();i++ ) {
                        // get proxy
                        proxy = (GEMSSProxy) vectorParties.elementAt(i);
                        if( !history.queryFinished( proxy ) ) {
                            // get WSLA template for this service provider
                            strWSLA = history.getWSLATemplate( proxy );
                            if( strWSLA == null ) throw new NoCFPsSentException("No WSLA template found for one of the participants. This is an infrastructure error - please check logs for details.","null WSLA for proxy");

                            // make CFP auction message for this service provider
                            strCFP = formatCFP( requirements, nMinBidValue, nDeadline );

                            // add CFP to history
                            history.setCFP( proxy, strCFP );

                            // make a FIPA cfp message for the WSLA
                            // TODO get a distinguished name from somewhere
                            strID = proxy.queryNegConversationID();
                            header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
                                                               getDistinguishedName( null ),
                                                               getDistinguishedName( proxy ) );

                            // make body (WSLA construct)
                            body = new AuctionMessageBody( strCFP );

                            // make message
                            message = mauctionHandler.formatCFPMessage( header,body );

                            // invoke auctionInform using the timeout thread and wait until
                            // finished or a time out / error occurs
                            thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticAuctionCFPFlag, message, mnInvocationTimeout );
                            thread.start();
                            while( thread.isWaiting() ) {
                                  // wait a short time
                                  try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                  // check for stop flag (set by terminate method)
                                  if( mbStop ) {
                                        thread.flagStop( "QoS negotiation component termination" );
                                        throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                  }
                            }

                            // re-throw any exceptions if thread failed
                            if( thread.getException() != null ) {
                                  throw thread.getException();
                            }
                       } // finished bidding check
                  } // next proxy in list of parties

                  // logging
                  mlogger.log( Level.INFO,"Probe: sendCFPs\t"+nMinBidValue+"\t"+nDeadline );

                  // all done
                  return history;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during sendCFPs",ex);
                  throw new NoCFPsSentException( "QoS negotiation error sending calls for proposals - please check logs for details","sendCFPs failed - check logs" );
            }
      }

      /**
       * Gets each of the proposals, if available, from the participants of the auction. The history timestamp will be updated
       * and new proposals added using this new timestamp.
       * @param history Proposal histroy which includes details of the participants in the auction
       * @return Updated proposal history containing the new bids from service providers who responded in time
       */
      public ProposalHistory getProposals( ProposalHistory history ) throws GridException
      {
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            GEMSSProxy proxy;
            NegotiationTimeoutThread thread;
            Vector vectorParties;
            int i;
            long nTimestamp, nDeadline;
            String strWSLA, strWSLATemplate, strID;
            Date date2000GMT, date;
            GregorianCalendar calendar;

            try {
                  // check params
                  if( history == null ) throw new NoProposalsAvailableException("Proposal history was null. This is an infrastructure problem - check logs for details","null params");

                  // check for an overall auction timout
                  nDeadline = history.getAuctionDeadline();
                  if( nDeadline != -1 ) {
                        calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
                        calendar.set( 2000,0,1,0,0,0 );
                        date2000GMT = calendar.getTime();
                        if( nDeadline - (System.currentTimeMillis() - date2000GMT.getTime()) < 1 ) {
                              date = new Date( date2000GMT.getTime() + nDeadline );
                              throw new AuctionTimeoutException("Overall auction deadline of "+date+" has expired","auction timeout "+date);
                        }
                  }

                  // update the timestamp and use this new value
                  nTimestamp = history.updateTimestamp();

                  mlogger.log( Level.INFO,"Probe: getProposals\t"+nTimestamp );

                  // get parties
                  vectorParties = history.getParties();

                  // loop on all parties
                  for( i=0;i<vectorParties.size();i++ ) {
                        // get proxy
                        proxy = (GEMSSProxy) vectorParties.elementAt(i);

                        // make a FIPA propose message to get bids if ready
                        // TODO get a distinguished name from somewhere
                        strID = proxy.queryNegConversationID();
                        header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
                                                           getDistinguishedName( null ),
                                                           getDistinguishedName( proxy ) );

                        // make body (no body for the propose message)
                        body = new AuctionMessageBody( "" );

                        // make message
                        message = mauctionHandler.formatProposeMessage( header,body );

                        // invoke auctionPropose using the timeout thread and wait until
                        // finished or a time out / error occurs
                        thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticAuctionProposeFlag, message, mnInvocationTimeout );
                        thread.start();
                        while( thread.isWaiting() ) {
                              // wait a short time
                              try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                              // check for stop flag (set by terminate method)
                              if( mbStop ) {
                                    thread.flagStop( "QoS negotiation component termination" );
                                    throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                              }
                        }

                        // If we get an exception then ignore this participant. This will mean that not all participants are
                        // guarenteed to have a bid (e.g. they are not ready or a timeout occured) for this round of the
                        // auction
                        if( thread.getException() == null ) {
                              // get response message which will contain a WSLA proposal (bid)
                              message = thread.getResultMessage();
                              if( message != null ) {
                                    // get body (WSLA) which will be "" is service provider does not want to, or cannot, bid
                                    body = mauctionHandler.parseMessageBody( message );
                                    if( body != null ) {
                                          // get bid(WSLA) to proposal history (or "" for a no bid)
                                          strWSLA = body.toString();
                                          if( !strWSLA.equals("") ) {
                                                // get WSLA template for this proxy
                                                strWSLATemplate = history.getWSLATemplate( proxy );

                                                // TODO validate WSLA against this template
                                                // Gerhard does not return comments in WSLA :( maybe add legal text into
                                                // a new construct extending the WSLA spec or something
                                                //if( !mwslaHandler.validateWSLA( strWSLA, strWSLATemplate ) ) {
                                                //      mlogger.log( Level.WARNING, "WSLA validation error - check service providers WSLA proposal");
                                                //}
                                                //else {
                                                // add WSLA to history
                                                history.addProposal( proxy, strWSLA, nTimestamp );
                                                mlogger.log( Level.INFO,"Bid received timestamp "+nTimestamp+" from neg conv "+proxy.queryNegConversationID() );
                                                mlogger.log( Level.INFO,"Probe: proposal-received\t"+nTimestamp+"\t"+proxy.queryNegConversationID() );
                                                //}

                                                // No need to check WSLA validity dates since if the WSLA contract has a mistake the
                                                // client will have grounds for not paying. It is therefore up to the service provider
                                                // to ensure that the WSLA is a valid one.
                                          }
                                          else {
                                                // no more bids will come from this proxy
                                                history.setFinished( proxy );
                                                mlogger.log( Level.INFO,"No bid received timestamp "+nTimestamp+" from neg conv "+proxy.queryNegConversationID() );
                                          }
                                    }
                                    else mlogger.log( Level.WARNING,"null body returned in a proposal return message - service provider bid ignored since not ready to bid" );
                              }
                              else mlogger.log( Level.SEVERE,"null message returned in a proposal return message - service provider bid ignored but this is a bug" );
                        }
                        else mlogger.log( Level.INFO,"Exception thrown when getting proposal from a participant (client will ignore this proxy)",thread.getException() ); // testing log really - could be removed
                  } // next proxy in list of parties

                  // all done
                  return history;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during getProposals",ex);
                  throw new NoProposalsAvailableException( "QoS negotiation error getting proposals - please check logs for details","getProposals failed - check logs" );
            }
      }

      /**
       * Evaluates a proposal history, and determines if the auction should finish or run another round of bidding. Also checks
       * the number of rounds (<= MaxRounds)
       * @param history Proposal histroy which includes details of the participants in the auction
       * @return boolean return, true means end the auction, false means run another round of bidding
       */
      public boolean evaluateProposals( ProposalHistory history ) throws GridException
      {
            long nTimeStamp;
            double nValue;
            boolean bOk;
            Hashtable hashProposals, hashValues;
            Enumeration enum;
            GEMSSProxy proxy;
            String strWSLA, strCFP;
            CallForProposalsXML call;

            try {
                  // reached the max number of rounds?
                  nTimeStamp = history.getLastTimestamp();
                  if( nTimeStamp >= mnMaxAuctionRounds ) {
                        mlogger.log( Level.INFO,"Max number of rounds reached at "+mnMaxAuctionRounds+" - auction ending");
                        mlogger.log( Level.INFO,"Probe: evaluateProposals\tmax-rounds-reached" );
                        return true;
                  }
                  mlogger.log( Level.INFO,"Probe: evaluateProposals\t"+nTimeStamp );

                  // loop on each proxies WSLA bids this timestamp
                  bOk = true;
                  hashProposals = history.getProposals( nTimeStamp ); // (GEMSSProxy,WSLA string)
                  if( hashProposals != null ) {
                        enum = hashProposals.keys();
                        while( enum.hasMoreElements() ) {
                              // get proxy
                              proxy = (GEMSSProxy) enum.nextElement();

                              // get last call for this proxy (if any)
                              strCFP = history.getCFP( proxy );
                              if( strCFP != null ) {
                                    call = new CallForProposalsXML();
                                    if( call.parseCFP( strCFP ) == true ) {
                                          // get WSLA proposal for this proxy (if any)
                                          strWSLA = (String) hashProposals.get( proxy );
                                          if( strWSLA != null && !strWSLA.equals("") ) {
                                                // parse WLSA and get the hash of is values
                                                hashValues = mwslaHandler.parseSLAParameters( strWSLA );
                                                if( hashValues != null ) {
                                                      // calculate the value of the overall WSLA bid
                                                      nValue = mevaluator.calculateScore( hashValues, call );

                                                      // does value meet the threshold value? if so we need another round of bidding since
                                                      // the client might be able to squeeze a better deal from the service provider(s)
                                                      if( nValue >= history.getBiddingThreshold() ) {
                                                            mlogger.log( Level.INFO,"Probe: evaluateProposals\tkeep-bidding" );
                                                            bOk = false; // force bidding to continue
                                                      }
                                                      else {
                                                            // end bidding for parties that do not meet the threshold
                                                            // will keep a record of thier bid since it might still win
                                                            // if the others fail to exchange contracts
                                                            history.setFinished( proxy );
                                                      }
                                                } // parse fail check
                                                else mlogger.log( Level.SEVERE,"Failed to parse WSLA - ignoring but this is an error - check service provider WSLA template" );
                                          } // no bid check
                                          else {
                                                // end bidding for parties that do not bid this round
                                                history.setFinished( proxy );
                                          }
                                    } // parse CFP check
                              } // no CFP check
                        } // next proxy
                  } // no proposals check

                  // logging
                  if( bOk ) mlogger.log( Level.INFO,"Probe: evaluateProposals\tend-bidding" );

                  // if no-one at all met the threshold the auction must end
                  return bOk;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during evaluateProposals",ex);
                  throw new NegotiationException( "QoS negotiation error evaluating proposals - please check logs for details","evaluateProposals failed - check logs" );
            }
      }

      /**
       * Accepts the best proposal and returns a bound GEMSSProxy object ready for job handling. The proposal history
       * is run from best bid to worst acceptable bid until a service provider successfully exchanges contracts. Only
       * after signed digital contracts are exchanges is any party committed to thier agreeemnt. Once exchanged the other
       * parties are rejected and the bound GEMSSProxy object returned.
       * @param history Proposal histroy which includes details of the participants in the auction
       * @return A bound GEMSSProxy object with which job handling can commence, or null if no bidders exchanged contracts successfully
       */
      public GEMSSProxy acceptBestProposal( ProposalHistory history ) throws GridException
      {
            Vector vectorProxies;
            Hashtable hashBestBidSummary; // [proxy,String WSLA summary]
            Hashtable hashBestBidValues; // [proxy,Double value]
            Hashtable hashBestBids; // [proxy,String WSLA]
            Hashtable hashProposals; // [proxy, String WSLA]
            Hashtable hashWSLAValues; // [prop name,vector(unit, type, value)]
            Enumeration enum;
            int i;
            long nTimestamp;
            double nValue, nTopValue;
			boolean bAcceptedOk;
            Double nValueObject;
            GEMSSProxy proxy, proxyBest;
            CallForProposalsXML call;
            String strWSLA, strCFP, strSignedWSLA, strSummary, strProp;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            NegotiationTimeoutThread thread;
            String strID;
			ComponentManager compMan;
			AcceptContractRequest request;
			Signal arraySignals[];

            try {
                  // get proxy list
                  vectorProxies = history.getParties();
                  if( vectorProxies == null ) {
                        mlogger.log( Level.INFO,"No parties in history - no winning bidder");
                        return null;
                  }

                  // make hash of proxies for best bid value
                  hashBestBidSummary = new Hashtable();
                  hashBestBidValues = new Hashtable();
                  hashBestBids = new Hashtable();

                  // get the best bid (last valid bid) and best bid value for each proxy
                  // loop on each proxy
                  for( i=0;i<vectorProxies.size();i++ ) {
                        // get proxy
                        proxy = (GEMSSProxy) vectorProxies.elementAt(i);

                        // loop on timestamps, going from last to first
                        nTimestamp = history.getLastTimestamp();
                        while( nTimestamp > 0 ) {
                              // get bid at this timestamp (if any)
                              hashProposals = history.getProposals( nTimestamp );
                              if( hashProposals != null ) {
                                    strWSLA = (String) hashProposals.get( proxy );
                                    if( strWSLA != null ) {
                                          // get WSLA values
                                          hashWSLAValues = mwslaHandler.parseSLAParameters( strWSLA );
                                          if( hashWSLAValues == null ) throw new NoProposalsAcceptedException("No QoS properties found within WSLA proposal. Please check server side WSLA template for this service.","failed to parse WSLA");

										  // make a summary string
										  strSummary = makeWSLASummary( hashWSLAValues );

                                          // get last cfp for this proxy
                                          // CFP's only vary in threshold so min/max values are fine to use for any proposal
                                          strCFP = history.getCFP( proxy );
                                          call = new CallForProposalsXML();
                                          if( !call.parseCFP( strCFP ) ) throw new NoProposalsAcceptedException("Failed to parse the original call for proposals. This is an infrastructure error - please check logs for details.","failed to parse CFP");

                                          // calc value of bid
                                          nValue = mevaluator.calculateScore( hashWSLAValues, call );
                                          nValueObject = new Double( nValue );

                                          // add value to hash of best bid values since we only want the latest bid. earlier bids
                                          // will not still be valid, so there is no point in calculating the value of anything other
                                          // that the latest valid bid
                                          hashBestBids.put( proxy,strWSLA );
                                          hashBestBidValues.put( proxy,nValueObject );
                                          hashBestBidSummary.put( proxy,strSummary );

                                          // stop looking for bids for this proxy
                                          nTimestamp = 0;
                                    } // WSLA null check
                              } // null proposal list check

                              // next timestamp to check
                              if( nTimestamp > 0 ) nTimestamp--;
                        } // next timestamp
                  } // next proxy

                  // while still proxies left
                  while( hashBestBidValues.size() > 0 ) {
                        // loop on bids that are still left and find best one
                        proxyBest = null;
                        nTopValue = -1;
                        enum = hashBestBidValues.keys();
                        while( enum.hasMoreElements() ) {
                              // get proxy
                              proxy = (GEMSSProxy) enum.nextElement();

                              // get bid value
                              nValueObject = (Double) hashBestBidValues.get( proxy );
                              if( nValueObject != null ) {
                                    // best bid so far?
                                    if( nValueObject.doubleValue() > nTopValue ) {
                                          // update best bid
                                          proxyBest = proxy;
                                          nTopValue = nValueObject.doubleValue();
                                    }
                              } // value null check
                        } // next proxy

                        // if we have a top bid that has SOME value, try to exchange contracts
                        // A bid might have value 0 if it violates the min/max criteria or is the very worst
                        if( nTopValue > 0 && proxyBest != null ) {
                              // logging
                              mlogger.log( Level.INFO,"Probe: acceptBestProposal\t"+nTopValue+"\t"+proxyBest.queryNegConversationID() );

                              // AcceptProposal

                              // make a FIPA accept proposal message for the WSLA
                              // TODO get a distinguished name from somewhere
                              strID = proxyBest.queryNegConversationID();
                              header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
                                                                 getDistinguishedName( null ),
                                                                 getDistinguishedName( proxyBest ) );

                              // make body (WSLA construct)
                              strWSLA = (String) hashBestBids.get( proxyBest );
                              if( strWSLA == null ) throw new NoProposalsAcceptedException("A null WSLA proposal was found within the list of proposals. This is an infrastructure error - please check logs for details.","null WSLA in best bids hash");
                              body = new AuctionMessageBody( strWSLA );

                              // make message
                              message = mauctionHandler.formatAcceptProposalMessage( header,body );

                              // invoke auctionInform using the timeout thread and wait until
                              // finished or a time out / error occurs
                              thread = new NegotiationTimeoutThread( proxyBest, NegotiationTimeoutThread.mstaticAuctionAcceptProposalFlag, message, mnInvocationTimeout );
                              thread.start();
                              while( thread.isWaiting() ) {
                                    // wait a short time
                                    try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                    // check for stop flag (set by terminate method)
                                    if( mbStop ) {
                                          thread.flagStop( "QoS negotiation component termination" );
                                          throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                    }
                              } // loop until invocation finished

                              // re-throw any exceptions if thread failed, otherwise responde to success or failure
                              if( thread.getException() != null ) {
                                    // log reason for failure (e.g. network connection failed)
                                    mlogger.log( Level.WARNING,"Best bidder failed to accept proposal (will try next best)",thread.getException() );
                                    mlogger.log( Level.INFO,"Probe: acceptBestProposal\tfailed-to-accept\t"+proxyBest.queryNegConversationID() );

                                    // invocation failed - log failure and procceed to the next best bid
                                    hashBestBids.remove( proxyBest );
                                    hashBestBidValues.remove( proxyBest );
                              }
                              else {

									// Fire AcceptContract signal

									// ask user to accept the contract just before we sign it. This happens with a signal
									// being generated. If no-one handles this signal this code will accept the contract ANYWAY.
									bAcceptedOk = true;
									strSummary = (String) hashBestBidSummary.get( proxyBest );

									// get comp man and make signal
									compMan = GEMSS.getInstance();
									if( compMan != null ) {
										// make signal
										request = new AcceptContractRequest( this );
										request.setWSLA( strWSLA,strSummary ); // this WSLA
										request.setContracts( hashBestBidValues.size() - 1 ); // bids remaining not including this one

										// check for termination of component
										if( mbStop ) throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");

										// send signal and wait for response (blocking)
										arraySignals = compMan.generateSignal( request );
										if( arraySignals.length > 0 ) {
											if( arraySignals[0] instanceof AcceptContractRequest ) {
												if( arraySignals.length > 1 ) mlogger.log( Level.WARNING,"Multiple handlers responded to AcceptContractRequest - first response used only, others are ignored");

												// get first response
												request = (AcceptContractRequest) arraySignals[0];
												if( !request.wasProcessed() ) mlogger.log( Level.WARNING,"Signal handler did not process the AcceptContractRequest signal. The contract is assumed accepted, but this should be looked into to make sure everything is ok. Do not register a signal handler if it is not going to process the signal.");
												if( request.queryAccept() == false ) {
													bAcceptedOk = false;
													mlogger.log( Level.INFO,"Contract rejected by an accept contract signal handler. Will try next best contract if there is one.");
												}
												else mlogger.log( Level.INFO,"Contract accepted by an accept contract signal handler.");
											} // instance type check
											else mlogger.log( Level.WARNING,"Signal handler returned incorrect signal type. Ignoring unknown signal return type.");
										} // assume acceptance if there are no signal handlers
										else mlogger.log( Level.INFO,"No accept contract signal handlers processed the accept contract signal (this is ok if client wants to automatically accept all negotiated contracts). Accepting contract as the default action.");
									}
									else throw new NoProposalsAcceptedException("Could not access the component manager to fire an accept contract signal. Please check component manager configuration or check logs for more details.","null comp man" );

									// ExchangeContracts
									if( bAcceptedOk ) {
	                                    // make a FIPA request message for the WSLA
		                                // TODO get a distinguished name from somewhere
			                            // TODO get a signed WSLA
				                        strID = proxyBest.queryNegConversationID();
					                    header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
						                                                   getDistinguishedName( null ),
							                                               getDistinguishedName( proxyBest ) );

	                                    // make body (signed WSLA)
		                                strSignedWSLA = strWSLA; // TODO need signing code here
			                            body = new AuctionMessageBody( strSignedWSLA );

				                        // make message
					                    message = mauctionHandler.formatRequestContractMessage( header,body );
	
		                                // invoke auctionInform using the timeout thread and wait until
			                            // finished or a time out / error occurs
				                        thread = new NegotiationTimeoutThread( proxyBest, NegotiationTimeoutThread.mstaticAuctionRequestFlag, message, mnInvocationTimeout );
					                    thread.start();
						                while( thread.isWaiting() ) {
							                  // wait a short time
								              try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

									          // check for stop flag (set by terminate method)
										      if( mbStop ) {
											        thread.flagStop( "QoS negotiation component termination" );
												    throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
	                                          }
		                                } // loop until invocation finished

			                            // re-throw any exceptions if thread failed, otherwise responde to success or failure
				                        if( thread.getException() != null ) {
					                          // log reason for failure (e.g. network connection failed)
						                      mlogger.log( Level.WARNING,"Best bidder failed request to exchange contracts (will try next best)",thread.getException() );
							                  mlogger.log( Level.INFO,"Probe: acceptBestProposal\tfailed-to-exchange\t"+proxyBest.queryNegConversationID() );
	
		                                      // invocation failed - log failure and procceed to the next best bid
			                                  hashBestBids.remove( proxyBest );
				                              hashBestBidValues.remove( proxyBest );
					                    }
						                else {
							                  // get result message (contract signed by the service provider)
								              message = thread.getResultMessage();

	                                          // TODO check signed contract is ok
		                                      // TODO record signed contract in an auditable database client side

			                                  // OK! we have a winner
				                              mlogger.log( Level.INFO,"Contracts exchanged ok, neg conv id "+proxyBest.queryNegConversationID()+" with value "+nTopValue);
					                          mlogger.log( Level.INFO,"Probe: acceptBestProposal\tcontracts-exchanged\t"+proxyBest.queryNegConversationID() );

						                      // reject remaining losers (ignore any service provider that failed to process an accept proposal
							                  // message since they are probably offline anyway!)
								              hashBestBids.remove( proxyBest );
									          rejectProposalList( hashBestBids ); // will ignore if fail to reject since timeout will occur anyway
	
		                                      return proxyBest;
			                            } // request exception check
									} // accept contract signal handler check
									else {
										// not accepted - remove this bid and procceed to the next best bid
										hashBestBids.remove( proxyBest );
										hashBestBidValues.remove( proxyBest );
									}
                              } // accept proposal exception check
                        } // value > 0 check
                        else {
                              // any remaining bids are value == 0 so reject them
                              // value 0 means they either fail to meet the basic standard min/max or they are absolutely the
                              // worst bid with all min values (very unlikely)
                              rejectProposalList( hashBestBids ); // will ignore if fail to reject since timeout will occur anyway
                        }
                  } // loop until we run out of bids

                  // at this point all bids have either been rejected or failed to accept. No more service provider
                  // notification is therefore needed
                  mlogger.log( Level.INFO,"All bidders failed to exchange contracts - auction ended with no contract agreed");
                  return null;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during acceptBestProposal",ex);
                  throw new NoProposalsAcceptedException( "QoS negotiation error accepting teh best proposal - please check logs for details","acceptBestProposal failed - check logs" );
            }
      }

      /**
       * Make a human readable summary string of the WSLA. This will end up in the AcceptContractRequest signal and
	   * being displayed to the user. This method knows about type mappings etc to make nice values for the time
	   * properties.
       * @param hashWSLAValues hash of WSLA values [prop name,vector(unit, type, value)]
       */
      private String makeWSLASummary( Hashtable hashWSLAValues ) throws Exception
      {
		Enumeration enum;
		String strSummary, strProp, strTime, strType, strValue;
		Vector vector;
		long nOffset;
		Integer nInteger;
		Date date2000GMT, date;
		GregorianCalendar calendar;

		try
		{
			// find the time type mapping name so we can match it with this property
			strTime = null;
			enum = mhashTypeMappings.keys();
			while( enum.hasMoreElements() ) {
				strType = (String) enum.nextElement();
				nInteger = (Integer) mhashTypeMappings.get( strType );
				if( nInteger.intValue() == GetQoSParametersRequest.mstaticGMT2000DateOffsetUnitFlag ) {
					strTime = strType;
					mlogger.log( Level.INFO,"Making WSLA summary using time type = "+strTime );
				}
			}

			// make year 2000 date
			calendar = new GregorianCalendar( TimeZone.getTimeZone("GMT") );
			calendar.set( 2000,0,1,0,0,0 );
			date2000GMT = calendar.getTime();

			// make summary for this WSLA
			strSummary = "--- WSLA property value agreements ---\n";
			enum = hashWSLAValues.keys();
			while( enum.hasMoreElements() ) {
				// get prop name
				strProp = (String) enum.nextElement();
				if( strProp == null ) throw new Exception("null prop in bid hash values");

				// get prop values
				vector = (Vector) hashWSLAValues.get( strProp );
				if( vector == null ) throw new Exception("null vector in bid hash values");
				if( vector.size() != 3 ) throw new Exception("vector has incorrect size of "+vector.size());

				// add property name
				strSummary += "\n" + strProp + " = ";

				// init time check stuff
				date = null;
				strValue = (String) vector.elementAt(2);

				// is the type a time type?
				if( strTime != null ) {
					strType = (String) vector.elementAt(1);
					if( strTime.equals( strType ) ) {
						// get offset value
						nOffset = Long.parseLong( strValue );

						// add year 2000 to make a real date
						date = new Date( nOffset + date2000GMT.getTime() );
					}
				}

				// add value (or date) to the summary
				if( date != null ) strSummary += date.toString();
				else strSummary += strValue;

			} // next property

			// all done
			return strSummary + "\n";
		}
		catch( Exception ex ) {
			mlogger.log( Level.SEVERE,"Exception in makeWSLASummary",ex);
			throw ex; // pass exception on through
		}
	  }

      /**
       * Send a reject proposal message to all the proxies in the list passed. This will end the negotiation
       * conversations with the proxies. Failure to reject a bid will not result in an exception being thrown
       * since the service providers can always timeout - therefore the clientn does not care about successful rejections
       * really. Software faults will throw exceptions of course.
       * @param hashRejectedBids hash of [proxy,WSLA string]
       */
      private void rejectProposalList( Hashtable hashRejectedBids ) throws Exception
      {
            Enumeration enum;
            GEMSSProxy proxy;
            AuctionMessage message;
            AuctionMessageHeader header;
            AuctionMessageBody body;
            NegotiationTimeoutThread thread;
            String strID, strWSLA;

            try {
                  // loop on proxies
                  enum = hashRejectedBids.keys();
                  while( enum.hasMoreElements() ) {
                        // get proxy
                        proxy = (GEMSSProxy) enum.nextElement();
                        if( proxy != null ) {
                              // get bid
                              strWSLA = (String) hashRejectedBids.get( proxy );
                              if( strWSLA != null ) {
                                    // RejectProposal

                                    // make a FIPA accept proposal message for the WSLA
                                    // TODO get a distinguished name from somewhere
                                    strID = proxy.queryNegConversationID();
                                    header = new AuctionMessageHeader( strID, mstaticLanguage, mstrOntologyURI,
                                                                 getDistinguishedName( null ),
                                                                 getDistinguishedName( proxy ) );

                                    // make body (WSLA construct)
                                    body = new AuctionMessageBody( strWSLA );

                                    // make message
                                    message = mauctionHandler.formatRejectProposalMessage( header,body );

                                    // invoke auctionInform using the timeout thread and wait until
                                    // finished or a time out / error occurs
                                    thread = new NegotiationTimeoutThread( proxy, NegotiationTimeoutThread.mstaticAuctionRejectProposalFlag, message, mnInvocationTimeout );
                                    thread.start();
                                    while( thread.isWaiting() ) {
                                          // wait a short time
                                          try { Thread.sleep( mstaticInvocationCheckPeriod ); } catch( InterruptedException ie ) { ; }

                                          // check for stop flag (set by terminate method)
                                          if( mbStop ) {
                                                thread.flagStop( "QoS negotiation component termination" );
                                                throw new NegotiationShutdownException("QoS negotiation component terminating.","stop flag set");
                                          }
                                    } // loop until invocation finished

                                    // carry on regardless if we get an exception invoking the service provider
                                    if( thread.getException() != null ) {
                                          // log reason for failure (e.g. network connection failed) but carry on
                                          mlogger.log( Level.INFO,"Failed to rejected bid (client will ingore), neg ID "+proxy.queryNegConversationID()+" : "+thread.getException().getMessage() );
                                          mlogger.log( Level.INFO,"Probe: rejectBestProposal\treject-failed\t"+proxy.queryNegConversationID() );
                                    }
                                    else {
                                          mlogger.log( Level.INFO,"Rejected bid ok, neg ID "+proxy.queryNegConversationID() );
                                          mlogger.log( Level.INFO,"Probe: rejectBestProposal\trejected-ok\t"+proxy.queryNegConversationID() );
                                    }
                              } // WSLA null check
                              else mlogger.log( Level.INFO,"Failed to rejected bid, neg ID "+proxy.queryNegConversationID()+" : null WSLA" );
                        } // proxy null check
                        else mlogger.log( Level.INFO,"Failed to rejected bid, null proxy" );
                  } // loop until we run out of bids

                  // all done
                  return;
            }
            catch( GridException gridex ) {
                  // pass all Grid exceptions back to the caller
                  throw gridex;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during rejectProposalList",ex);
                  throw new NegotiationException( "QoS negotiation error rejecting failed proposals - please check logs for details","rejectProposalList failed - check logs" );
            }
      }

      /**
       * Creates an XML string containing the auction details. This will include things like
       * the auction overall end time, max number of rounds of bidding
       * @param nMaxRounds max number of rounds of the auction
       * @param nStart start time millisecond GMT 2000 offset
       * @param nMaxDuration max auction duration in milliseconds from the start time
       * @return An XML string containing the auction details (null on error)
       */
      private String formatAuctionDetails( int nMaxRounds, long nStart, long nMaxDuration ) throws Exception
      {
            AuctionDetailsXML details;

            try {
                  // auction information
                  // start time, auction max duration, no of rounds of bidding
                  // start time is millisecond offset from year 2000 GMT
                  details = new AuctionDetailsXML();
                  details.setAuctionDetails( nMaxRounds, nStart, nMaxDuration );
                  return details.formatAuctionDetails();
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during formatAuctionDetails (returns null)",ex);
                  return null;
            }
      }

      /**
       * Construct an XML CFP object based on the requirements and bidding threshold.
       * @param reqs QoS requirements specifying weights, min, max etc
       * @param nBidThreshold bidding value threshold (bids must be worth more than this)
       * @param nDeadline deadline for bid collection as an offset from GMT 2000
       * @return A call for proposals XML object
       */
      private String formatCFP( QoSRequirements reqs, double nBidThreshold, long nDeadline ) throws Exception
      {
            CallForProposalsXML cfp;
            Hashtable hashProps;
            Vector vectorProp;
            Enumeration enum;
            String strValue, strName, strConstraint, strMax, strMin;
            double nMax, nMin, nRange;
            long nTimeOffset;

            try {
                  // cfp information
                  // bidding threshold, cfp vector of (param name URI, weight, min, max, constraint )
                  cfp = new CallForProposalsXML();
                  cfp.setDetails( nBidThreshold, nDeadline );

                  // loop on props
                  // reqs = [name,(unit type, weight, min, max, constraint)] all strings
                  hashProps = reqs.getQoSProperties();
                  if( hashProps != null ) {
                        enum = hashProps.keys();
                        while( enum.hasMoreElements() ) {
                              // get property
                              strName = (String) enum.nextElement();
                              vectorProp = (Vector) hashProps.get( strName );

                              // size check
                              if( vectorProp.size() != 5 ) throw new Exception("Reqs vector size "+vectorProp.size()+" != 5");

                              // get constraint, min and max
                              strConstraint = (String) (String) vectorProp.elementAt(4);
                              strMax = (String) vectorProp.elementAt(3);
                              strMin = (String) vectorProp.elementAt(2);

                              // add property to CFP
                              cfp.addProperty( strName, // name
                                               (String) vectorProp.elementAt(1), // weight
                                               strMin, strMax, strConstraint );
                        } // next reqs property
                  } // null hash check

                  return cfp.formatCFP();
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Exception during formatCFP (returns null)",ex);
                  return null;
            }
      }

      /**
       * Returns the distinguished name of a proxy (service provider), or if the parameter is null this current user (client).
       * This method is a stub, and will be implemented later in the project.
       * @return string containing the distinguished name
       */
      private String getDistinguishedName( GEMSSProxy proxy ) throws Exception
      {
            // TODO DN support for both client and service provider, based on the security
            // certificates probably
            if( proxy == null ) {
                  return "Client-DN-not-supported";
            }
            else {
                  return "Service-Provider-DN-not-supported";
            }
      }

      /**
       * Helper thread class to invoke blocking GEMSS proxy functions and enable a timeout
       * to be triggered if they take too long. Exceptions are recorded and made accessible
       * via a get function if needed. The output, if any, is available too.
       */
      class NegotiationTimeoutThread extends Thread {
            // member data
            AuctionMessage mmessageIn;      // FIPA message in (if any)
            AuctionMessage mmessageOut;     // FIPA message out (if any)
            String mstrRequestDescFilename; // Request desc file (if any)
            String mstrWSLA;                // WSLA string (if any)
            String mstrNegID;               // Neg ID (if any)
            GEMSSProxy mproxy;
            Exception mexception;
            long mnMaxTime;
            int mnInvocationFlag;
            boolean mbWaiting;

            // constants
            public static final int mstaticAuctionInformFlag = 1;
            public static final int mstaticAuctionCFPFlag = 2;
            public static final int mstaticAuctionProposeFlag = 3;
            public static final int mstaticAuctionAcceptProposalFlag = 4;
            public static final int mstaticAuctionRejectProposalFlag = 5;
            public static final int mstaticAuctionRequestFlag = 6;
            public static final int mstaticUploadRequestDescFlag = 7;
            public static final int mstaticGetWSLAFlag = 8;
            public static final int mstaticGetNegIDFlag = 9;

            /**
             * constructor for FIPA message calls
             * @param proxy GEMSSProxy instance
             * @param nFlag type of FIPA message call to make
             * @param message FIPA message argument for the proxy invocation
             * @param nTimeout Time to allocate, in milliseconds, for this invocation before timeout occurs
             */
             public NegotiationTimeoutThread( GEMSSProxy proxy, int nFlag, AuctionMessage message, long nTimeout )
             {
                   // do the thread thing
                   super();

                   // record setup
                   mnInvocationFlag = nFlag;
                   mstrRequestDescFilename = null;
                   mmessageIn = message;
                   mmessageOut = null;
                   mstrWSLA = null;
                   mnMaxTime = System.currentTimeMillis() + nTimeout;
                   mproxy = proxy;
                   mbWaiting = true;
                   mexception = null;
                   mstrNegID = null;
             }

             /**
              * constructor for the request desc operation
              * @param proxy GEMSSProxy instance
              * @param strRequestDescFilename filename of the RequestDesc file to upload
              * @param nTimeout Time to allocate, in milliseconds, for this invocation before timeout occurs
              */
              public NegotiationTimeoutThread( GEMSSProxy proxy, String strRequestDescFilename, long nTimeout )
              {
                    // do the thread thing
                    super();

                    // record setup
                    mnInvocationFlag = mstaticUploadRequestDescFlag;
                    mstrRequestDescFilename = strRequestDescFilename;
                    mmessageIn = null;
                    mmessageOut = null;
                    mstrWSLA = null;
                    mnMaxTime = System.currentTimeMillis() + nTimeout;
                    mproxy = proxy;
                    mbWaiting = true;
                    mexception = null;
                    mstrNegID = null;
              }

              /**
               * constructor for the get WSLA and get Neg ID operation's
               * @param proxy GEMSSProxy instance
               * @param nFlag type of FIPA message call to make
               * @param nTimeout Time to allocate, in milliseconds, for this invocation before timeout occurs
               */
               public NegotiationTimeoutThread( GEMSSProxy proxy, int nFlag, long nTimeout )
               {
                     // do the thread thing
                     super();

                     // record setup
                     mnInvocationFlag = nFlag;
                     mstrRequestDescFilename = null;
                     mmessageIn = null;
                     mmessageOut = null;
                     mstrWSLA = null;
                     mnMaxTime = System.currentTimeMillis() + nTimeout;
                     mproxy = proxy;
                     mbWaiting = true;
                     mexception = null;
                     mstrNegID = null;
               }

             /**
              * reports if the thread is waiting to finish. This method will actually kill
              * the thread if the wait time has gone over the max time for this thread.
              * @return true if still waiting
              */
              public boolean isWaiting()
              {
                    // check for timeout
                    if( System.currentTimeMillis() > mnMaxTime ) {
                          // stop this invocation thread (timeout)
                          flagStop( "invocation time timeout" );
                    }

                    // return waiting flag
                    return mbWaiting;
              }

              /**
               * interrupts the thread and sets the stop flag
               */
               public void flagStop( String strExceptionMessage )
               {
                     // set timeout flag
                     mbWaiting = false;

                     // stop thread
                     this.interrupt();

                     // set an interrupted exception (this will probably be superceeded by
                     // the run loop's exception handler but that does not matter)
                     mexception = new InterruptedException( strExceptionMessage );
                     mlogger.log( Level.INFO,"NegotiationTimeoutThread flagStop : "+strExceptionMessage);
               }

              /**
               * returns the recorded exception (if any)
               * @return exception class thrown during thread execution
               */
               public Exception getException()
               {
                     return mexception;
               }

               /**
                * returns the result (if any)
                * @return auction message returned by the proxy invocation
                */
                public AuctionMessage getResultMessage()
                {
                      return mmessageOut;
                }

                /**
                 * returns the WSLA result (if any)
                 * @return WSLA string
                 */
                 public String getResultWSLA()
                 {
                       return mstrWSLA;
                 }

                 /**
                  * returns the neg ID (if any)
                  * @return neg ID
                  */
                  public String getNegID()
                  {
                        return mstrNegID;
                  }

               /**
                * invokes the proxy method and wait for a return.
                * NetworkExceptions are re-attempted but other exceptions will cause the
                * exception to be passed through and the invocation to fail
                */
                public void run()
                {
                      String strMessageOut;

                      while( mbWaiting ) {
                            try {
                                  // do invocation (blocking) of the right type
                                  switch( mnInvocationFlag ) {
                                        case mstaticAuctionInformFlag :
                                              mproxy.auctionInform( mmessageIn.toString() );
                                              break;
                                        case mstaticAuctionCFPFlag :
                                              mproxy.auctionCFP( mmessageIn.toString() );
                                              break;
                                        case mstaticAuctionProposeFlag :
                                              strMessageOut = mproxy.auctionPropose();
                                              mmessageOut = new AuctionMessage( strMessageOut );
                                              break;
                                        case mstaticAuctionAcceptProposalFlag :
                                              mproxy.auctionAcceptProposal( mmessageIn.toString() );
                                              break;
                                        case mstaticAuctionRejectProposalFlag :
                                              mproxy.auctionRejectProposal( mmessageIn.toString() );
                                              break;
                                        case mstaticAuctionRequestFlag :
                                              strMessageOut = mproxy.auctionRequest( mmessageIn.toString() );
                                              mmessageOut = new AuctionMessage( strMessageOut );
                                              break;
                                        case mstaticUploadRequestDescFlag :
                                              mproxy.uploadRequestDesc( mstrRequestDescFilename );
                                              break;
                                        case mstaticGetWSLAFlag :
                                              mstrWSLA = mproxy.getWSLA();
                                              break;
                                        case mstaticGetNegIDFlag :
                                              mstrNegID = mproxy.getNegCID();
                                              break;
                                        default:
                                              throw new Exception("Invalid invocation flag");
                                  }

                                  // all done (finish)
                                  mbWaiting = false;
                            }
                            catch( Exception ex ) {
                                  // check for NetworkExceptions and retry
                                  if( ex instanceof NetworkException ) {
                                        // retry (log for info)
                                        mlogger.log( Level.INFO,"Negotiator re-trying a network exception, invocation flag "+mnInvocationFlag+", error : "+ex.getMessage() );
                                  }
                                  else {
                                        // record any exception that is thrown
                                        // including the InterruptedException from a timeout
                                        mexception = ex;
                                        mbWaiting = false;
                                  }
                            }
                      } // end while - try until waiting is false
                } // end of run
      } // end of NegotiationTimeoutThread
} // end of QoSNegotiationImpl


