/////////////////////////////////////////////////////////////////////////
//
// © Institute for Software Science, University of Vienna, 2004
//
// Copyright in this software belongs to Institute for Software Science, 
// University of Vienna, Nordbergstrasse 15/C/3, 1090 Vienna, Austria
//
// 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 :		Gerhard Engelbrecht
//	Created Date :		2004/04/20
//	Created for Project:	GEMSS
//
////////////////////////////////////////////////////////////////////////
//
// Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//	Last commit info:	$Author: gerry $
//					$Date: 2004/12/14 22:13:32 $
//					$Revision: 1.14 $
//
/////////////////////////////////////////////////////////////////////////

package at.ac.univie.iss.service.qos.impl;


import java.rmi.RemoteException;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
//import java.text.*;
//import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;
//import java.util.logging.*;

import javax.activation.DataHandler;


import at.ac.univie.iss.apm.PerformanceModel;
import at.ac.univie.iss.apm.PerformanceModelFactory;
import at.ac.univie.iss.bm.BusinessModel;
import at.ac.univie.iss.bm.BusinessModelFactory;
import at.ac.univie.iss.common.session.IDGenerator;
import at.ac.univie.iss.crm.ComputeResourceManager;
import at.ac.univie.iss.crm.ComputeResourceManagerFactory;
import at.ac.univie.iss.crm.JobInfo;
import at.ac.univie.iss.descriptors.ApplicationDescriptor;
import at.ac.univie.iss.descriptors.qos.QoSDescriptor;
import at.ac.univie.iss.descriptors.qos.QoSDescriptorImpl;
import at.ac.univie.iss.descriptors.qos.RequestDescriptor;
import at.ac.univie.iss.descriptors.qos.RequestDescriptorImpl;
import at.ac.univie.iss.descriptors.qos.PerformanceDescriptor;
import at.ac.univie.iss.descriptors.qos.PerformanceDescriptorImpl;
import at.ac.univie.iss.service.app.AppHandler;
import at.ac.univie.iss.service.global.ServiceInfo;
import at.ac.univie.iss.service.qos.QoSHandler;
import at.ac.univie.iss.service.qos.state.*;
import at.ac.univie.iss.service.state.*;



final public class QoSHandlerImpl implements QoSHandler {

    private static Logger logger = null; 
    
    private SimpleDateFormat dateAndTimeFormatter = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss,S z"); 
    
    final private String NOT_AVAILABLE = "notAvailable";
    final private String MANIPULATED = "manipulated";
    
    final private long TEMP_RESERVATION_TIME = 120000; // 120 sec
    
    final private int DEFAULT_NODES_FOR_SUBMISSION = 4; // 30 sec
    final private int DEFAULT_MINUTES_FOR_SUBMISSION = 30; // 30 sec

	private AppHandler appHandler = null;

    private ApplicationDescriptor appDesc = null;
	private String serviceName = null;
	private DetailsRepository detailsRepository = null;

	private PerformanceParameter[] allowedPerfParams = null;
	private int[] allowedNodes = null;
    
    private ComputeResourceManager crm = null;

	private PerformanceModel perfModel = null;
	
	private BusinessModel businessModel = null;


	private boolean qosEnabled = true;
	
	
    private Hashtable offers;
    private Hashtable confirmedOffers;
    

	private boolean checkAllNumbersOfNodes = false;

    public QoSHandlerImpl() {
    	
    	try {
    		logger = ServiceInfo.getLogger("qos");
    		
    		logger.log(Level.INFO, "Started.\n");
    		
	        appDesc = ServiceInfo.getAppDsc();
	        
	        serviceName = ServiceInfo.getServiceName();
    		
   			// inits details repository (for state, id and QoS info)

			detailsRepository = ServiceInfo.getDetailsRepository();
			
    		try {
    			// init performance & machine parameters from app desc
    			
    			this.initQoSParameters();

				// init compute resource manager
				
				this.initComputeResourceManager();    			

				// init performance model
				
				this.initPerformanceModel();

				// init business model
				
				this.initBusinessModel();    			
    			
    		}
    		catch (Exception initException) {
    			
    			logger.log(Level.SEVERE, "QoS initialization failed!\n");
    			logger.log(Level.SEVERE, " - initException: " + initException.getMessage()  +" !\n");
    			logger.log(Level.SEVERE, " - initException! \n", initException);
    		
    			qosEnabled = false;
    		}
    		
			
        } catch (Exception exception) {

   			logger.log(Level.SEVERE, " - constructor exception: " + exception.getMessage()  +" !\n");
   			logger.log(Level.SEVERE, " - initException! \n", exception);
        }
        offers = new Hashtable();

    	logger.log(Level.INFO, "Finished.\n");
    }

	final private void initQoSParameters() throws Exception {
	
		// check if app desc supports performance parameters and machine parameters

		String[] names = appDesc.getPerformanceParameterNames();
		
		allowedPerfParams = new PerformanceParameter[names.length];
		
		for (int count = 0; count < names.length; count++) {
			
			allowedPerfParams[count] = new PerformanceParameter(names[count], null);
		}

		logger.log(Level.FINE, "Found " + allowedPerfParams.length + " allowed performance parameters in application descriptor.\n");

		allowedNodes = appDesc.getNumberOfNodes();
		
		logger.log(Level.FINE, "Found " + allowedNodes.length + " allowed numbers of nodes in application descriptor.\n");
		
   		logger.log(Level.FINE, "Finished.\n");
	}
	
	final private void initComputeResourceManager() throws Exception {
	
		// initialize compute resource manager	
		
		ComputeResourceManagerFactory factory = ComputeResourceManagerFactory.getInstance();
		
		String crmClass = appDesc.getComputeResourceManagerClass();
		
		if (crmClass == null) {
			
			crmClass = new String("at.ac.univie.iss.crm.test.ComputeResourceManagerImpl");
		}

		crm = factory.getComputeResourceManager(crmClass);

		if (crm == null) {
		
			throw new Exception("Initialization of the compute resource manager failed!");	
		}
		
   		logger.log(Level.FINE, "Finished.\n");
	}
	
	final private void initPerformanceModel() throws Exception {
		
		PerformanceModelFactory factory = PerformanceModelFactory.getInstance();
		
		String perfModelClass = appDesc.getPerformanceModelClass();
		
		perfModel = factory.getPerformanceModel(perfModelClass);
		
		if (perfModel == null) {
			
			throw new Exception("Initialization of the performance model failed!");	
		}

   		logger.log(Level.FINE, "Finished.\n");
	}
	
	final private void initBusinessModel() throws Exception {
		
		BusinessModelFactory factory = BusinessModelFactory.getInstance();
		
		String businessModelClass = appDesc.getBusinessModelClass();
		
		if (businessModelClass == null) {
			
			businessModelClass = new String("at.ac.univie.iss.bm.ppu.BusinessModelImpl");
		}
		
		businessModel = factory.getBusinessModel(businessModelClass);
		
		if (businessModel == null) {
			
			throw new Exception("Initialization of the business model failed!");	
		}

   		logger.log(Level.FINE, "Finished.\n");
	}
	
