/////////////////////////////////////////////////////////////////////////
//
// © 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:             Darren Marvin
//      Created date:           2003/10/17
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.transportmessaging.messaging;

import eu.gemss.components.transport.servicedescription.*;
import eu.gemss.components.transport.servicedescription.policytypes.*;
import java.util.*;
import java.util.logging.*;
import eu.gemss.signals.SignalHandler;
import uk.ac.soton.itinnovation.gemss.transportmessaging.configuration.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.processing.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.processing.providers.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.servicedescription.policytypes.*;
import java.io.File;
import uk.ac.soton.itinnovation.gemss.utils.configuration.ConfigurationException;

/**
 * The MessageEnforcer class enforces message formatting according to the supplied policy
 * list.
 */
public class MessageEnforcer {

    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.MessageEnforcer");
    private TransportMessagingConfiguration mConfiguration;
    private SignalHandler mSignalHandler;
    private List staticInputPolicies;//are left unsynchronised to speed up access,
    private List staticOutputPolicies;//they should not be written to except in the loadStaticPolicyChains method
    private boolean mIncludeStaticPolicies = false;


    public MessageEnforcer(TransportMessagingConfiguration config, SignalHandler signalHandler, boolean includePolicyFile) throws MessagingException  {
      try{

        mConfiguration = config;
        mSignalHandler = signalHandler;
        String enforcementFile = config.getMessageEnforcementFilePath();
        if(staticInputPolicies==null || staticOutputPolicies==null)
          loadStaticPolicyChains(enforcementFile);
        if(includePolicyFile)
          mIncludeStaticPolicies = true;
        mLogger.log(Level.INFO,"Using configuration directory '" + mConfiguration.getConfigurationFileDir() + "' for message enforcment processors");
      }
      catch(ConfigurationException ex) {
        mLogger.log(Level.SEVERE,"Unable to access the static message enforcement policy file 'message_enforcement.xml', please check your configuration");
        throw new MessagingException("Unable to access the static message enforcement policy file 'message_enforcement.xml', please check your configuration");
      }

    }

	/**
     * Retrieve the static policies configured in the file message_enforcement.xml
     * @return static policies
     */
    public ServicePolicy retrieveStaticPolicies() {
      ServicePolicyImp sPolicy = new ServicePolicyImp();
      Iterator it = staticInputPolicies.iterator();
      while(it.hasNext()) {
        sPolicy.addIncomingPolicy((Policy) it.next());
      }
      it = staticOutputPolicies.iterator();
      while(it.hasNext()) {
        sPolicy.addOutgoingPolicy((Policy) it.next());
      }
      return sPolicy;
    }

