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

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

import eu.gemss.components.transport.servicedescription.interfacetypes.*;
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.*;

/**
 * CoDecProviderRegistry loads the configured provider list and will load providers dynamically using entries
 * within that list.
 */
public class CoDecProviderRegistry {

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

    private static Map mCoDecProviderClassNames;


        /**
         * Default constructor takes no parameters but does initialise the ProviderRegistry.
         * @throws MessageGenerationException
         */
        private CoDecProviderRegistry(TransportMessagingConfiguration config) throws MessageCoDecException {
	    mConfiguration = config;
            if(mCoDecProviderClassNames==null)
                init();
	}

        /**
         * Retrieve a registry for obtaining codec providers
         * @param config
         * @return
         * @throws MessageCoDecException
         */
        public static synchronized CoDecProviderRegistry getInstance(TransportMessagingConfiguration config) throws MessageCoDecException {
          if(mRegistry==null) {
            mRegistry = new CoDecProviderRegistry(config);
          }
          return mRegistry;
        }

        /**
         * Retrieves the MessageCoDecProvider matching a particular provider name
         * @param interface type description
         * @return provider instance
         * @throws MessageGenerationException
         */
        public MessageCoDecProvider getProvider(InterfaceTypeDescriptor typeDesc) throws MessageCoDecException{
            if(typeDesc==null) {
                mLogger.log(Level.WARNING,"Can't instantiate security token because the supplied type description is null");
                throw new MessageCoDecException("Can't instantiate security token because the supplied type description is null");
            }
            //this method manages a cache of typedescriptor classname mappings and
            //also creates a provider instance when supplied a supported token descriptor.
            MessageCoDecProvider provider = null;
            String providerClassName = null;

            try {
                //first lookup classname in token descriptor classname mappings
                Set set = mCoDecProviderClassNames.keySet();
                Iterator iterator = set.iterator();
                while(iterator.hasNext()) {
                    InterfaceTypeDescriptor tD = (InterfaceTypeDescriptor) iterator.next();
                    if(tD.isEqual(typeDesc))
                        providerClassName = (String) mCoDecProviderClassNames.get(tD);
                }
                if(providerClassName==null) {
                    //could not find token descriptor, therefore it is not
                    //supported
                    mLogger.log(Level.WARNING,"No provider exists for the interface type described as '" + typeDesc.getDescription() + "'");
                    throw new MessageCoDecException("No provider exists for the interface 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 MessageCoDecException)
                    throw (MessageCoDecException) ex;
                else if(ex instanceof MessageCoDecException)
                    throw (MessageCoDecException) ex;
                else {
                    mLogger.log(Level.WARNING,"Can't instantiate security token described as '" + typeDesc.getDescription() +"' for unknown reason, please send a bug report including all log files.",ex);
                    throw new MessageCoDecException("Can't instantiate security token described as '" + typeDesc.getDescription() +"' for unknown reason, please send a bug report including all log files.");
                }
            }
            return provider;
        }

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

        protected void loadConfiguration() throws MessageCoDecException {

            //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.getMessageCoDecProviderFilePath()));
                //Grab each provider description and load the providers token types
                Element rootE = doc.getRootElement();
                List children = rootE.getChildren("provider");
                if(children.size()==0) {
                  mLogger.log(Level.SEVERE,"There are no CoDec providers, invocation is not possible. Please check the file 'message_codec_providers.xml'");
                  throw new MessageCoDecException("There are no CoDec providers, invocation is not possible. Please check the file 'message_codec_providers.xml'");
                }
                Iterator iterator = children.iterator();
                while(iterator.hasNext()) {
                    Element mapping = (Element) iterator.next();
                    String name = mapping.getChildText("provider-name");
                    String providerClass = mapping.getChildText("provider-classname");
                    MessageCoDecProvider provider = loadProvider(providerClass);
                    //get all the interface type descriptors for this provider and add to the list if not there
                    InterfaceTypeDescriptor[] descriptors = provider.getSupportedInterfaceTypeList();
                    for(int i=0;i<descriptors.length;i++) {
                        if(!typePresent(descriptors[i])) {
                            mLogger.log(Level.INFO,"Associating interface type described as '" + descriptors[i].getDescription() + "' with provider class '" + providerClass + "'");
                            mCoDecProviderClassNames.put(descriptors[i],providerClass);
                        }
                    }
                }
            }
            catch(JDOMException ex) {
                mLogger.log(Level.SEVERE,"Unable to parse provider list xml",ex);
                throw new MessageCoDecException(ex.getMessage());
            }
            catch(Exception ex) {
                if(ex instanceof MessageCoDecException) {
                    throw (MessageCoDecException) ex;
                }
                else {

                    mLogger.log(Level.SEVERE,"Unable to load provider configuration",ex);
                    throw new MessageCoDecException(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(InterfaceTypeDescriptor tokenDesc) {
		Set set = mCoDecProviderClassNames.keySet();
		Iterator iterator = set.iterator();
		while(iterator.hasNext()) {
			InterfaceTypeDescriptor tD = (InterfaceTypeDescriptor) iterator.next();
			if(tD.isEqual(tokenDesc)) {
				return true;
			}
		}
		return false;
	}

        protected MessageCoDecProvider loadProvider(String providerClassName) throws MessageCoDecException {
            MessageCoDecProvider 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 = (MessageCoDecProvider) providerClass.newInstance();
                //set the provider configuration
                provider.setMessagingProviderConfiguration(mConfiguration.getMessagingProviderConfiguration());
            } catch (InstantiationException e) {
                mLogger.log(Level.SEVERE,"Can't instantiate interface type provider with classname '" + providerClassName +"'",e);
                throw new MessageCoDecException("Couldn't load implementation for interface type provider with classname '" + providerClassName +"'");
            } catch (IllegalAccessException e) {
                mLogger.log(Level.SEVERE,"Not allowed to instantiate interface type provider with classname '" + providerClassName +"'",e);
                throw new MessageCoDecException("Couldn't load implementation for interface type provider with classname '" + providerClassName +"'");
            } catch (ClassNotFoundException e) {
                mLogger.log(Level.SEVERE,"Couldn't locate class for interface type provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'",e);
                throw new MessageCoDecException("Couldn't locate class for interface type provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'");
            }
            catch(ClassCastException e) {
                mLogger.log(Level.SEVERE,"Provider given classname '" + providerClassName +"' is not a provider, please check classpath for class '" + providerClassName + "'",e);
                throw new MessageCoDecException("Provider given classname '" + providerClassName +"' is not a provider, please check classpath for class '" + providerClassName + "'");
            }
            if(provider==null) {
                mLogger.log(Level.SEVERE,"Failed to load implementation for interface type provider with classname '" + providerClassName + "'");
                throw new MessageCoDecException("Failed to load implementation for interface type provider with classname '" + providerClassName + "'");
            }
            return provider;

        }


}