/////////////////////////////////////////////////////////////////////////
//
// © 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.security.context.token.trust;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import eu.gemss.components.security.token.types.UnsupportedTokenException;
import eu.gemss.components.security.token.TokenDescriptor;
import uk.ac.soton.itinnovation.gemss.security.context.configuration.SecurityContextConfiguration;
import eu.gemss.components.security.token.trust.*;
/**
 * TrustProviderRegistry loads the configured provider list and will load providers dynamically using entries
 * within that list.
 */
public class TrustProviderRegistry {

    private static final String CONFIG_FILE = "security_token_trust_providers.xml";
    private static final String SYS_PROP_NAME = "gemss.security.token.provider.list.file";
    private static TrustProviderRegistry mRegistry = null;
    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.security.context.token.trust.TrustProviderRegistry");

    private SecurityContextConfiguration mConfiguration;
    private static Map mTrustProviderClassNames;


        /**
         * Default constructor takes no parameters but does initialise the TrustProviderRegistry.
         * @throws TrustProviderException
         */
        private TrustProviderRegistry(SecurityContextConfiguration config) throws TrustProviderException {
	    mConfiguration = config;
            if(mTrustProviderClassNames==null)
                init();
	}

        /**
         * Retrieve a registry for obtaining trust providers
         * @param config
         * @return
         * @throws TrustProviderException
         */
        public static synchronized TrustProviderRegistry getInstance(SecurityContextConfiguration config) throws TrustProviderException {
          if(mRegistry==null) {
            mRegistry = new TrustProviderRegistry(config);
          }
          return mRegistry;
        }

        /**
         * Retrieves the SecurityTrustProvider matching a particular provider name
         * @param security token description
         * @return security provider instance
         * @throws TrustProviderException
         */
        public SecurityTrustProvider getProvider(TokenDescriptor tokenDesc) throws TrustProviderException, UnsupportedTokenException {
            //this method manages a cache of TokenDescriptor classname mappings and
            //also creates a provider instance when supplied a supported token descriptor.
            SecurityTrustProvider provider = null;
            String providerClassName = null;

            try {
                //first lookup classname in token descriptor classname mappings
                Set set = mTrustProviderClassNames.keySet();
                Iterator iterator = set.iterator();
                while(iterator.hasNext()) {
                    TokenDescriptor tD = (TokenDescriptor) iterator.next();
                    if(tD.isEqual(tokenDesc))
                        providerClassName = (String) mTrustProviderClassNames.get(tD);
                }
                if(providerClassName==null) {
                    //could not find token descriptor, therefore it is not
                    //supported
                    mLogger.log(Level.WARNING,"No trust provider exists for the token described as '" + tokenDesc.getDescription() + "'");
                    throw new UnsupportedTokenException("No trust provider exists for the token described as '" + tokenDesc.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.
                //set the provider configuration
                provider.setTrustProviderConfiguration(this.mConfiguration.getSecurityProviderConfiguration());

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

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

        protected void loadConfiguration() throws TrustProviderException {

            //Loads the configuration information for the TrustProviderRegistry. 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
                File file = new File(mConfiguration.getSecurityTrustProviderFilePath());
                if(!file.exists())
                    throw new TrustProviderException("Configuration file '" + file.getAbsoluteFile() + "' does not exist");
                Document doc = sBuilder.build(file);
                //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");
                    SecurityTrustProvider provider = loadProvider(providerClass);
                    //get all the token descriptors for this provider and add to the list if not there
                    TokenDescriptor[] descriptors = provider.getSupportedTokenTrustList();
                    for(int i=0;i<descriptors.length;i++) {
                        if(!tokenPresent(descriptors[i])) {
                            mLogger.log(Level.INFO,"Associating described as '" + descriptors[i].getDescription() + "' with trust provider class '" + providerClass + "'");
                            mTrustProviderClassNames.put(descriptors[i],providerClass);
                        }
                    }
                }

            }
            catch(JDOMException ex) {
                mLogger.log(Level.SEVERE,"Unable to parse provider list xml",ex);
                throw new TrustProviderException(ex.getMessage());
            }
            catch(Exception ex) {
                if(ex instanceof TrustProviderException) {
                    throw (TrustProviderException) ex;
                }
                else {

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

	}

	protected boolean tokenPresent(TokenDescriptor tokenDesc) {
		Set set = mTrustProviderClassNames.keySet();
		Iterator iterator = set.iterator();
		while(iterator.hasNext()) {
			TokenDescriptor tD = (TokenDescriptor) iterator.next();
			if(tD.equals(tokenDesc)) {
				return true;
			}
		}
		return false;
	}

        protected SecurityTrustProvider loadProvider(String providerClassName) throws TrustProviderException {
            SecurityTrustProvider 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 = (SecurityTrustProvider) providerClass.newInstance();
            } catch (InstantiationException e) {
                mLogger.log(Level.SEVERE,"Can't instantiate trust provider with classname '" + providerClassName +"'",e);
                throw new TrustProviderException("Couldn't load implementation for trust provider with classname '" + providerClassName +"'");
            } catch (IllegalAccessException e) {
                mLogger.log(Level.SEVERE,"Not allowed to instantiate trust provider with classname '" + providerClassName +"'",e);
                throw new TrustProviderException("Couldn't load implementation for trust provider with classname '" + providerClassName +"'");
            } catch (ClassNotFoundException e) {
                mLogger.log(Level.SEVERE,"Couldn't locate class for trust provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'",e);
                throw new TrustProviderException("Couldn't locate class for security token provider with classname '" + providerClassName +"', please check classpath for class '" + providerClassName + "'");
            }
            if(provider==null) {
                mLogger.log(Level.SEVERE,"Failed to load implementation for trust provider with classname '" + providerClassName + "'");
                throw new TrustProviderException("Failed to load implementation for trust provider with classname '" + providerClassName + "'");
            }
            return provider;

        }


}