/////////////////////////////////////////////////////////////////////////
//
// © 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.wsla;

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

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.OutputKeys;

import uk.ac.soton.itinnovation.gemss.negotiation.wsla.WSLAQoSHandler;

/*
* WSLA parse and formatter, designed to work specifically on the SLA parameters refering to
* quality of service parameters. Heavily uses the JAXP XML parsing code.
* A set of construct element tags are used, along with deep searches for these tags, to remove some of the
* dependance of this parser on the exact structure of each WSLA. This allows WSLA authors to
* be able to add and remove SLAParameters and Obligations, without changing and re-compiling
* this parser class.
* Valid for WSLA spec version v1.0 2003 [restricted set of constructs - see docs]
*/
public class WSLAQoSHandlerImpl implements WSLAQoSHandler {

      // WSLA tag names (read from config file)
      private String mstrSLAElement;
      // service def elements
      private String mstrServiceDefinitionElement;
      private String mstrServiceDefNameAttribute;			// attr is service def name
      private String mstrPeriodElement;
      private String mstrPeriodStartElement;				// text child is start date
      private String mstrPeriodEndElement;				// text child is end date
      private String mstrPeriodMonthsElement;				// text child is interval months
      // SLA parameter elements (occurs deep within ServiceDef)
      private String mstrSLAParameterElement;
      private String mstrSLAParamNameAttribute;			// attr is property name
      private String mstrSLAParamTypeAttribute;			// attr is property type
      private String mstrSLAParamUnitAttribute;			// attr is property unit
      // Obligation elements
      private String mstrObligationsElement;
      private String mstrServiceLevelObjectiveElement;
      private String mstrValidityStartElement;			// text child is start date
      private String mstrValidityEndElement;				// text child is end date
      private String mstrPredicateSLAParameterElement;	// text child is property name
      private String mstrPredicateValueElement;			// text child is property value

      // Config file constants
      private final String mstaticSLAElement = "SLAElement";
      private final String mstaticServiceDefinitionElement = "ServiceDefinitionElement";
      private final String mstaticServiceDefNameAttribute = "ServiceDefNameAttribute";
      private final String mstaticPeriodElement = "PeriodElement";
      private final String mstaticPeriodStartElement = "PeriodStartElement";
      private final String mstaticPeriodEndElement = "PeriodEndElement";
      private final String mstaticPeriodMonthsElement = "PeriodMonthsElement";
      private final String mstaticSLAParameterElement = "SLAParameterElement";
      private final String mstaticSLAParamNameAttribute = "SLAParamNameAttribute";
      private final String mstaticSLAParamTypeAttribute = "SLAParamTypeAttribute";
      private final String mstaticSLAParamUnitAttribute = "SLAParamUnitAttribute";
      private final String mstaticObligationsElement = "ObligationsElement";
      private final String mstaticServiceLevelObjectiveElement = "ServiceLevelObjectiveElement";
      private final String mstaticValidityStartElement = "ValidityStartElement";
      private final String mstaticValidityEndElement = "ValidityEndElement";
      private final String mstaticPredicateSLAParameterElement = "PredicateSLAParameterElement";
      private final String mstaticPredicateValueElement = "PredicateValueElement";

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