	final public boolean isQoSEnabled() {
		
		return qosEnabled;
	}
	
	
	///////////////////////////////////////////////////////////////////////////
	// REQUST QOS DESCRIPTOR 
	///////////////////////////////////////////////////////////////////////////
	
    final public String requestQoSDescriptor(String qosDescriptor, String requestDescriptor) throws RemoteException {

		Details details = null;

		try {

	        logger.log(Level.FINE, "Started.\n");
	        logger.log(Level.FINEST, "Parameter: Given (String) QoS Descriptor:\n" + qosDescriptor + "*\n\n");
	        logger.log(Level.FINEST, "Parameter: Given (String) Request Descriptor:\n" + requestDescriptor + "*\n\n");
	
			QoSDescriptor qosDesc = null;
			RequestDescriptor reqDesc = null;
			QoSDescriptor offer = null;
	
			//////////////////////////////////////////
			// PHASE 1: INITIAL CHECKING OF QOS & REQ
			//////////////////////////////////////////

			try {
				qosDesc = new QoSDescriptorImpl(qosDescriptor);
				reqDesc = new RequestDescriptorImpl(requestDescriptor);

				this.initialQoSDescCheck(qosDesc);
				this.initialReqDescCheck(reqDesc);
			}
			catch (Exception exception) {
		        logger.log(Level.SEVERE, "QoSDescriptor:\n" + qosDescriptor + "\n\n\n");
		        logger.log(Level.SEVERE, "RequestDescriptor:\n" + requestDescriptor + "\n\n\n");
				throw (new RemoteException("JAXB Validation or initial descriptor check error (see details below)!", exception));
			}
				
	        logger.log(Level.FINEST, "Parameter: Given validated (JAXB) QoS Descriptor:\n" + qosDesc.toString() + "\n\n");
	        logger.log(Level.FINEST, "Parameter: Given validated (JAXB) Request Descriptor:\n" + reqDesc.toString() + "\n\n");

			/////////////////////////////////////////
			// PHASE 2: RETRIEVING DETAILS
			/////////////////////////////////////////

			details = this.getDetailsForOffering(qosDesc, reqDesc);

			logger.log(Level.FINER, "Details of current invocation (before offering):\n" + details + "\n");

			/////////////////////////////////////////
			// PHASE 3: CREATING NEW OFFER
			/////////////////////////////////////////

			offer = this.createOffer(details, qosDesc, reqDesc);

			logger.log(Level.FINER, "Details of current invocation (after offering):\n" + details + "\n");

			/////////////////////////////////////////
			// PHASE 4: UPDATING DETAILS AND RETURN
			/////////////////////////////////////////

			detailsRepository.setDetails(details);
			
			logger.log(Level.FINEST, "Repository: " + detailsRepository);

	        logger.log(Level.FINE, "Finished.\n");

			if (offer != null) {
				logger.log(Level.FINEST, "Returning QoS Descriptor:\n" + offer.toString() + "\n\n");

				return (offer.toString());
			}

			logger.log(Level.WARNING, "Returning NO QoS Descriptor!\n");
		}
		catch (Exception exception) {
			throw this.handleException(exception, details);
		}
    	return null;
    }

	final private void initialQoSDescCheck(QoSDescriptor qosDesc) throws Exception {
		
		Calendar now = Calendar.getInstance();
	
        double reqPrice = qosDesc.getObjectivePrice();
        Calendar reqBegin = qosDesc.getObjectiveBeginTime();
        Calendar reqEnd = qosDesc.getObjectiveEndTime();

		if (reqPrice < 0) {
			
	        logger.log(Level.FINEST, "Given price: " + reqPrice +"\n");
			throw (new Exception("Given QoS Descriptor specified a price below zero!"));
		}
		if (!reqEnd.after(now)) {

	        logger.log(Level.FINEST, "Given end: " + formatCalendar(reqEnd) +"\n");
	        logger.log(Level.FINEST, "Now:         " + formatCalendar(now) +"\n");
			throw (new Exception("Given QoS Descriptor specified a objective end time in the past!"));
		}
		if (!reqEnd.after(reqBegin)) {
	        logger.log(Level.FINEST, "Given begin: " + formatCalendar(reqBegin) +"\n");
	        logger.log(Level.FINEST, "Given end:   " + formatCalendar(reqEnd) +"\n");
			throw (new Exception("Given QoS Descriptor specified a objective end time before the begin time!"));
		}
	}
	
	final private void initialReqDescCheck(RequestDescriptor reqDesc) throws Exception {
	
		// to do: check with fields specified in the app desc
	}

	final private Details getDetailsForOffering(QoSDescriptor qosDesc, RequestDescriptor reqDesc) throws Exception {

		Details details = null;
		
		String cid = this.getIdFromSLAName(qosDesc.getSLAName());

		// if cid could be extracted from SLA name of the given QoS Descriptor
		// check if Details object could be read from repository
		
		if (cid != null) {
		
			details = detailsRepository.getDetails(cid);

		}
		// if details object is (still) null create a new one
		// (either of no cid could found or no details on given cid)
		
		if (details == null) {

			cid = IDGenerator.getSessionId();
			qosDesc.setSLAName(qosDesc.getSLAName() + cid);
	        logger.log(Level.FINE, "Generated new session id!\n");

			details = new Details();

			IdDetails initialIdDetails = new IdDetails();
			initialIdDetails.setCId(cid);
			details.setDetails(StateConstants.IDDETAILS, initialIdDetails);
		}

		// retrieve state details (if null, create new and set in overall details)

		StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
		
		if (stateDetails == null) {
			stateDetails = new StateDetails();	
			details.setDetails(StateConstants.STATEDETAILS, stateDetails);
		}
		
		// retrieve QoS details (if null, create new and set in overall details)

		QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);

		if (qosDetails == null) {
			qosDetails = new QoSDetails();	
			details.setDetails(StateConstants.QOSDETAILS, qosDetails);
		}

		// set initial OFFERING state and both input descriptors

		stateDetails.setState(StateConstants.OFFERING);
		qosDetails.addInputDescriptors(qosDesc.toString(), reqDesc.toString());

		// save all details in repository

		detailsRepository.setDetails(details);

		//logger.log(Level.FINER, "Details of current invocation (before offering):\n" + details + "\n");

