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

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.ArrayList;
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.store.*;
/**
 * StoreProviderRegistry loads the configured provider list and will load providers dynamically using entries
 * within that list.
 */
public class StoreProviderRegistry {

    //private static final String CONFIG_FILE = "security_token_store_providers.xml";

    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.security.context.token.trust.StoreProviderRegistry");
    private static StoreProviderRegistry mRegistry;
    private SecurityContextConfiguration mConfiguration;
    private static Map mStoreProviderClassNames;


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

        /**
         * Retrieve a registry for obtaining token stores
         * @param config
         * @return
         * @throws StoreProviderException
         */
        public static synchronized StoreProviderRegistry getInstance(SecurityContextConfiguration config) throws StoreProviderException {
          if(mRegistry==null) {
            mRegistry = new StoreProviderRegistry(config);
          }
          return mRegistry;
        }

        /**
         * Retrieves the SecurityTokenStoreProvider matching a particular provider name
         * @param security token description
         * @return security provider instance
         * @throws StoreProviderException
         */
        public SecurityTokenStoreProvider[] getProvider(TokenDescriptor tokenDesc) throws StoreProviderException, UnsupportedTokenException {
            //this method manages a cache of classname mappings and
            //also creates a provider instance when supplied a supported token descriptor.
            SecurityTokenStoreProvider provider = null;
            String providerClassName = null;
            List providers = new ArrayList();
            try {
                //first lookup classname in  classname mappings
                Set set = mStoreProviderClassNames.keySet();
                Iterator iterator = set.iterator();
                while(iterator.hasNext()) {
                    TokenDescriptor tD = (TokenDescriptor) iterator.next();
                    if(tD.isEqual(tokenDesc)) {
                        providerClassName = (String) mStoreProviderClassNames.get(tD);
                        //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.setStoreProviderConfiguration(this.mConfiguration.getSecurityProviderConfiguration());
                        providers.add(provider);
                    }
                }
                if(providerClassName==null) {
                    //could not find token provider, therefore it is not
                    //supported
                    mLogger.log(Level.WARNING,"No store provider exists for the token described as '" + tokenDesc.getDescription() + "'");
                    throw new UnsupportedTokenException("No store provider exists for the token described as '" + tokenDesc.getDescription() + "'");
                }
                //return the providers
                iterator = providers.iterator();
                SecurityTokenStoreProvider[] provs = new SecurityTokenStoreProvider[providers.size()];
                int index = 0;
                while(iterator.hasNext()) {
                    SecurityTokenStoreProvider prov = (SecurityTokenStoreProvider) iterator.next();
                    provs[index++] = prov;
                }
                return provs;

            } catch (Exception ex) {
                if(ex instanceof StoreProviderException)
                    throw (StoreProviderException) 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 StoreProviderException("Can't instantiate trust provider described as '" + tokenDesc.getDescription() +"' for unknown reason, please send a bug report including all log files.");
                }
            }
        }

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

        protected void loadConfiguration() throws StoreProviderException {

            //Loads the configuration information for the StoreProviderRegistry. 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.getSecurityStoreProviderFilePath());
                if(!file.exists())
                    throw new StoreProviderException("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");
                    SecurityTokenStoreProvider provider = loadProvider(providerClass);
                    //get all the token descriptors for this provider and add to the list if not there
                    TokenDescriptor[] descriptors = provider.getSupportedTokenList();
                    for(int i=0;i<descriptors.length;i++) {
                        if(!tokenPresent(descriptors[i])) {
                            mLogger.log(Level.INFO,"Associating token described as '" + descriptors[i].getDescription() + "' with store provider class '" + providerClass + "'");
                            mStoreProviderClassNames.put(descriptors[i],providerClass);
                        }
                    }
                }

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

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

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

        }


}