      /**
       * Constructor. Loads a config file with set-up infromation about the WSLA tag names.
       * @param strConfigFile configuration file
       */
      public WSLAQoSHandlerImpl( String strConfigFile ) throws Exception
      {
            Properties props;

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

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

                  // get config entries
                  mstrSLAElement = props.getProperty( mstaticSLAElement );
                  mstrServiceDefinitionElement = props.getProperty( mstaticServiceDefinitionElement );
                  mstrServiceDefNameAttribute = props.getProperty( mstaticServiceDefNameAttribute );
                  mstrPeriodElement = props.getProperty( mstaticPeriodElement );
                  mstrPeriodStartElement = props.getProperty( mstaticPeriodStartElement );
                  mstrPeriodEndElement = props.getProperty( mstaticPeriodEndElement );
                  mstrPeriodMonthsElement = props.getProperty( mstaticPeriodMonthsElement );
                  mstrSLAParameterElement = props.getProperty( mstaticSLAParameterElement );
                  mstrSLAParamNameAttribute = props.getProperty( mstaticSLAParamNameAttribute );
                  mstrSLAParamTypeAttribute = props.getProperty( mstaticSLAParamTypeAttribute );
                  mstrSLAParamUnitAttribute = props.getProperty( mstaticSLAParamUnitAttribute );
                  mstrObligationsElement = props.getProperty( mstaticObligationsElement );
                  mstrServiceLevelObjectiveElement = props.getProperty( mstaticServiceLevelObjectiveElement );
                  mstrValidityStartElement = props.getProperty( mstaticValidityStartElement );
                  mstrValidityEndElement = props.getProperty( mstaticValidityEndElement );
                  mstrPredicateSLAParameterElement = props.getProperty( mstaticPredicateSLAParameterElement );
                  mstrPredicateValueElement = props.getProperty( mstaticPredicateValueElement );

                  // 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");
            }
      }

      /**
       * parses a WSLA string and extracts all SLA parameters in the service definition section
       * of the WSLA. The result is a list of SLA properties, and associated with this list
       * are type URI's, unit URI's and values (from obligations section) for the properties.
       * @param strWSLA string representation of the WSLA document
       * @return hashtable containing (propery URI, vector). The vector contains (unit, type, value) strings.
       */
      public Hashtable parseSLAParameters( String strWSLA ) throws Exception
      {
            Document docWSLA;
            Hashtable hashResult;

            try
            {
                  // parse XML and make a DOM document object
                  docWSLA = parseWSLAString( strWSLA );
                  if( docWSLA == null ) throw new Exception("Failed to parse WSLA XML string");

                  // parse the XML contents to extract the properties
                  hashResult = new Hashtable();
                  navigateSLA( docWSLA,hashResult,false ); // recursive navigation, dumping data into hash table as it goes

                  // all done
                  return hashResult;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to parse SLA parameters",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * parses a WSLA string and sets all SLA parameter values. Values are set in the obligations section.
       * @param strWSLA string representation of the WSLA document
       * @param hashValues hashtable containing (propery URI, vector). The vector contains (name, unit, type, value) strings.
       * @return new WSLA string containign the new values
       */
      public String setSLAValues( String strWSLA, Hashtable hashSLAValues ) throws Exception
      {
            Document docWSLA;
            TransformerFactory tFactory;
            Transformer transformer;
            String strNewWSLA, strValue;
            DOMSource domSource;
            StreamResult result;
            StringWriter writerString;

            try
            {
                  // parse XML and make a DOM document object
                  docWSLA = parseWSLAString( strWSLA );
                  if( docWSLA == null ) throw new Exception("Failed to parse WSLA XML string");

                  // navigate the XML contents to set the properties
                  navigateSLA( docWSLA, hashSLAValues, true ); // recursive navigation, dumping data into document nodes as it goes

                  // serialize the WSLA document with it's new values
                  // Use a Transformer for output
                  tFactory = TransformerFactory.newInstance();
                  transformer = tFactory.newTransformer();
                  transformer.setOutputProperty( OutputKeys.INDENT, "yes" );

                  // preserve DOCTYPE = XML (NOT NEEDED?)
//                  if (docWSLA.getDoctype() != null) {
//                        strValue = ( new File( docWSLA.getDoctype().getSystemId() ) ).getName();
//                        transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, strValue );
//                  }

                  // transform DOM -> string writer
                  domSource = new DOMSource(docWSLA);
                  writerString = new StringWriter();
                  result = new StreamResult( writerString );
                  transformer.transform( domSource, result );

                  // make string from stream
                  strNewWSLA = writerString.toString();

                  // all done
                  return strNewWSLA;
            }
            catch (TransformerConfigurationException tce) {
                  mlogger.log( Level.WARNING,"Failed to set SLA parameters [transformer-config]",tce );
                  throw tce;
            }
            catch (TransformerException te) {
                  mlogger.log( Level.WARNING,"Failed to set SLA parameters [transformer]",te );
                  throw te;
            }
            catch (SAXParseException spe) {
                  mlogger.log( Level.WARNING,"Failed to set SLA parameters [sax-parser]",spe );
                  throw spe;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to set SLA parameters",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * parses a WSLA string and extracts the service definition validity dates
       * of the WSLA. Start, End and Months are extracted.
       * @param strWSLA string representation of the WSLA document
       * @return vector containing (start, end) validity parameters for this WSLA.
       */
      public Vector parseValidity( String strWSLA ) throws Exception
      {
            Document docWSLA;
            Vector vectorValidity;

            try
            {
                  // parse XML and make a DOM document object
                  docWSLA = parseWSLAString( strWSLA );
                  if( docWSLA == null ) throw new Exception("Failed to parse WSLA XML string");

                  // parse the XML contents to extract the properties
                  vectorValidity = new Vector();
                  navigateValidity( docWSLA,vectorValidity,false ); // recursive navigation, dumping data into vector as it goes

                  // all done
                  return vectorValidity;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to parse SLA parameters",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * parses a WSLA string and sets all validity dates. Only one validity date is used throughout the contract, so obligation validity
       * fields are set along with teh service definition dates. If more fine grained control is needed, then another method would need
       * to be written to do this.
       * @param strWSLA string representation of the WSLA document
       * @param vectorValidity vector containing (start, end) validity parameters for this WSLA.
       * @return new WSLA string containign the new values
       */
      public String setValidity( String strWSLA, Vector vectorValidity ) throws Exception
      {
            Document docWSLA;
            TransformerFactory tFactory;
            Transformer transformer;
            String strNewWSLA, strValue;
            DOMSource domSource;
            StreamResult result;
            StringWriter writerString;

            try
            {
                  // parse XML and make a DOM document object
                  docWSLA = parseWSLAString( strWSLA );
                  if( docWSLA == null ) throw new Exception("Failed to parse WSLA XML string");

                  // navigate the XML contents to set the properties
                  navigateValidity( docWSLA, vectorValidity, true ); // recursive navigation, dumping data into document nodes as it goes

                  // serialize the WSLA document with it's new values
                  // Use a Transformer for output
                  tFactory = TransformerFactory.newInstance();
                  transformer = tFactory.newTransformer();
                  transformer.setOutputProperty( OutputKeys.INDENT, "yes" );

                  // preserve DOCTYPE = XML (NOT NEEDED?)
//                  if (docWSLA.getDoctype() != null) {
//                        strValue = ( new File( docWSLA.getDoctype().getSystemId() ) ).getName();
//                        transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, strValue );
//                  }

                  // transform DOM -> string writer
                  domSource = new DOMSource(docWSLA);
                  writerString = new StringWriter();
                  result = new StreamResult( writerString );
                  transformer.transform( domSource, result );

                  // make string from stream
                  strNewWSLA = writerString.toString();

                  // all done
                  return strNewWSLA;
            }
            catch (TransformerConfigurationException tce) {
                  mlogger.log( Level.WARNING,"Failed to set validity [transformer-config]",tce );
                  throw tce;
            }
            catch (TransformerException te) {
                  mlogger.log( Level.WARNING,"Failed to set validity [transformer]",te );
                  throw te;
            }
            catch (SAXParseException spe) {
                  mlogger.log( Level.WARNING,"Failed to set validity [sax-parser]",spe );
                  throw spe;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to set validity",ex );
                  throw ex;	// re-throw exception
            }
      }


      /**
       * validates a WSLA string against another template WSLA string. Everything is checked for
       * an exact match except for the parameter values.
       * @param strWSLA string representation of the WSLA document
       * @param strWSLATemplate string representation of the WSLA template
       * @return true if the WSLA matches the format of the template
       */
      public boolean validateWSLA( String strWSLA, String strWSLATemplate ) throws Exception
      {
            Document docWSLA, docTemplate;

            try {
                  // make a DOM model for BOTH WSLA strings
                  try {
                        docWSLA = parseWSLAString( strWSLA );
                        if( docWSLA == null ) {
                              mlogger.log( Level.INFO,"Validation failed on WSLA parse - null return" );
                              return false;
                        }
                        docTemplate = parseWSLAString( strWSLATemplate );
                        if( docTemplate == null ) {
                              mlogger.log( Level.INFO,"Validation failed on template parse - null return" );
                              return false;
                        }
                  }
                  catch( Exception parseex ) {
                        mlogger.log( Level.INFO,"Validation failed on WSLA parse : "+parseex.getMessage() );
                        return false;
                  }

                  // navigate each node, checking that all element nodes are the same. Do NOT check
                  // for any node values, just node names. This will allow a template to have it's
                  // values changed and still remain valid.
                  if( !validateNode( docWSLA, docTemplate ) ) {
                        mlogger.log( Level.INFO,"Node validation failed" );
                        return false;
                  }

                  // all done
                  return true;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.WARNING,"Failed to validate WSLA string",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * Parses a WSLA string and returns a document. Simple XML parse only.
       * @param strWSLA string representation of the WSLA document
       * @return DOM document object (null on error)
       */
      private Document parseWSLAString( String strWSLA )
      {
            StringReader reader;
            InputSource source;
            DocumentBuilderFactory factory;
            DocumentBuilder builder;
            Document document;

            try
            {
                  // get a doc builder
                  factory = DocumentBuilderFactory.newInstance();
                  factory.setValidating(true);
                  factory.setNamespaceAware(true);
                  builder = factory.newDocumentBuilder();
                  builder.setErrorHandler( null );

                  // parse WSLA (needs to read from an input source)
                  reader = new StringReader( strWSLA );
                  source = new InputSource( reader );
                  document = builder.parse( source );

                  // return doc
                  return document;
            }
            catch( SAXException se )
            {
                  mlogger.log( Level.WARNING,"WSLA parse error",se );
                  return null;
            }
            catch( ParserConfigurationException pce ) {
                  mlogger.log( Level.WARNING,"WSLA parser configuration error",pce );
                  return null;
            }
            catch( IOException ie )
            {
                  mlogger.log( Level.WARNING,"WSLA parse IO error",ie );
                  return null;
            }
      }

      /**
       * navigates around a root level WSLA DOM document. Will call other navigate methods, recursively if needed,
       * and either add values to the hash of contents, or set values from the hash of contents.
       * @param docWSLA DOM XML document of the root level WSLA
       * @param hashContents hashtable containing (propery URI, vector). The vector contains (unit, type, value) strings.
       * @param bSetValues if true the values contained in the hashContents will overwrite the node values. If false the node values will overwrite the hash values.
       */
      private void navigateSLA( Document docWSLA, Hashtable hashContents, boolean bSetValues ) throws Exception
      {
            NodeList nodeList, nodeList2;
            org.w3c.dom.Node node, node2;	// clash with Jena Node class
            int i,j;
            boolean bOk;

            try
            {
                  // check node is a document type
                  if( docWSLA == null ) throw new Exception("null node");
                  if( docWSLA.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE ) throw new Exception("expected a DOCUMENT node");

                  // get child and check it's a SLA element
                  bOk = false;
                  nodeList = docWSLA.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node = nodeList.item( i );
                              if( node == null ) throw new Exception("null node");

                              if( node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // is this the SLA element?
                                    if( mstrSLAElement.equals( node.getLocalName() ) ) {
                                          // found SLA element (stop looking)
                                          bOk = true;
                                          i = nodeList.getLength();

                                          // get it's children and process all service desc and obligation constructs
                                          nodeList2 = node.getChildNodes();
                                          if( nodeList2 != null ) {
                                                // loop on all nodes in list, looking for ELEMENT nodes
                                                for( j=0;j<nodeList2.getLength();j++ ) {
                                                      node2 = nodeList2.item( j );
                                                      if( node2 == null ) throw new Exception("null node");

                                                      if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                                            // is this a ServiceDescription element?
                                                            if( mstrServiceDefinitionElement.equals( node2.getLocalName() ) ) {
                                                                  navigateServiceDesc( node2,hashContents,bSetValues );
                                                            }
                                                            // is this an Obligations element?
                                                            else if( mstrObligationsElement.equals( node2.getLocalName() ) ) {
                                                                  navigateObligations( node2,hashContents,bSetValues );
                                                            }
                                                      } // type check
                                                } // next node
                                          } // null check
                                    } // SLA check
                              } // element check
                        } // next node
                  } // null check

                  // did we find a SLA?
                  if( !bOk ) throw new Exception("SLA element not found");

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to navigage root WSLA",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * navigates around the service def node set of a DOM nodes. Will call itself recursively looking deep into the
       * structure of the nodes. It will add SLAParameter.name, SLAParameter.type, SLAParameter.unit occurances to the
       * properties hash table.
       * @param node DOM node for the service definition element
       * @param hashContents hashtable containing (propery URI, vector). The vector contains (unit, type, value) strings.
       * @param bSetValues if true the values contained in the hashContents will overwrite the node values. If false the node values will overwrite the hash values.
       */
      private void navigateServiceDesc( org.w3c.dom.Node node, Hashtable hashContents, boolean bSetValues ) throws Exception
      {
            NodeList nodeList;
            NamedNodeMap nodeMap;
            int i,j;
            org.w3c.dom.Node node2, nodeAttr;	// clash with Jena Node class
            Vector vector;
            boolean bOk;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) throw new Exception("expected an ELEMENT node");

                  // get children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for other ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");

                              if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // is this a SLAParameter node?
                                    if( mstrSLAParameterElement.equals( node2.getLocalName() ) ) {
                                          // stop recursing and extract it's attribute children
                                          bOk = false;
                                          if( !bSetValues ) {
                                                vector = new Vector(3);
                                                vector.addElement( null );
                                                vector.addElement( null );
                                                vector.addElement( null );
                                          }
                                          else {
                                                vector = null;
                                          }

                                          // get name attribute
                                          nodeMap = node2.getAttributes();
                                          if( nodeMap != null ) {
                                                for( j=0;j<nodeMap.getLength();j++ ) {
                                                      nodeAttr = nodeMap.item(j);
                                                      if( mstrSLAParamNameAttribute.equals( nodeAttr.getLocalName() ) ) {
                                                            if( !bSetValues ) {
                                                                  // add new entry to hash table (replace if already one there)
                                                                  // this might be bad for garbage collection since should release old vector contents first
                                                                  hashContents.put( nodeAttr.getNodeValue(), vector );
                                                            }
                                                            else {
                                                                  // get existing vector from hash contents
                                                                  vector = (Vector) hashContents.get( nodeAttr.getNodeValue() );
                                                            }
                                                            bOk = true;
                                                      } // SLAParameterName attribute check (name)
                                                } // next node
                                          } // null check

                                          // check name was found
                                          if( !bOk ) throw new Exception("SLA name atribute not found");

                                          // get unit, type and value attributes
                                          nodeMap = node2.getAttributes();
                                          if( nodeMap != null ) {
                                                for( j=0;j<nodeMap.getLength();j++ ) {
                                                      nodeAttr = nodeMap.item(j);
                                                      if( mstrSLAParamUnitAttribute.equals( nodeAttr.getLocalName() ) ) {
                                                            if( !bSetValues ) {
                                                                  vector.setElementAt( nodeAttr.getNodeValue(),0 );
                                                            }
                                                            else {
                                                                  // set nodes value to hash contents
                                                                  nodeAttr.setNodeValue( (String) vector.elementAt(0) );
                                                            }
                                                      } // SLAParameterName attribute check (unit)
                                                      else if( mstrSLAParamTypeAttribute.equals( nodeAttr.getLocalName() ) ) {
                                                            if( !bSetValues ) {
                                                                  vector.setElementAt( nodeAttr.getNodeValue(),1 );
                                                            }
                                                            else {
                                                                  // set nodes value to hash contents
                                                                  nodeAttr.setNodeValue( (String) vector.elementAt(1) );
                                                            }
                                                      } // SLAParameterName attribute check (type)
                                                      else if( mstrSLAParamTypeAttribute.equals( nodeAttr.getLocalName() ) ) {
                                                            if( bSetValues ) {
                                                                  // set nodes value to hash contents
                                                                  nodeAttr.setNodeValue( (String) vector.elementAt(2) );
                                                            }
                                                            else {
                                                                  // set nodes value to hash contents
                                                                  nodeAttr.setNodeValue( (String) vector.elementAt(2) );
                                                            }
                                                      } // SLAParameterName attribute check (value)
                                                } // next attr
                                          } // null check
                                    }
                                    else {
                                          // recurse on the children until we find a SLAParameter element
                                          navigateServiceDesc( node2, hashContents,bSetValues );
                                    }
                              } // type check
                        } // next node
                  } // null check

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to navigage service desc WSLA",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * navigates around an obligation set of a DOM nodes. It will search each service level objective element
       * deeply for the SLA parameter and value. The value will then be added to the result hash table OR the nodes values
       * replaced with the hash contents
       * @param node DOM node for the service definition element
       * @param hashContents hashtable containing (propery URI, vector). The vector contains (name, unit, type, value) strings.
       * @param bSetValues if true the values contained in the hashContents will overwrite the node values. If false the node values will overwrite the hash values.
       */
      private void navigateObligations( org.w3c.dom.Node node, Hashtable hashContents, boolean bSetValues ) throws Exception
      {
            NodeList nodeList;
            NamedNodeMap nodeMap;
            int i,j;
            org.w3c.dom.Node node2, nodeSLA, nodeValue;	// clash with Jena Node class
            Vector vector;
            String strPropertyName, strPropertyValue;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) throw new Exception("expected an ELEMENT node");

                  // get children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for other ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");

                              if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // is this a service level objective?
                                    if( mstrServiceLevelObjectiveElement.equals( node2.getLocalName() ) ) {
                                          // get SLA parameter node
                                          nodeSLA = getNodeFromTree( node2,mstrPredicateSLAParameterElement );
                                          if( nodeSLA == null ) throw new Exception("service level objective with no SLA parameter");

                                          // get the aggregation of all text nodes under this node to find its value
                                          strPropertyName = getTextUnderNode( nodeSLA );

                                          // get value node
                                          nodeValue = getNodeFromTree( node2,mstrPredicateValueElement );
                                          if( nodeValue == null ) throw new Exception("service level objective with no value");

                                          if( !bSetValues ) {
                                                // get the aggregation of all text nodes under this node to find its value
                                                strPropertyValue = getTextUnderNode( nodeValue );

                                                // make new hash entry if needed
                                                if( !hashContents.containsKey( strPropertyName ) ) {
                                                      vector = new Vector();
                                                      vector.addElement( null ); // unit
                                                      vector.addElement( null ); // type
                                                      vector.addElement( strPropertyValue ); // value
                                                      hashContents.put( strPropertyName,vector );
                                                }
                                                else {
                                                      // add the value to the hash table
                                                      vector = (Vector) hashContents.get( strPropertyName );
                                                      vector.setElementAt( strPropertyValue,2 );
                                                }
                                          }
                                          else {
                                                // set value
                                                vector = (Vector) hashContents.get( strPropertyName );
                                                strPropertyValue = (String) vector.elementAt( 2 );
                                                setTextUnderNode( nodeValue,strPropertyValue );
                                          }
                                    } // service level objective check
                              } // type check
                        } // next node
                  } // null check

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to navigage obligations WSLA",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * navigates around a root level WSLA DOM document. Will call other navigate methods, recursively if needed,
       * and either add values to the vector, or set values from the vector.
       * @param docWSLA DOM XML document of the root level WSLA
       * @param vectorValidity vector containing (start, end) validity parameters for this WSLA.
       * @param bSetValues if true the values contained in the hashContents will overwrite the node values. If false the node values will overwrite the hash values.
       */
      private void navigateValidity( Document docWSLA, Vector vectorValidity, boolean bSetValues ) throws Exception
      {
            NodeList nodeList, nodeList2;
            org.w3c.dom.Node node, node2;	// clash with Jena Node class
            int i,j;
            boolean bOk;

            try
            {
                  // check node is a document type
                  if( docWSLA == null ) throw new Exception("null node");
                  if( docWSLA.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE ) throw new Exception("expected a DOCUMENT node");

                  // get child and check it's a SLA element
                  bOk = false;
                  nodeList = docWSLA.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node = nodeList.item( i );
                              if( node == null ) throw new Exception("null node");

                              if( node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // is this the SLA element?
                                    if( mstrSLAElement.equals( node.getLocalName() ) ) {
                                          // found SLA element (stop looking)
                                          bOk = true;
                                          i = nodeList.getLength();

                                          // get it's children and process all obligation constructs
                                          nodeList2 = node.getChildNodes();
                                          if( nodeList2 != null ) {
                                                // loop on all nodes in list, looking for ELEMENT nodes
                                                for( j=0;j<nodeList2.getLength();j++ ) {
                                                      node2 = nodeList2.item( j );
                                                      if( node2 == null ) throw new Exception("null node");

                                                      if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                                            // is this an Obligations element?
                                                            if( mstrObligationsElement.equals( node2.getLocalName() ) ) {
                                                                  navigateObligationValidity( node2,vectorValidity,bSetValues );
                                                            }
                                                      } // type check
                                                } // next node
                                          } // null check
                                    } // SLA check
                              } // element check
                        } // next node
                  } // null check

                  // did we find a SLA?
                  if( !bOk ) throw new Exception("SLA element not found");

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to navigage root WSLA",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * navigates around an obligation set of a DOM nodes. It will search each service level objective element
       * deeply for the validity construct. The value will then be added to the result hash table OR the nodes values
       * replaced with the hash contents
       * @param node DOM node for the service definition element
       * @param vectorValidity vector containing (start, end) validity parameters for this WSLA.
       * @param bSetValues if true the values contained in the hashContents will overwrite the node values. If false the node values will overwrite the hash values.
       */
      private void navigateObligationValidity( org.w3c.dom.Node node, Vector vectorValidity, boolean bSetValues ) throws Exception
      {
            NodeList nodeList;
            NamedNodeMap nodeMap;
            int i,j;
            org.w3c.dom.Node node2, nodeStart, nodeEnd;	// clash with Jena Node class
            Vector vector;
            String strStart, strEnd;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) throw new Exception("expected an ELEMENT node");

                  // get children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for other ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");

                              if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // is this a service level objective?
                                    if( mstrServiceLevelObjectiveElement.equals( node2.getLocalName() ) ) {
                                          // get start node under this service level objective node
                                          nodeStart = getNodeFromTree( node2, mstrValidityStartElement );
                                          if( nodeStart == null ) throw new Exception("validity node with no start element");

                                          // get start node under this validity node
                                          nodeEnd = getNodeFromTree( node2, mstrValidityEndElement );
                                          if( nodeEnd == null ) throw new Exception("validity node with no end element");

                                          // set or get values?
                                          if( !bSetValues ) {
                                                // get
                                                // get the aggregation of all text nodes under this node to find its value
                                                strStart = getTextUnderNode( nodeStart );

                                                // get the aggregation of all text nodes under this node to find its value
                                                strEnd = getTextUnderNode( nodeEnd );

                                                // clear vector
                                                vectorValidity.removeAllElements();

                                                // add new values
                                                vectorValidity.addElement( strStart );
                                                vectorValidity.addElement( strEnd );
                                          }
                                          else {
                                                // set
                                                // check vector is ok
                                                if( vectorValidity.size() != 2 ) throw new Exception("validity vector size mis-match = "+vectorValidity.size() );

                                                // set new start and end values from the validity vector
                                                strStart = (String) vectorValidity.elementAt( 0 );
                                                setTextUnderNode( nodeStart,strStart );
                                                strEnd = (String) vectorValidity.elementAt( 1 );
                                                setTextUnderNode( nodeEnd,strEnd );
                                          }
                                    } // service level objective check
                              } // type check
                        } // next node
                  } // null check

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to navigage obligations WSLA",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * recursive function to find a target element node. null return indicates that the target is not within the
       * node tree specified.
       * @param node DOM node from which to start deep search
       * @param strTarget name of target node
       * @return target node
       */
      private org.w3c.dom.Node getNodeFromTree( org.w3c.dom.Node node, String strTarget ) throws Exception
      {
            NodeList nodeList;
            int i;
            org.w3c.dom.Node node2;	// clash with Jena Node class

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) return null; // ignore non-element nodes

                  // is node the target node?
                  if( strTarget.equals( node.getLocalName() ) ) {
                        // return the value
                        return node;
                  }

                  // loop on children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for other ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");
                              if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // check child
                                    node2 = getNodeFromTree( node2, strTarget );

                                    // found target?
                                    if( node2 != null ) return node2;
                                    // if not, keep looking
                              } // type check
                        } // next node
                  } // null check

                  // all done - not found
                  return null;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to get node from tree",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * recursive function to aggregate all child text nodes from a target node. The total string, trimmed of pre and post whitespace,
       * will be returned. Will return an empty string if no Text nodes found (or they had no content)
       * @param node DOM node whose children will be searched for Text nodes
       * @return text string derived from any text node children
       */
      private String getTextUnderNode( org.w3c.dom.Node node ) throws Exception
      {
            NodeList nodeList;
            int i;
            org.w3c.dom.Node node2;	// clash with Jena Node class
            String strAggregateText;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) return null; // ignore non-element nodes

                  // init aggregate value
                  strAggregateText = "";

                  // loop on children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for TEXT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");
                              if( node2.getNodeType() == org.w3c.dom.Node.TEXT_NODE ) {
                                    // get text value
                                    strAggregateText += node2.getNodeValue().trim();
                              } // type check
                        } // next node
                  } // null check

                  // all done
                  return strAggregateText;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to get text under node",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * recursive function to set the text field under a target node. It will blank all child text nodes and change
       * the value of the first one to the value passed in the method arguments. This method will throw an exception
       * if cannot find or set a text element
       * @param node DOM node whose children will be searched for Text nodes
       * @param strValue value string
       */
      private void setTextUnderNode( org.w3c.dom.Node node, String strValue ) throws Exception
      {
            NodeList nodeList;
            int i;
            org.w3c.dom.Node node2;	// clash with Jena Node class
            boolean bOk;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE ) throw new Exception("non element root node");

                  // loop on children and blank all text nodes, set teh first one to the value arg
                  bOk = false;
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for TEXT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");
                              if( node2.getNodeType() == org.w3c.dom.Node.TEXT_NODE ) {
                                    // if this the first node
                                    if( !bOk ) {
                                          // set text value
                                          node2.setNodeValue( strValue );
                                          bOk = true;
                                    }
                                    else {
                                          // blank node
                                          node2.setNodeValue("");
                                    }
                              } // type check
                        } // next node
                  } // null check

                  // throw an exception if we found no text nodes
                  if( !bOk ) throw new Exception("Failed to find a child text node");

                  // all done
                  return;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to get text under node",ex );
                  throw ex;	// re-throw exception
            }
      }

      /**
       * Node validation. Checks the name of all nodes but not the values (aparted from comment values).
       * This allows a template to have its values set and still be validated. This method will heavily recurse.
       * @param nodeWSLA WSLA DOM node
       * @param nodeTemplate template DOM node
       * @return false if the validation fails
       */
      private boolean validateNode( org.w3c.dom.Node nodeWSLA, org.w3c.dom.Node nodeTemplate ) throws Exception
      {
            NodeList nodeList1, nodeList2;
            int i;
            org.w3c.dom.Node node1, node2;	// clash with Jena Node class

            try
            {
                  // check nodes are not nul
                  if( nodeWSLA == null ) throw new Exception("null WSLA node");
                  if( nodeTemplate == null ) throw new Exception("null template node");

                  // node types should be the same
                  if( nodeWSLA.getNodeType() != nodeTemplate.getNodeType() ) throw new Exception("Node type mis-match");

                  // switch on node type
                  switch( nodeWSLA.getNodeType() ) {
                        case org.w3c.dom.Node.ELEMENT_NODE :
                              // check node names match
                              if( nodeWSLA.getLocalName() == null ) throw new Exception("null node local name");
                              if( !nodeWSLA.getLocalName().equals( nodeTemplate.getLocalName() ) ) throw new Exception("local name mis-match");

                              // now follow through to DOCUMENT_NODE processing and recurse

                        case org.w3c.dom.Node.DOCUMENT_NODE :
                              // treat doc (start node) same as element node and recurse deeply
                              // except do NOT check for the local name

                              // loop on children
                              nodeList1 = nodeWSLA.getChildNodes();
                              nodeList2 = nodeTemplate.getChildNodes();
                              if( nodeList1 != null ) {
                                    // check list length
                                    if( nodeList1.getLength() != nodeList2.getLength() ) throw new Exception("node list length mis-match");
                                    // loop on all nodes in list
                                    for( i=0;i<nodeList1.getLength();i++ ) {
                                          // get children
                                          node1 = nodeList1.item( i );
                                          node2 = nodeList2.item( i );

                                          // check these nodes
                                          if( !validateNode( node1,node2 ) ) return false;
                                    } // next node
                              } // null check
                              else {
                                    // node list template should be null too
                                    if( nodeList2 != null ) throw new Exception("node list mis-match");
                              }

                              break;
                        case org.w3c.dom.Node.COMMENT_NODE :
                              // check node value
                              if( nodeWSLA.getNodeValue() == null ) throw new Exception("null comment value");
                              if( !nodeWSLA.getNodeValue().equals( nodeTemplate.getNodeValue() ) ) throw new Exception("comment value mismatch");
                              break;
                        case org.w3c.dom.Node.TEXT_NODE :
                        default:
                              // do nothing since we are not checking values
                  } // end switch

                  // all done - ok!
                  return true;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.INFO,"WSLA validation check failed",ex );
                  return false;
            }
      }

} // end of WSLAQoSHandlerImpl