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

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

import eu.gemss.components.transport.servicedescription.policytypes.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import org.jdom.*;
import org.jdom.input.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.configuration.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.processing.*;
/**
 * ProcessorProviderRegistry loads the configured provider list and will load providers dynamically using entries
 * within that list.
 */
public class ProcessorProviderRegistry {

    private static final String CONFIG_FILE = "message_processor_providers.xml";
    private static final String SYS_PROP_NAME = "gemss.message.processor.provider.list.file";

    private static ProcessorProviderRegistry mRegistry;
    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.processing.ProcessorProviderRegistry");
    private TransportMessagingConfiguration mConfiguration;

    private Map mProcessorProviderClassNames;


        /**
         * Default constructor takes no parameters but does initialise the ProcessorProviderRegistry.
         * @throws ProviderException
         */
        private ProcessorProviderRegistry(TransportMessagingConfiguration config) throws MessageProcessingException {
	    mConfiguration = config;
            if(mProcessorProviderClassNames==null)
                init();
	}

        /**
         * Retrieve a registry for obtaining message processors
         * @param config
         * @return
         * @throws MessageProcessingException
         */
        public static synchronized ProcessorProviderRegistry getInstance(TransportMessagingConfiguration config) throws MessageProcessingException {
          if(mRegistry==null)
            mRegistry = new ProcessorProviderRegistry(config);
          return mRegistry;
        }

        /**
         * Retrieves the MessageProcessorProvider matching a particular provider name
         * @param policy type description
         * @return provider instance
         * @throws MessageProcessingException
         */
        public MessageProcessorProviderSpi getProvider(PolicyTypeDescriptor typeDesc) throws MessageProcessingException{
            //this method manages a cache of typedescriptor classname mappings and
            //also creates a provider instance when supplied a supported token descriptor.
            MessageProcessorProviderSpi provider = null;
            String providerClassName = null;

            try {
                //first lookup classname in token descriptor classname mappings
                Set set = mProcessorProviderClassNames.keySet();
                Iterator iterator = set.iterator();
                while(iterator.hasNext()) {
                    PolicyTypeDescriptor tD = (PolicyTypeDescriptor) iterator.next();
                    mLogger.log(Level.INFO,"Comparing policy '" + tD.getDescription() + "' with '" + typeDesc.getDescription() + "'");
                    if(tD.isEqual(typeDesc))
                        providerClassName = (String) mProcessorProviderClassNames.get(tD);
                }
                if(providerClassName==null) {
                    //could not find token descriptor, therefore it is not
                    //supported
                    mLogger.log(Level.WARNING,"No provider exists for the policy type described as '" + typeDesc.getDescription() + "'");
                    throw new MessageProcessingException("No provider exists for the policy type described as '" + typeDesc.getDescription() + "'");
                }

                //create an instance of the required class
                //going to attempt to load the class dynamically
                provider = loadProvider(providerClassName);
                //no reference to this provider instance is held here, this should mean the garbage collector
                //removes unused instances quicker.

            } catch (Exception ex) {
                if(ex instanceof MessageProcessingException)
                    throw (MessageProcessingException) ex;
                else if(ex instanceof MessageProcessingException)
                    throw (MessageProcessingException) ex;
                else {
                    mLogger.log(Level.WARNING,"Can't get processor provider for policy described as '" + typeDesc.getDescription() +"' for unknown reason, please send a bug report including all log files.",ex);
                    throw new MessageProcessingException("Can't get processor for policy described as '" + typeDesc.getDescription() +"' for unknown reason, please send a bug report including all log files.");
                }
            }
            return provider;
        }

	protected void init() throws MessageProcessingException {
            //load the provider list from the configuration file.
            mProcessorProviderClassNames = new HashMap();
            loadConfiguration();
	}