    /**
     * Creates a new message that includes all the encoding requirements defined by the passed
     * policies when applied to the passed message.
     * @param serviceDesc service description including all policies to apply
     * @param message original message to copy
     * @return new message including all encoding changes
     * @throws MessagingException
     */
    public Message enforceOutgoingMessage(ServiceDescription serviceDesc, Message message) throws MessagingException {

      //only go through whole chain if a processor exists for all the policy items
        List chain = new ArrayList();


        //grab the outgoing policy list
        ProcessorProviderRegistry providerRegistry = ProcessorProviderRegistry.getInstance(mConfiguration);
        Policy[] outgoingPolicies = serviceDesc.getServicePolicy().getOrderedOutgoingPolicies();
        Message manipulatedMessage = message;


        //if any of the policy descriptors does not have a matching messageprocessor then a MessagingException
        //is thrown and the chain is not completed - all or nothing here! All except the NoPolicy implementation
        //it is simply ignored.
        PolicyTypeDescriptor noPolicyDescriptor = (new NoPolicyImp()).getPolicyTypeDescriptor();
        for(int i=0;i<outgoingPolicies.length;i++) {
            PolicyTypeDescriptor descriptor = outgoingPolicies[i].getPolicyTypeDescriptor();

            if(!descriptor.isEqual(noPolicyDescriptor)) {
              MessageProcessorProviderSpi provider = providerRegistry.getProvider(descriptor);
                provider.setSignalHandler(mSignalHandler);
                provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
                MessageProcessor processor = provider.getMessageProcessor(descriptor);
                chain.add(new PolicyProcessorPair(outgoingPolicies[i],processor));
            }

        }
        //add the static policy list as the last policies in the outgoing chain
        if(mIncludeStaticPolicies) {
          if(staticOutputPolicies!=null) {
            Iterator outIt = staticOutputPolicies.iterator();
            while(outIt.hasNext()) {
              Policy policy = (Policy) outIt.next();
              PolicyTypeDescriptor descriptor = policy.getPolicyTypeDescriptor();
              if(!descriptor.isEqual(noPolicyDescriptor)) {
                MessageProcessorProviderSpi provider = providerRegistry.getProvider(descriptor);
                provider.setSignalHandler(mSignalHandler);
                provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
                MessageProcessor processor = provider.getMessageProcessor(descriptor);
                chain.add(new PolicyProcessorPair(policy,processor));
              }

            }
          }
        }

        //does the message have attachments
        boolean hasAttachments = false;
        if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                    SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                    if(sm.getSOAPWireMessage().getAttachments().hasNext())
                      hasAttachments = true;
                }
                catch(Exception ex) {
                  //best efforts only
                }
              }

        //execute the chain of processors
        Iterator iterator = chain.iterator();
        int fileCount = 0;
        while(iterator.hasNext()) {
          PolicyProcessorPair pair = (PolicyProcessorPair) iterator.next();
          Policy policy = pair.getPolicy();
            MessageProcessor processor = pair.getMessageProcessor();
            Message tmpMsg = manipulatedMessage;
            if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                    SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                    if(hasAttachments) {
                      Iterator it = sm.getSOAPWireMessage().getAttachments();
                      while(it.hasNext()) {
                        javax.xml.soap.AttachmentPart part = (javax.xml.soap.AttachmentPart) it.next();
                        mLogger.log(Level.FINE,"Before processor '" + processor.getDescription() + "', attachment part '" + part.getContentId() + "' has " + part.getDataHandler().getInputStream().available() + " bytes available");
                      }
                    }

                }
                catch(Exception ex) {
                  //best efforts only
                }
              }
            //enforce the message
            mLogger.log(Level.FINE,"Executing outgoing chain processor '" + processor.getDescription() + "' ...");
            manipulatedMessage = processor.enforceMessage(serviceDesc,policy,manipulatedMessage);
            if(manipulatedMessage==null) {
                mLogger.log(Level.SEVERE,"Processor described as '" + processor.getDescription() + "' has generated an invalid message object, stopping invocation");
                throw new MessagingException("Unable to invoke service because the service has a policy that is not supported","Processor described as '" + processor.getDescription() + "' has generated an invalid message object, stopping invocation");
            }


              if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                    SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                    if(hasAttachments) {
                      Iterator it = sm.getSOAPWireMessage().getAttachments();
                      while(it.hasNext()) {
                        javax.xml.soap.AttachmentPart part = (javax.xml.soap.AttachmentPart) it.next();
                        mLogger.log(Level.FINE,"After processor '" + processor.getDescription() + "', attachment part '" + part.getContentId() + "' has " + part.getDataHandler().getInputStream().available() + " bytes available");
                      }
                    }

                }
                catch(Exception ex) {
                  //best efforts only
                }
              }


            //release previous message
            if(tmpMsg!=manipulatedMessage)
              tmpMsg.release();
            mLogger.log(Level.FINE,"Completed executing processor '" + processor.getDescription() + "'");
            //for some reason the following code causes problems with in correct sized messages - likely a bug in Axis
            /*
            if(manipulatedMessage instanceof SOAPMessage) {
              try{
                SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                if(sm.getSOAPWireMessage().saveRequired()) {
                  sm.getSOAPWireMessage().saveChanges();
                  mLogger.log(Level.WARNING,"Processor described as '" + processor.getDescription() + "' did not save its changes");
                }
              }
              catch(javax.xml.soap.SOAPException ex) {
                //best efforts
              }
            }
            */
        }
        //have to make sure that the message is ready for wire transfer
        if(manipulatedMessage instanceof SOAPMessage) {
            SOAPMessage sm = (SOAPMessage) manipulatedMessage;
            SOAPMessage sMsg = new SOAPMessage(sm.getSOAPWireMessage(),sm.getMessageContext());

            manipulatedMessage = sMsg;
        }
        /*
        Check other message types are correct for wire transfer too.
        */
        return manipulatedMessage;
    }

    private static int mMessageCount = 0;

    /**
     * Enforces policies associated with incoming messages
     * policy container
     * @param serviceDescription describes how to invoke the service
     * @param message message to check
     * @return modified message
     * @throws MessagingException
     */
    public Message enforceIncomingMessage(ServiceDescription serviceDesc, Message message) throws MessagingException {
        //only go through whole chain if a processor exists for all the policy items
      List chain = new ArrayList();
        //grab the incoming policy list
        ProcessorProviderRegistry providerRegistry = ProcessorProviderRegistry.getInstance(mConfiguration);
        Policy[] incomingPolicies = serviceDesc.getServicePolicy().getOrderedIncomingPolicies();
        Message manipulatedMessage = message;

        //if any of the policy descriptors does not have a matching messageprocessor then a MessagingException
        //is thrown and the chain is not completed - all or nothing here!
        PolicyTypeDescriptor noPolicyDescriptor = (new NoPolicyImp()).getPolicyTypeDescriptor();
        //add the static policy list as the first policies in the incoming chain
        if(mIncludeStaticPolicies) {
          if(staticInputPolicies!=null) {
            Iterator outIt = staticInputPolicies.iterator();
            while(outIt.hasNext()) {
              Policy policy = (Policy) outIt.next();
              PolicyTypeDescriptor descriptor = policy.getPolicyTypeDescriptor();
              if(!descriptor.isEqual(noPolicyDescriptor)) {
                MessageProcessorProviderSpi provider = providerRegistry.getProvider(descriptor);
                provider.setSignalHandler(mSignalHandler);
                provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
                MessageProcessor processor = provider.getMessageProcessor(descriptor);
                chain.add(new PolicyProcessorPair(policy,processor));
              }
            }
          }
        }
        //add the client assigned policies

        for(int i=0;i<incomingPolicies.length;i++) {
            //for each find a message processor provider that supports the policy, except the special NoPolicy

            PolicyTypeDescriptor descriptor = incomingPolicies[i].getPolicyTypeDescriptor();
            if(!descriptor.isEqual(noPolicyDescriptor)) {
                MessageProcessorProviderSpi provider = providerRegistry.getProvider(descriptor);
                provider.setSignalHandler(mSignalHandler);
                provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
                MessageProcessor processor = provider.getMessageProcessor(descriptor);
                chain.add(new PolicyProcessorPair(incomingPolicies[i],processor));
            }
        }

        //does the message have attachments
        boolean hasAttachments = false;
        if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                    SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                    if(sm.getSOAPWireMessage().getAttachments().hasNext())
                      hasAttachments = true;
                }
                catch(Exception ex) {
                  //best efforts only
                }
              }

        //execute the chain of processors
        Iterator iterator = chain.iterator();
        int procCount = 0;
        while(iterator.hasNext()) {
          PolicyProcessorPair pair = (PolicyProcessorPair) iterator.next();
          Policy policy = (Policy) pair.getPolicy();
          //MessageProcessor processor = (MessageProcessor) iterator.next();
            MessageProcessor processor = pair.getMessageProcessor();

            Message tmpMsg = manipulatedMessage;
            if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                  SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                  if(hasAttachments) {
                    Iterator it = sm.getSOAPWireMessage().getAttachments();
                    while(it.hasNext()) {
                      javax.xml.soap.AttachmentPart part = (javax.xml.soap.AttachmentPart) it.next();
                      mLogger.log(Level.FINE,"Before processor '" + processor.getDescription() + "', attachment part '" + part.getContentId() + "' has " + part.getDataHandler().getInputStream().available() + " bytes available");
                    }
                  }
                }
                catch(Exception ex) {
                  //best efforts only
                }
              }

            //enforce the message
            mLogger.log(Level.FINE,"Executing incoming chain processor '" + processor.getDescription() + "' ...");
            manipulatedMessage = processor.enforceMessage(serviceDesc,policy,manipulatedMessage);
            if(manipulatedMessage==null) {
                mLogger.log(Level.SEVERE,"Processor described as '" + processor.getDescription() + "' has generated an invalid message object, stopping invocation");
                throw new MessagingException("Unable to invoke service because the service has a policy that is not supported","Processor described as '" + processor.getDescription() + "' has generated an invalid message object, stopping invocation");
            }


              if(manipulatedMessage instanceof SOAPMessage) {
                try{
                  //print out the soap message part
                  SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                  if(hasAttachments) {
                    Iterator it = sm.getSOAPWireMessage().getAttachments();
                    while(it.hasNext()) {
                      javax.xml.soap.AttachmentPart part = (javax.xml.soap.AttachmentPart) it.next();
                      mLogger.log(Level.FINE,"After processor '" + processor.getDescription() + "', attachment part '" + part.getContentId() + "' has " + part.getDataHandler().getInputStream().available() + " bytes available");
                    }
                  }

                }
                catch(Exception ex) {
                  //best efforts only
                }
              }


            //release previous message
            if(tmpMsg!=manipulatedMessage)
              tmpMsg.release();
            mLogger.log(Level.FINE,"Completed executing processor '" + processor.getDescription() + "'");
            //for some reason the following code causes problems with in correct sized messages - likely a bug in Axis

            if(manipulatedMessage instanceof SOAPMessage) {
              try{
                SOAPMessage sm = (SOAPMessage) manipulatedMessage;
                if(sm.getSOAPWireMessage().saveRequired()) {
                  sm.getSOAPWireMessage().saveChanges();
                  mLogger.log(Level.WARNING,"Processor described as '" + processor.getDescription() + "' did not save its changes");
                }
              }
              catch(javax.xml.soap.SOAPException ex) {
                //best efforts
              }
            }

        }
        //have to make sure that the message is ready for wire transfer
        if(manipulatedMessage instanceof SOAPMessage) {
            SOAPMessage sm = (SOAPMessage) manipulatedMessage;
            SOAPMessage sMsg = new SOAPMessage(sm.getSOAPWireMessage(),sm.getMessageContext());

            manipulatedMessage = sMsg;
        }
        return manipulatedMessage;
    }


    private void loadStaticPolicyChains(String enforcementFile) throws MessagingException {
      try{
        //this method loads the static message enforcement policy file.
        //it is later combined with the client supplied policy values
        //load the chain information
        org.jdom.input.SAXBuilder sBuilder = new org.jdom.input.SAXBuilder();	//don't use syntax verification
        org.jdom.Document doc = sBuilder.build(new File(enforcementFile));

        staticInputPolicies = new ArrayList();
        staticOutputPolicies = new ArrayList();

        org.jdom.Element rootE = doc.getRootElement();
        List children = rootE.getChildren("message-processor-incoming-chain");
        Iterator iterator = children.iterator();
        while(iterator.hasNext()) {
          //deal with the request chain
          org.jdom.Element inChain = (org.jdom.Element) iterator.next();
          List policyList = inChain.getChildren("message-policy");
          Iterator it = policyList.iterator();
          while(it.hasNext()) {
            org.jdom.Element policyE = (org.jdom.Element) it.next();
            staticInputPolicies.add(loadPolicy(policyE.getChildText("policy-classname")));
          }
        }
        children = rootE.getChildren("message-processor-outgoing-chain");
        iterator = children.iterator();
        while(iterator.hasNext()) {
          org.jdom.Element outChain = (org.jdom.Element) iterator.next();
          List policyList = outChain.getChildren("message-policy");
          Iterator it = policyList.iterator();
          while(it.hasNext()) {
            org.jdom.Element policyE = (org.jdom.Element) it.next();
            staticOutputPolicies.add(loadPolicy(policyE.getChildText("policy-classname")));
          }
        }
      }
      catch(org.jdom.JDOMException ex) {
        mLogger.log(Level.SEVERE,"Failure loading static policy file",ex);
        throw new MessagingException("Failure loading static policy file");

      }
    }

    private Policy loadPolicy(String classname) throws MessagingException{
        try{
            //create an instance of the required class
            //going to attempt to load the class dynamically
            Class providerClass = Class.forName(classname);
            Policy policy = (Policy) providerClass.newInstance();
            return policy;
        }
     catch (InstantiationException e) {
            mLogger.log(Level.SEVERE,"Can't instantiate policy with classname '" + classname +"'",e);
            throw new MessagingException("Couldn't load implementation for policy with classname '" + classname +"'");
        } catch (IllegalAccessException e) {
            mLogger.log(Level.SEVERE,"Not allowed to instantiate policy with classname '" + classname +"'",e);
            throw new MessagingException("Couldn't load implementation for policy with classname '" + classname +"'");
        } catch (ClassNotFoundException e) {
            mLogger.log(Level.SEVERE,"Couldn't locate class for policy with classname '" + classname +"', please check classpath for class '" + classname + "'",e);
            throw new MessagingException("Couldn't locate class for policy with classname '" + classname +"', please check classpath for class '" + classname + "'");
        } catch(ClassCastException e) {
            mLogger.log(Level.SEVERE,"Failed to load implementation for policy with classname '" + classname + "'");
            throw new MessagingException("Failed to load implementation for policy with classname '" + classname + "'");
        }
    }


}