		return details;
	}
	
	final private QoSDescriptor createOffer(Details details, QoSDescriptor qosDesc, RequestDescriptor reqDesc) throws Exception {
		
		String cid = ((IdDetails)details.getDetails(StateConstants.IDDETAILS)).getCId();

        logger.log(Level.FINE, cid + " Started.\n");

		// preliminary offer data (containing jobId, begin, end and price)

        OfferData preliminaryOffer = null;

		// counter object for different loops

        int count = 0;
        
        // get requested values for price, begin and end
        
        double reqPrice = qosDesc.getObjectivePrice();
        Calendar reqBegin = qosDesc.getObjectiveBeginTime();
        Calendar reqEnd = qosDesc.getObjectiveEndTime();

        logger.log(Level.FINER, cid + " QoSDescriptor - requested price: " + reqPrice + "\n");
        logger.log(Level.FINER, cid + " QoSDescriptor - requested begin: " + formatCalendar(reqBegin) + "\n");
        logger.log(Level.FINER, cid + " QoSDescriptor - requested end:   " + formatCalendar(reqEnd) + "\n");

		// do some begin and end correction (round up begin and round down end) if necessary

        if ((reqBegin.getTimeInMillis() % 1000) > 0) {
    		long correction = (reqBegin.getTimeInMillis()/1000 + 1)*1000;
    		reqBegin.setTimeInMillis(correction);
            logger.log(Level.FINER, cid + " QoSDescriptor - corr. requested begin: " + formatCalendar(reqBegin) + "\n");
        }
        if ((reqEnd.getTimeInMillis() % 1000) > 0) {
    		long correction = (reqEnd.getTimeInMillis()/1000)*1000;
    		reqEnd.setTimeInMillis(correction);
            logger.log(Level.FINER, cid + " QoSDescriptor - corr. requested end:   " + formatCalendar(reqEnd) + "\n");
        }

        // calculate requested interval (max interval)
        
        long reqMaxInterval =  reqEnd.getTimeInMillis() - reqBegin.getTimeInMillis();
        
        // check allowed number of nodes and do some logging
        
        StringBuffer allowedNodesStringBuffer = 
        	new StringBuffer("Application Descriptor - allowed nodes: ");

		for (count = 0; count < allowedNodes.length; count++) {
	        allowedNodesStringBuffer.append(allowedNodes[count]);
			
			if ((count + 1) < allowedNodes.length) {
		        allowedNodesStringBuffer.append(", ");
			}
		}
        logger.log(Level.FINE, cid + " " + allowedNodesStringBuffer.toString() + "\n");

		/////////////////////////////////////////
		// MAIN LOOP for allowed number of nodes
		/////////////////////////////////////////

		for (count = 0; this.moreIterations(count, allowedNodes.length, preliminaryOffer); count++) {

	        logger.log(Level.FINE, cid + " Next attempt: " + allowedNodes[count] + " nodes.\n");

			// create machine descriptor (input for performance model)

			int currentNumberOfNodes = allowedNodes[count];
			String machineDesc = new String("<machine-descriptor><number-of-nodes>" + 
									currentNumberOfNodes + "</number-of-nodes></machine-descriptor>");
			
			// Invoke application performance model to estimate 
			// runtime, etc. for given request and QoS Descriptor 
			
			PerformanceDescriptor perfDesc = null;
			String perfModelOutput = null;
			
			try {
				perfModelOutput = perfModel.getPerformanceEstimate(reqDesc.toString(), machineDesc);
				
				perfDesc = new PerformanceDescriptorImpl(perfModelOutput);
				
				logger.log(Level.FINEST, cid + " Perf-Desc:\n" + perfDesc + "\n");

			}
			catch (Exception perfModelException) {
				
				logger.log(Level.SEVERE, "\nAn Error occured while talking to the Application Performance Model!\n" + 
					"This service uses a " + perfModel + "!\nPerformanceModel Input:\nRequestDescriptor:\n" + reqDesc + "\n" +
					"MachineDescriptor:\n" + machineDesc + "\n\n + PerformanceModel Output:\n" + perfModelOutput + "\n");
				throw perfModelException;
			}
				
			double estimatedRuntime = perfDesc.getRuntime();
			
	        logger.log(Level.FINE, cid + " Estimated runtime for " + currentNumberOfNodes + " nodes is: " + estimatedRuntime + " seconds.\n");

			// if estimated runtime (seconds) is smaller than requested max interval
			// try to do a resource reservation in the compute resource manager

			if ((estimatedRuntime*1000) < reqMaxInterval) {

				int runtimeInMins = (int) (estimatedRuntime/60)+1;

		        logger.log(Level.FINE, cid + " Estimated runtime is less than requested maximum interval.\n");
		        logger.log(Level.FINE, cid + " Trying to get a temp. reservation from CRM with nodes: " + currentNumberOfNodes + 
		        						", interval begin: " + formatCalendar(reqBegin) + ", interval end: " + formatCalendar(reqEnd) + 
		        						", and runtime: " + runtimeInMins + " mins\n");

				long jid = crm.getTempReservation(currentNumberOfNodes, runtimeInMins, 
					new Date(reqBegin.getTimeInMillis()), new Date(reqEnd.getTimeInMillis()), 
					new Date(System.currentTimeMillis() + TEMP_RESERVATION_TIME));

		        logger.log(Level.FINE, cid + " Compute resource manager returned: " + jid + "\n");


				// Get data from reservation (containing: begin, end, number of nodes)

		        JobInfo currentJobInfo = crm.getJobInfo(jid);

				// check the returned jid is associated with a valid
				// temporary reservation

		        if (this.checkJId(jid, currentJobInfo)) {


			        logger.log(Level.FINE, cid + " CRM returned jobInfo:\n" + currentJobInfo + "\n");
		        	
					Calendar jobBegin = Calendar.getInstance();
					jobBegin.setTime(currentJobInfo.startTime);

					Calendar jobEnd = Calendar.getInstance();
					if (currentJobInfo.endTime != null) {
						jobEnd.setTime(currentJobInfo.endTime);
					}
					else {
				        
						jobEnd.setTime(new Date((long)(currentJobInfo.startTime.getTime() + estimatedRuntime*1000)));
					}

					int jobNodes = currentJobInfo.numNodes;
					
					// Calculate price with given business model based on 
					// begin, end and number of nodes for this job
					
					// NOTE: Maybe this will be replaced by a more general
					// business model (e.g. getPrice(QOSDescriptor or WSLA))
					// which may cause much more handling here, because the 
					// given (initial) QoSDescriptor (WSLA) has to be clonded
					// and manipulated. Question remain: Do the business 
					// model have to know the number of nodes? IMHO: YES
					
					double jobPrice = businessModel.getPrice(jobBegin, jobEnd, jobNodes);
		        	
		        	// Either there is no preliminary offer yet (e.g. first attempt) or
		        	// the price of a previous (preliminary) offer is higher then the
		        	// price of the current job-offer 
		        	
		        	if ((preliminaryOffer == null) || (jobPrice < preliminaryOffer.getPrice())){
		        		
		        		// create a new preliminary offer
		        		
						preliminaryOffer = new OfferData();
						preliminaryOffer.setJobId(jid);
						preliminaryOffer.setPrice(jobPrice);
						preliminaryOffer.setBegin(jobBegin);
						preliminaryOffer.setEnd(jobEnd);

				        logger.log(Level.FINE, cid + " New priliminary offer: " + preliminaryOffer.toString() + "\n");
					}
					else {
						// If a previous offer was not underbidden, cancel the 
						// current reservation for resources

				        logger.log(Level.FINE, cid + " Former priliminary offer was not underbidden (new vs. old price: " + jobPrice + " vs. " + preliminaryOffer.getPrice() + ") - cancel reservation!\n");
						crm.cancelReservation(jid);
					}
		        }
		        else {
			        logger.log(Level.FINE, cid + " No reservation could be made within the requested interval for the estimated runtime!\n");
		        }
		        
			}			
			else {
		        logger.log(Level.FINE, cid + " Estimated runtime does NOT match with requested interval!\n");
			}				
				
		}

        logger.log(Level.FINE, cid + " End of internal scheduler reservation round ... \n");

		QoSDescriptor finalOffer = null;

        if (preliminaryOffer != null) {
        
        	finalOffer = qosDesc; //new QoSDescriptorImpl(qosDesc.toString());
        	
        	finalOffer.setObjectivePrice(preliminaryOffer.getPrice());
        	finalOffer.setObjectiveBeginTime(preliminaryOffer.getBegin());
        	finalOffer.setObjectiveEndTime(preliminaryOffer.getEnd());
        	
        	//this.dbHandling("add", cid, preliminaryOffer.getJobId(), cid, finalOffer);
        	
        	// Updating details
        	
        	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
        	idDetails.setJobId(preliminaryOffer.getJobId());
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
        	stateDetails.setState(StateConstants.OFFERED);
        	QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);
        	qosDetails.addOutputDescriptor(finalOffer.toString());

	        logger.log(Level.FINE, cid + " Offered QoSDescriptor - requested price: " + preliminaryOffer.getPrice() + "\n");
	        logger.log(Level.FINE, cid + " Offered QoSDescriptor - requested begin: " + formatCalendar(preliminaryOffer.getBegin()) + "\n");
	        logger.log(Level.FINE, cid + " Offered QoSDescriptor - requested end:   " + formatCalendar(preliminaryOffer.getEnd()) + "\n");
		}
		else {
			
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
        	stateDetails.setState(StateConstants.OFFERINGFAILED);
        	
	        logger.log(Level.INFO, cid + " System was not able to provide an appropriate offer to the client's request!\n");
		}			


		return finalOffer;
	}

	private boolean moreIterations(int count, int allowedNodesLength, OfferData offerData) {
	
		if (count < allowedNodesLength) {	// if more nodes could be checked

			if (offerData != null) {	// if an offer can be made with prev. number of nodes
			
				if (checkAllNumbersOfNodes) {	// if all number should be checked 
					
					return true;	// more tries
				}
				return false;	// go with prev offer
			}
			return true;	// try more numbers of nodes
		}				
		return false;	// no more number of nodes: go with prev. offer (if there)
	}

	private boolean checkJId(long jid, JobInfo jobInfo) throws Exception {
	
		// COSY Scheduler returns a jid but no jobInfo.jid if no reservation could be made
		// TINY Scheduler returns -1L if no reservation could be made
		// Dummy Test Scheduler returns -1L if no reservation could be made  

		if (jid <= 0L) {
			return false;
		}
		if (jobInfo == null) {
			return false;
		}
		if (jobInfo.jobId != jid) {
			return false;
		}
		return true;
	}


	///////////////////////////////////////////////////////////////////////////
	// CONFIRM QOS DESCRIPTOR 
	///////////////////////////////////////////////////////////////////////////

    public String confirmQoSDescriptor(String qosDescriptor) throws RemoteException {

		Details details = null;

		try {
	        logger.log(Level.FINE, "Started.\n");
	        logger.log(Level.FINEST, "Parameter: Given (String) QoS Descriptor:\n" + qosDescriptor + "*\n\n");
	
			QoSDescriptor qosDesc = null;
	
			//////////////////////////////////////////
			// PHASE 1: INITIAL CHECKING OF QOS DESC
			//////////////////////////////////////////

			try {
				qosDesc = new QoSDescriptorImpl(qosDescriptor);

			}
			catch (Exception validationException) {
		        logger.log(Level.SEVERE, "QoSDescriptor:\n" + qosDescriptor + "\n\n\n");
				throw (new RemoteException("Given QoS Descriptor could not be validated against Schema (see details below)!", validationException));
			}

			///////////////////////////////////////////////
			// PHASE 2: RETRIEVE DETAILS BASED ON QOS DESC
			///////////////////////////////////////////////

			// retrieve details object for given QoS Descriptor or throw Exception

			details = this.getDetailsFromDB(qosDesc);

			// retrieve sub-details
			
        	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
        	QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);
        	
        	// set CONFIRMING state

        	this.checkAndSetState(details, StateConstants.CONFIRMING);
        	//stateDetails.setState(StateConstants.CONFIRMING);

			// check if details contain a valid descriptor to confirm

			this.checkDetails(details, qosDesc);

	        logger.log(Level.FINE, "Given QoS Descriptor is valid, try confirm temporary CRM reservation.\n");
				
			///////////////////////////////////////////////
			// PHASE 3: CONFIRMATION OF TEMP RESERVATION
			///////////////////////////////////////////////

            boolean confirmed = crm.confirmReservation(idDetails.getJobId());
	
			if (confirmed) {
					
		        logger.log(Level.FINE, "CRM confirms temporary reservation!\n");
		        
    	    	stateDetails.setState(StateConstants.CONFIRMED);
    	    	qosDetails.setConfirmedDescriptor(qosDescriptor);
    	    	
				detailsRepository.setDetails(details);

		        logger.log(Level.FINE, "Finished.\n");
				return qosDescriptor;
			}

			throw (new RemoteException("Internal temporary reservation could not be confirmed by CRM!"));

        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
	}

	///////////////////////////////////////////////////////////////////////////
	// CANCEL QOS DESCRIPTOR 
	///////////////////////////////////////////////////////////////////////////

    public void cancelQoSDescriptor(String qosDescriptor) throws RemoteException {

		Details details = null;

		try {
	        logger.log(Level.FINE, "Started.\n");
	        logger.log(Level.FINEST, "Parameter: Given (String) QoS Descriptor:\n" + qosDescriptor + "*\n\n");
	
			QoSDescriptor qosDesc = null;
	
			//////////////////////////////////////////
			// PHASE 1: INITIAL CHECKING OF QOS DESC
			//////////////////////////////////////////

			try {
				qosDesc = new QoSDescriptorImpl(qosDescriptor);
			}
			catch (Exception validationException) {
		        logger.log(Level.SEVERE, "QoSDescriptor:\n" + qosDescriptor + "\n\n\n");
				throw (new RemoteException("Given QoS Descriptor could not be validated against Schema (see details below)!", validationException));
			}

			///////////////////////////////////////////////
			// PHASE 2: RETRIEVE DETAILS BASED ON QOS DESC
			///////////////////////////////////////////////

			// retrieve details object for given QoS Descriptor or throw Exception
			
			details = this.getDetailsFromDB(qosDesc);

			// retrieve sub-details
			
        	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
        	QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);
        	
        	// set CANCELING state

        	//stateDetails.setState(StateConstants.CANCELING);
        	this.checkAndSetState(details, StateConstants.CANCELING);

			// check if details contain a valid descriptor to confirm

			this.checkDetails(details, qosDesc);

	        logger.log(Level.FINE, "Given QoS Descriptor is valid, try to cancel temporary CRM reservation.\n");
	        
			///////////////////////////////////////////////
			// PHASE 3: CANCELING OF TEMP RESERVATION
			///////////////////////////////////////////////

            boolean confirmed = crm.cancelReservation(idDetails.getJobId());
	
			if (confirmed) {
						
		        logger.log(Level.FINE, "CRM cancels temporary reservation!\n");
		        
    	    	stateDetails.setState(StateConstants.CANCELED);
    	    	//qosDetails.addCanceledDescriptor(qosDescriptor); //?? should we do this
			}
			else { 
    	    	stateDetails.setState(StateConstants.CANCELED);
		        logger.log(Level.WARNING, "CRM has not removed temporary reservation!\n");
			} 
			detailsRepository.setDetails(details);
	        logger.log(Level.FINE, "Finished.\n");
			return;

        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
    }


	///////////////////////////////////////////////////////////////////////////
	// SET JOB-CONV-ID
	///////////////////////////////////////////////////////////////////////////

	public void setJobConvId(String qosDescriptor, String cid) throws RemoteException {
	
		Details details = null;

		try {
	        logger.log(Level.FINE, "Started.\n");
	        logger.log(Level.FINEST, "Parameter: Given (String) QoS Descriptor:\n" + qosDescriptor + "\nJobConvId/cid: " + cid + "\n");
	
			QoSDescriptor qosDesc = null;
	
			//////////////////////////////////////////
			// PHASE 1: INITIAL CHECKING OF QOS DESC
			//////////////////////////////////////////

			try {
				qosDesc = new QoSDescriptorImpl(qosDescriptor);
			}
			catch (Exception validationException) {
		        logger.log(Level.SEVERE, "QoSDescriptor:\n" + qosDescriptor + "\n\n\n");
				throw (new RemoteException("Given QoS Descriptor could not be validated against Schema (see details below)!", validationException));
			}

			///////////////////////////////////////////////
			// PHASE 2: RETRIEVE DETAILS BASED ON QOS DESC
			///////////////////////////////////////////////

			// retrieve details object for given QoS Descriptor or throw Exception
			
			details = this.getDetailsFromDB(qosDesc);

			// retrieve sub-details
			
        	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
        	
        	// set CHANGINGCID state

        	//stateDetails.setState(StateConstants.CHANGINGCID);
        	this.checkAndSetState(details, StateConstants.CHANGINGCID);

			// check if details contain a valid descriptor to confirm

			this.checkDetails(details, qosDesc);

	        logger.log(Level.FINE, "Given QoS Descriptor is valid, try set given cid.\n");

			///////////////////////////////////////////////
			// PHASE 3: SETTING OF SPECIFIC JOB CONV ID
			///////////////////////////////////////////////

			idDetails.setCId(cid);

        	this.checkAndSetState(details, StateConstants.CHANGEDCID);

			detailsRepository.setDetails(details);

			return;

        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
	}
	
	final private void checkAndSetState(Details details, String newState) throws Exception {
		
       	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
		String previousState = stateDetails.getState();
		
		if ((newState.equals(StateConstants.CONFIRMING)) || 
			(newState.equals(StateConstants.CANCELING))) {
			
			if (!previousState.equals(StateConstants.OFFERED)) {
				
		        logger.log(Level.WARNING, "New State: " + newState + " is not allowed after state " + previousState + ".\n");
				throw (new Exception("State change exception! It is not allowed to change the state from " + previousState + " to " + newState + "!"));
			}
		}

		stateDetails.setState(newState);
		
		

//		if (qosDetails.getConfirmedDescriptor() != null) {
//		
//	        logger.log(Level.FINEST, "Given QoS Descriptor:\n" + qosDesc +"\nGiven Details:\n" + details + "\n");
//			throw (new Exception("(This) Client has already confirmed a QoS Descriptor!"));
//		}
		
		
	}

	// checkDetails is invoked by confirm|cancelQoSDescriptor and setJobConvId

	final private void checkDetails(Details details, QoSDescriptor qosDesc) throws Exception {

		// retrieve sub-details

       	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
       	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);
       	QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);

		if (stateDetails.getState().equals(StateConstants.CHANGINGCID)) {
			
			TSDescriptor confirmedTSDesc = qosDetails.getConfirmedDescriptor();
			QoSDescriptor confirmedDesc = new QoSDescriptorImpl(confirmedTSDesc.getDescriptor());
					
			if (qosDesc.equals(confirmedDesc)) {
				return;
			}
		}
		else {
			Iterator offeredQoSDescriptors = qosDetails.getOutputDescriptors().iterator();
				
			while (offeredQoSDescriptors.hasNext()) {
				
				TSDescriptor next = (TSDescriptor)offeredQoSDescriptors.next();
				QoSDescriptor current = new QoSDescriptorImpl(next.getDescriptor());
					
				if (qosDesc.equals(current)) {
					return;
				}
			}
		}
        logger.log(Level.FINEST, "Given QoS Descriptor:\n" + qosDesc +"\nGiven Details:\n" + details + "\n");
		throw (new Exception("The given QoS Descriptor was never offered to this client!"));
	}


	// OTHER SHARED METHODS (by requestQoSDescriptor, confirm|cancelQoSDescriptor and setJobConvId)
	
	final private String getIdFromSLAName(String slaName) {
		
        //logger.log(Level.FINE, "Started with SLA name: " + slaName + ".\n");
		String id = null;
		
		try {
			int stringlengthOfToday = Long.toString(System.currentTimeMillis()).length();
	        //logger.log(Level.FINE, "Length of current time (in millis): " + stringlengthOfToday + ".\n");
		
			if ((slaName != null) && (slaName.lastIndexOf("-") != -1) && (slaName.lastIndexOf("-") >= stringlengthOfToday)) {
				
				id = slaName.substring(slaName.lastIndexOf("-") - stringlengthOfToday);
				
				logger.log(Level.FINEST, "Extracted id: " + id + " from SLA name: " + slaName + ".\n");
				
				if ((id.lastIndexOf("-") >= 0) && (id.length() >= 1)) {
				
					// check if extracted id looks like a valid id xxxx-yy
					// where xxxx is a long number (milliseconds since 1.1.70 GMT
					// and yy is an integer number counting generated ids (number of services)
				
					Long.parseLong(id.substring(0, id.lastIndexOf("-") - 1));
					Integer.parseInt(id.substring(id.lastIndexOf("-") + 1));
				}
			}
		}
		catch (Exception exception) {
			
	        logger.log(Level.FINER, "Id could not be extracted from SLA name!");
			id = null;
		}
		return id;	
	}			

	final private Details getDetailsFromDB(QoSDescriptor qosDesc) throws Exception {

		Details details = null;
		
		String cid = this.getIdFromSLAName(qosDesc.getSLAName());

		// if cid could be extracted from SLA name of the given QoS Descriptor
		// check if Details object could be read from repository
		
		if (cid != null) {
		
			details = detailsRepository.getDetails(cid);

		}
		else {
	        logger.log(Level.FINEST, "Given QoS Descriptor:\n" + qosDesc +"\n");
			throw (new Exception("Given QoS Descriptor does not specify an internal CId!"));
		}
			
		// do some checks
		
		if ((details == null) ||
			(details.getDetails(StateConstants.STATEDETAILS) == null) ||
			(details.getDetails(StateConstants.IDDETAILS) == null)) {

		        logger.log(Level.FINEST, "Given QoS Descriptor:\n" + qosDesc +"\nGiven CId: " + cid + "\nDetails:\n" + details + "\n");
				throw (new Exception("No matching details to given QoS Descriptor could be found in local database!"));
		}
		logger.log(Level.FINER, "Details of from local database:\n" + details + "\n");

		return details;
	}

	final private void setErrorState(Details details) {
	
		// TODO: Write error state in details and repository!	
		// Includes:
		// - check state
		// - check qos desc (equal size of IN and OUT descs)
		// - ...
	}

	final private RemoteException handleException(Exception exception, Details details) {

		if (exception == null) {
	        logger.log(Level.WARNING, "Given exception is null!\n");
	        return (new RemoteException("Given exception is null!"));
		}

        logger.log(Level.SEVERE, "Details (before error state is set):\n" + details + "\n");

		//TODO: set an error in details and save details!
        this.setErrorState(details);
	        
        StringBuffer severeMsg = new StringBuffer();

        severeMsg.append("An error occured (see details below)!\n");

        if (exception.getMessage() != null) {
	        severeMsg.append("Message: " + exception.getMessage() + "\n");
        }

        if (exception.getCause() != null) {
	        severeMsg.append("Cause: " + exception.getCause() + "\n");
        }
		logger.log(Level.SEVERE, severeMsg.toString(), exception);
		return (new RemoteException("An error occured (see details below)!", exception));
	}


	///////////////////////////////////////////////////////////////////////////
	// SET APP HANDLER
	///////////////////////////////////////////////////////////////////////////

    public void setAppHandler(AppHandler appHandler) {
    	
    	this.appHandler = appHandler;	
    	
    }

	// METHODS from app handler ---- begin


	public void upload(String id, byte[] file) throws java.rmi.RemoteException {
	
		this.doUploadQoS(id);
		
		appHandler.upload(id, file);
	}
	
    public void uploadAttachment(String cid, DataHandler dataHandler) throws RemoteException {
    	
		this.doUploadQoS(cid);
		
    	appHandler.uploadAttachment(cid, dataHandler);
    }

	public void uploadString(String cid, String data) throws RemoteException {
   	
		this.doUploadQoS(cid);
		
   		appHandler.uploadString(cid, data);
	}

	private void doUploadQoS(String cid) throws java.rmi.RemoteException {
		
		Details details = null;

    	try {	    	
	        logger.log(Level.INFO, "" + cid + " - Started.\n");
	        
 			details = detailsRepository.getDetails(cid);

			if ((details == null) || (details.getDetails(StateConstants.QOSDETAILS) == null)) {
				
				// if no details could be retrieved from repository or the retrieved
				// details have no internal QoS details, the client
				// has not agreed certain QoS criteria in advance which means 
				// that the client simply uploads its data without QoS constraints:
				// Therefore a new details object is generated
				
				details = new Details();

				IdDetails initialIdDetails = new IdDetails();
				initialIdDetails.setCId(cid);
				details.setDetails(StateConstants.IDDETAILS, initialIdDetails);

				StateDetails initialStateDetails = new StateDetails();
		
				details.setDetails(StateConstants.STATEDETAILS, initialStateDetails);
			}
			else {

				// if a details object could be retrieved from repository the
				// current time is checked against the begin time in the agreed 
				// QoS Descriptor (if available) to see if the client has 
				// uploaded its input data on time

       			QoSDetails qosDetails = (QoSDetails)details.getDetails(StateConstants.QOSDETAILS);

				TSDescriptor confirmedTSDesc = qosDetails.getConfirmedDescriptor();
				
				if ((confirmedTSDesc == null) || (confirmedTSDesc.getDescriptor() == null)) {
					
			        logger.log(Level.FINER, "No agreed/confirmed QoS Descriptor could be found for the given cid: " + cid + ".\n");
					throw (new Exception("No agreed/confirmed QoS Descriptor could be found for the given cid: " + cid + ".\n"));
				}

				QoSDescriptor confirmedDesc = new QoSDescriptorImpl(confirmedTSDesc.getDescriptor());
					
				if (Calendar.getInstance().after(confirmedDesc.getObjectiveBeginTime())) {

			        logger.log(Level.FINER, "Agreed objective begin time: " + this.formatCalendar(confirmedDesc.getObjectiveBeginTime()) 
			        						+ " vs. current time: " + this.formatCalendar(Calendar.getInstance()) + ".\n");
			        logger.log(Level.SEVERE, "Upload has been performed too late (not accordingly to the agreed QoS Descriptor).\n");
					throw (new Exception("Upload has been performed too late (not accordingly to the agreed QoS Descriptor).\n"));
				}
		        logger.log(Level.FINER, "Upload has been performed accordingly to the agreed QoS Descriptor.\n");
			}

   			this.checkAndSetState(details, StateConstants.UPLOADED);

			detailsRepository.setDetails(details);
			
        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
	}
	


	public byte[] download(String cid, String fileName) throws RemoteException {
		
		return appHandler.download(cid, fileName);
	}

    public DataHandler downloadAttachment(String cid, String fileName) throws RemoteException {
    	
    	return appHandler.downloadAttachment(cid, fileName);
    }

    public String downloadString(String cid, String fileName) throws RemoteException {
    	
    	return appHandler.downloadString(cid, fileName);
    }

	public void start(String cid) throws java.rmi.RemoteException {
		
		Details details = null;

    	try {	    	
	        logger.log(Level.INFO, "" + cid + " - Started.\n");
	        
 			details = detailsRepository.getDetails(cid);
	       	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
        	StateDetails stateDetails = (StateDetails)details.getDetails(StateConstants.STATEDETAILS);

	        String startCommand = appDesc.getJobScript();
	        String sessionDirectory = appDesc.getWorkingDirectory() + File.separatorChar + String.valueOf(cid);
	        String outputFileName = appDesc.getOutputFileName();
	        String inputFileName = appDesc.getInputFileName();
	        String finishFileName = appDesc.getFinishFileName();
	
			String cmdLine = new String(startCommand + " " + sessionDirectory + " " + inputFileName + " " + outputFileName + " " + finishFileName);
		    
		    long jobId = idDetails.getJobId();

			if (jobId > 0) {

			    logger.log(Level.CONFIG, "" + cid + " - jobId: " + jobId + " Submitting: " + cmdLine + "\n");
				
				crm.insertBatchScript(jobId, cmdLine);
					
       			this.checkAndSetState(details, StateConstants.INSERTED);
			}
			else {

			    logger.log(Level.INFO, "" + cid + " - Assuming service usage with no QoS support, submitting plain job-script to CRM!\n");
	
				long subJobId = crm.makeSubmission(DEFAULT_NODES_FOR_SUBMISSION, DEFAULT_MINUTES_FOR_SUBMISSION, cmdLine);
	
				idDetails.setJobId(subJobId);
	
			    logger.log(Level.CONFIG, "" + cid + " - Submitted: " + cmdLine + "\n");
			    logger.log(Level.FINE, "" + cid + " - JobInfo:\n" + crm.getJobInfo(subJobId) + "\n");

       			this.checkAndSetState(details, StateConstants.SUBMITTED);
			}

			detailsRepository.setDetails(details);

        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
	}

	public void kill(String id) throws java.rmi.RemoteException {
		
		appHandler.kill(id);
	}

	public byte[] getStatus(String id) throws java.rmi.RemoteException {
		
		Details details = null;

    	try {	    	
	        logger.log(Level.INFO, "" + id + " - Started.\n");
	        
	        String appHandlerStatus = new String(appHandler.getStatus(id));
	        
	        if (appHandlerStatus.equals("FINISHED")) {
	        	
	        	return (appHandlerStatus.getBytes());
	        }
	        
	        StringBuffer status = new StringBuffer();
	        status.append("<app-status>\n");
	        status.append(appHandlerStatus + "\n");
	        status.append("</app-status>\n");
	        
 			details = detailsRepository.getDetails(id);
	       	IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);

		    long jobId = idDetails.getJobId();

			if (jobId > 0) {

				JobInfo jobInfo = crm.getJobInfo(jobId);
					
		        if (jobInfo != null) {
		        
			        status.append("<job-status>\n");
	
					if ((jobInfo.status >= 0) && (jobInfo.status <= 7)) {
	
				        logger.log(Level.FINE, id + " - getJobStatus: " + jobInfo.statusInfo[jobInfo.status] + ".\n");
				        status.append(jobInfo.statusInfo[jobInfo.status] + "\n");
					}
					else {
				        logger.log(Level.FINE, id + " - getJobStatus: " + jobInfo.status + ".\n");
				        status.append(jobInfo.status + "\n");
					}
	
	    	        status.append("</job-status>\n");
				}
				else {
			        logger.log(Level.WARNING, id + " - Could not retrieve job-information for JobId: " + jobId + ".\n");
				}
			}

			return (status.toString().getBytes());

        } catch (Exception exception) {
			throw this.handleException(exception, details);
        }
	}

    public String getStatusAsString(String id) throws java.rmi.RemoteException {
    	
		return appHandler.getStatusAsString(id);
	}    	
    
	public void acknowledgeResults(String id) throws RemoteException {
		
		appHandler.acknowledgeResults(id);
	}
	
	public String getCId() throws java.rmi.RemoteException {
		
		return appHandler.getCId();
	}

	// METHODS from app handler ---- end
    

/*

	private synchronized AgreementDetails dbHandling(String command, String qosId, long jobId, String cid, QoSDescriptor offer) throws RemoteException {
		
		if (command.equals("add")) {
			
			AgreementDetails details = new AgreementDetails();
			details.setQoSDescriptor(offer);
			details.setJobConvId(jobConvId);
			details.setJobId(jobId);
			
			offers.put(qosId, details);
			return details;
		} 
		else if (command.equals("check")) {
			
			Iterator ids = offers.keySet().iterator();
			String slaId = this.getIdFromSLAName(offer.getSLAName());
			
			logger.log(Level.FINEST, "Looking for slaId: " + slaId + "\n");
			
			while (ids.hasNext()) {
				
				String currentId = (String) ids.next();
				
				logger.log(Level.FINEST, "Next Id in DB: " + currentId + "\n");
				
				if (currentId.equals(slaId)) {
					
					AgreementDetails details = (AgreementDetails)offers.get(currentId);
					
					if (offer.equals(details.getQoSDescriptor())) {
						
						return details; // correct (return key of db/hashtable)
					}
					else {

						logger.log(Level.WARNING, "Given QoS Descriptor does not match to the one in the local database (Manipulated?)!");

						this.printComparison(details.getQoSDescriptor(), offer);

						throw (new RemoteException("Given QoS Descriptor does not match to the one in the local database (Manipulated?)!"));
					}
				}
			}
			logger.log(Level.WARNING, "Given QoS Descriptor not available in database!");
			logger.log(Level.FINEST, "Given QoS Descriptor:\n" + offer + "\n");
			throw (new RemoteException("Given QoS Descriptor not available in database!"));
		}
		else if (command.equals("remove")) {
			
			Iterator ids = offers.keySet().iterator();
			String slaId = this.getIdFromSLAName(offer.getSLAName());
			
			logger.log(Level.FINEST, "Looking for slaId: " + slaId + "\n");
			
			while (ids.hasNext()) {
				
				String currentId = (String) ids.next();
				
				logger.log(Level.FINEST, "Next Id in DB: " + currentId + "\n");
				
				if (currentId.equals(slaId)) {
					
					AgreementDetails details = (AgreementDetails)offers.get(currentId);
					
					if (offer.equals(details.getQoSDescriptor())) {
						
						return (AgreementDetails)offers.remove(currentId);
					}
					else {

						logger.log(Level.WARNING, "Given QoS Descriptor does not match to the one in the local database (Manipulated?)!");

						this.printComparison(details.getQoSDescriptor(), offer);

						throw (new RemoteException("Given QoS Descriptor does not match to the one in the local database (Manipulated?)!"));
					}
				}
			}
			logger.log(Level.WARNING, "Given QoS Descriptor not available in database!");
			logger.log(Level.FINEST, "Given QoS Descriptor:\n" + offer + "\n");
			throw (new RemoteException("Given QoS Descriptor not available in database!"));
		}
		else if (command.equals("getDetailsByJobConvId")) {
			
			logger.log(Level.FINEST, "getDetailsByJobConvId:" + jobConvId + " -  begin!\n");

			Iterator ids = offers.keySet().iterator();
			
			while (ids.hasNext()) {
				
				String currentId = (String) ids.next();
				
				AgreementDetails details = (AgreementDetails)offers.get(currentId);
					
				logger.log(Level.FINEST, "Next Id in DB: " + currentId + ": " + details.toString() + "\n");

				if ((details.getJobConvId() != null) && (details.getJobConvId().equals(jobConvId))) {
					
					return details;
				}
			}
			logger.log(Level.WARNING, "Given jobConvId is not available in the database!");
			logger.log(Level.FINEST, "Given jobConvId:" + jobConvId + "\n");
			//throw (new RemoteException("Given jobConvId is not available in the database!"));
			return null;

		} 
		else if (command.equals("checkAndSetJobConvId")) {
			
			logger.log(Level.FINEST, "setJobConvId:" + jobConvId + " -  begin!\n");

			Iterator ids = offers.keySet().iterator();
			String slaId = this.getIdFromSLAName(offer.getSLAName());
			
			logger.log(Level.FINEST, "Looking for slaId: " + slaId + "\n");
			
			while (ids.hasNext()) {
				
				String currentId = (String) ids.next();
				
				logger.log(Level.FINEST, "Next Id in DB: " + currentId + "\n");
				
				if (currentId.equals(slaId)) {
					
					AgreementDetails details = (AgreementDetails)offers.get(currentId);
					
					if (offer.equals(details.getQoSDescriptor())) {
						
						details = (AgreementDetails)offers.remove(currentId);
						details.setJobConvId(jobConvId);
						offers.put(currentId, details);					
						
						return details; // correct (return key of db/hashtable)
					}
					else {

						logger.log(Level.WARNING, "Given QoS Descriptor does not match to the one in the local database (Manipulated?)!");

						this.printComparison(details.getQoSDescriptor(), offer);

						throw (new RemoteException("Given QoS Descriptor does not match to the one in the local database (Manipulated?)!"));
						//return details; // correct (return key of db/hashtable)
					}
				}
			}
			logger.log(Level.WARNING, "Given QoS Descriptor not available in database!");
			logger.log(Level.FINEST, "Given QoS Descriptor:\n" + offer + "\n");
			throw (new RemoteException("Given QoS Descriptor not available in database!"));
		}

		logger.log(Level.WARNING, "WRONG COMMAND!");
		return null; // should not be reached!
	}

	final private void printComparison(QoSDescriptor dbQoSDesc, QoSDescriptor qosDesc) {
	
		logger.log(Level.FINEST, "Given QoS Descriptor:\n" + qosDesc + "\n");
		logger.log(Level.FINEST, "DB QoS Descriptor:\n" + dbQoSDesc + "\n");
    	
    	StringBuffer compString = new StringBuffer();
    	
		compString.append("DB QoS Descriptor vs. given QoS Descriptor.\n");
		if (dbQoSDesc instanceof QoSDescriptor) {
			compString.append("Object \"dbQoSDesc\" instanceof QoSDescriptor: true\n");
		}
		else {
			compString.append("Object \"dbQoSDesc\" instanceof QoSDescriptor: false\n");
		}
		if (qosDesc instanceof QoSDescriptor) {
			compString.append("Object \"qosDesc\" instanceof QoSDescriptor: true\n");
		}
		else {
			compString.append("Object \"qosDesc\" instanceof QoSDescriptor: false\n");
		}
    	compString.append("SLAName: " + dbQoSDesc.getSLAName() + "/" + qosDesc.getSLAName() + " (" + dbQoSDesc.getProviderName().equals(qosDesc.getProviderName()) + ")\n");
    	compString.append("ProviderName: " + dbQoSDesc.getProviderName() + "/" + qosDesc.getProviderName() + " (" + dbQoSDesc.getProviderName().equals(qosDesc.getProviderName()) + ")\n");
    	compString.append("ConsumerName: " + dbQoSDesc.getConsumerName() + "/" + qosDesc.getConsumerName() + " (" + dbQoSDesc.getConsumerName().equals(qosDesc.getConsumerName()) + ")\n");
    	compString.append("Price: " + dbQoSDesc.getObjectivePrice() + "/" + qosDesc.getObjectivePrice() + " (" + (dbQoSDesc.getObjectivePrice() == qosDesc.getObjectivePrice()) + ")\n");;
    	compString.append("BeginTime: " + formatCalendar(dbQoSDesc.getObjectiveBeginTime()) + "/" + formatCalendar(qosDesc.getObjectiveBeginTime()) + " (" + dbQoSDesc.getObjectiveBeginTime().equals(qosDesc.getObjectiveBeginTime()) + ")\n");
    	compString.append("EndTime: " + formatCalendar(dbQoSDesc.getObjectiveEndTime()) + "/" + formatCalendar(qosDesc.getObjectiveEndTime()) + " (" + dbQoSDesc.getObjectiveEndTime().equals(qosDesc.getObjectiveEndTime()) + ")\n");
    	
    	logger.log(Level.FINER, compString.toString());
	}
*/




	private String formatCalendar(Calendar calendar) {
		
		return (dateAndTimeFormatter.format(new Date(calendar.getTimeInMillis())));
	}

	private class OfferData {
		
		private long jid = -1;
		
		private Calendar begin = null;
		private Calendar end = null;

		private double price = -1;

		public OfferData() {
		}


		public void setJobId(long jid) {
			
			this.jid = jid;
		}
		
		public long getJobId() {
			
			return jid;
		}


		public void setBegin(Calendar begin) {
			
			this.begin = begin;
		}
		
		public Calendar getBegin() {
			
			return begin;
		}
		

		public void setEnd(Calendar end) {
			
			this.end = end;
		}
		
		public Calendar getEnd() {
			
			return end;
		}
		

		public void setPrice(double price) {
			
			this.price = price;
		}
		
		public double getPrice() {
			
			return price;
		}

		public String toString() {
		
			StringBuffer buf = new StringBuffer();
			buf.append("[OfferData - id: " + jid);
			buf.append(", price: " + price);
			buf.append(", begin: " + dateAndTimeFormatter.format(new Date(begin.getTimeInMillis())));
			buf.append(", end: " + dateAndTimeFormatter.format(new Date(end.getTimeInMillis())) + "]");
				
			return buf.toString();			
		}
		

	}
	/*	
	private class OfferPriceComparator implements Comparator {
		
 		int compare(Object o1, Object o2) {
 			
 			OfferData oa1 = (OfferData) o1;
 			OfferData oa2 = (OfferData) o2;
 			
 			if (oa1.getPrice() > oa2.getPrice()) {
 				
 				return -1;
 			}
 			else if (oa1.getPrice() == oa2.getPrice()) {
 				
 				return 0;
 			}
			return 1;
 		}
	}
	*/


}