        protected void loadConfiguration() throws MessageProcessingException {

            //Loads the configuration information for the ProviderRegistry. It does
            //this by loading the 'providers.xml' file. It then creates a map containing all token descriptors and
            //their relevant provider classname.

            try{
                SAXBuilder sBuilder = new SAXBuilder();	//don't use syntax verification
                Document doc = sBuilder.build(new File(mConfiguration.getMessageProcessorProviderFilePath()));
                //Grab each provider description and load the providers token types
                Element rootE = doc.getRootElement();
                List children = rootE.getChildren("provider");
                Iterator iterator = children.iterator();
                while(iterator.hasNext()) {
                    Element mapping = (Element) iterator.next();
                    String name = mapping.getChildText("provider-name");
                    String providerClass = mapping.getChildText("provider-classname");
                    MessageProcessorProviderSpi provider = loadProvider(providerClass);
                    //get all the interface type descriptors for this provider and add to the list if not there
                    PolicyTypeDescriptor[] descriptors = provider.getSupportedPolicyTypeList();
                    for(int i=0;i<descriptors.length;i++) {
                        if(!typePresent(descriptors[i])) {
                            mLogger.log(Level.INFO,"Associating policy type described as '" + descriptors[i].getDescription() + "' with provider class '" + providerClass + "'");
                            mProcessorProviderClassNames.put(descriptors[i],providerClass);
                        }
                    }
                    //set the provider configuration
                    provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
                }
            }
            catch(JDOMException ex) {
                mLogger.log(Level.SEVERE,"Unable to parse provider list xml",ex);
                throw new MessageProcessingException(ex.getMessage());
            }
            catch(Exception ex) {
                if(ex instanceof MessageProcessingException) {
                    throw (MessageProcessingException) ex;
                }
                else {

                    mLogger.log(Level.SEVERE,"Unable to load provider configuration",ex);
                    throw new MessageProcessingException(ex.getMessage());
                }
            }
            finally {
                //I would close the stream if JDOM wasn't handling the stream - I wonder if JDOM closes the stream.
            }
	}

	protected boolean typePresent(PolicyTypeDescriptor tokenDesc) {
		Set set = mProcessorProviderClassNames.keySet();
		Iterator iterator = set.iterator();
		while(iterator.hasNext()) {
			PolicyTypeDescriptor tD = (PolicyTypeDescriptor) iterator.next();
			if(tD.isEqual(tokenDesc)) {
				return true;
			}
		}
		return false;
	}

        protected MessageProcessorProviderSpi loadProvider(String providerClassName) throws MessageProcessingException {
            MessageProcessorProviderSpi provider = null;
            try{
                mLogger.log(Level.INFO,"Loading provider with classname '" + providerClassName + "'");
                //create an instance of the required class
                //going to attempt to load the class dynamically
                Class providerClass = Class.forName(providerClassName);
                provider = (MessageProcessorProviderSpi) providerClass.newInstance();
            } catch (InstantiationException e) {
                mLogger.log(Level.SEVERE,"Can't instantiate policy type provider with classname '" + providerClassName +"'",e);
                throw new MessageProcessingException("Couldn't load implementation for policy type provider with classname '" + providerClassName +"'");
            } catch (IllegalAccessException e) {
                mLogger.log(Level.SEVERE,"Not allowed to instantiate policy type provider with classname '" + providerClassName +"'",e);
                throw new MessageProcessingException("Couldn't load implementation for policy type provider with classname '" + providerClassName +"'");
            } catch (ClassNotFoundException e) {
                mLogger.log(Level.SEVERE,"Couldn't locate class for policy type provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'",e);
                throw new MessageProcessingException("Couldn't locate class for policy type provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'");
            } catch(ClassCastException e) {
                mLogger.log(Level.SEVERE,"Failed to load implementation for policy type provider with classname '" + providerClassName + "', because incorrect type of class found",e);
                throw new MessageProcessingException("Failed to load implementation for policy type provider with classname '" + providerClassName + "'");
            }

            if(provider==null) {
                mLogger.log(Level.SEVERE,"Failed to load implementation for policy type provider with classname '" + providerClassName + "', because class creation produced no class");
                throw new MessageProcessingException("Failed to load implementation for policy type provider with classname '" + providerClassName + "'");
            }
            return provider;

        }


}