package uws.config;

/*
 * This file is part of UWSLibrary.
 *
 * UWSLibrary is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * UWSLibrary is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2016-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

import static uws.config.UWSConfiguration.DEFAULT_BACKUP_BY_USER;
import static uws.config.UWSConfiguration.DEFAULT_BACKUP_FREQUENCY;
import static uws.config.UWSConfiguration.DEFAULT_DIRECTORY_PER_USER;
import static uws.config.UWSConfiguration.DEFAULT_GROUP_USER_DIRECTORIES;
import static uws.config.UWSConfiguration.DEFAULT_LOGGER;
import static uws.config.UWSConfiguration.DEFAULT_UWS_CONF_FILE;
import static uws.config.UWSConfiguration.KEY_ADD_SERIALIZERS;
import static uws.config.UWSConfiguration.KEY_ADD_UWS_ACTIONS;
import static uws.config.UWSConfiguration.KEY_BACKUP_BY_USER;
import static uws.config.UWSConfiguration.KEY_BACKUP_FREQUENCY;
import static uws.config.UWSConfiguration.KEY_DESTRUCTION_MANAGER;
import static uws.config.UWSConfiguration.KEY_DIRECTORY_PER_USER;
import static uws.config.UWSConfiguration.KEY_ERROR_WRITER;
import static uws.config.UWSConfiguration.KEY_EXECUTION_MANAGER;
import static uws.config.UWSConfiguration.KEY_FILE_MANAGER;
import static uws.config.UWSConfiguration.KEY_FILE_ROOT_PATH;
import static uws.config.UWSConfiguration.KEY_GROUP_USER_DIRECTORIES;
import static uws.config.UWSConfiguration.KEY_HOME_PAGE;
import static uws.config.UWSConfiguration.KEY_HOME_PAGE_MIME_TYPE;
import static uws.config.UWSConfiguration.KEY_JOB_LISTS;
import static uws.config.UWSConfiguration.KEY_LOGGER;
import static uws.config.UWSConfiguration.KEY_LOG_ROTATION;
import static uws.config.UWSConfiguration.KEY_MAX_RUNNING_JOBS;
import static uws.config.UWSConfiguration.KEY_MIN_LOG_LEVEL;
import static uws.config.UWSConfiguration.KEY_SERVICE_DESCRIPTION;
import static uws.config.UWSConfiguration.KEY_SERVICE_NAME;
import static uws.config.UWSConfiguration.KEY_USER_IDENTIFIER;
import static uws.config.UWSConfiguration.KEY_UWS_FACTORY;
import static uws.config.UWSConfiguration.KEY_XSLT_STYLESHEET;
import static uws.config.UWSConfiguration.REGEXP_JOB_LIST_NAME;
import static uws.config.UWSConfiguration.SLF4J_LOGGER;
import static uws.config.UWSConfiguration.UWS_CONF_PARAMETER;
import static uws.config.UWSConfiguration.VALUE_LOCAL;
import static uws.config.UWSConfiguration.VALUE_NEVER;
import static uws.config.UWSConfiguration.VALUE_USER_ACTION;
import static uws.config.UWSConfiguration.getProperty;
import static uws.config.UWSConfiguration.hasConstructor;
import static uws.config.UWSConfiguration.isClassName;
import static uws.config.UWSConfiguration.newInstance;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import uws.UWSException;
import uws.job.JobList;
import uws.job.manager.DefaultDestructionManager;
import uws.job.manager.DefaultExecutionManager;
import uws.job.manager.DestructionManager;
import uws.job.manager.ExecutionManager;
import uws.job.manager.QueuedExecutionManager;
import uws.job.serializer.UWSSerializer;
import uws.job.serializer.XMLSerializer;
import uws.service.UWSFactory;
import uws.service.UWSService;
import uws.service.UserIdentifier;
import uws.service.actions.ShowHomePage;
import uws.service.actions.UWSAction;
import uws.service.backup.DefaultUWSBackupManager;
import uws.service.backup.UWSBackupManager;
import uws.service.error.ServiceErrorWriter;
import uws.service.file.LocalUWSFileManager;
import uws.service.file.UWSFileManager;
import uws.service.log.DefaultUWSLog;
import uws.service.log.Slf4jUWSLog;
import uws.service.log.UWSLog;
import uws.service.log.UWSLog.LogLevel;

/**
 * <p>HTTP servlet fully configured with a UWS configuration file.</p>
 *
 * <p>
 * 	This configuration file may be specified in the initial parameter named
 * 	{@link UWSConfiguration#UWS_CONF_PARAMETER} of this servlet inside the
 * 	WEB-INF/web.xml file. If none is specified, the file
 * 	{@link UWSConfiguration#DEFAULT_UWS_CONF_FILE} will be searched inside the
 * 	directories of the classpath, and inside WEB-INF and META-INF.
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 4.3 (07/2018)
 * @since 4.2
 */
public class ConfigurableUWSServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/** UWS object representing the UWS service. */
	private UWSService uws = null;

	@Override
	public void init(final ServletConfig config) throws ServletException{
		// Nothing to do, if UWS is already initialized:
		if (uws != null)
			return;

		/* 1. GET THE FILE PATH OF THE UWS CONFIGURATION FILE */
		String uwsConfPath = config.getInitParameter(UWS_CONF_PARAMETER);
		if (uwsConfPath == null || uwsConfPath.trim().length() == 0)
			uwsConfPath = null;
		//throw new ServletException("Configuration file path missing! You must set a servlet init parameter whose the name is \"" + UWS_CONF_PARAMETER + "\".");

		/* 2. OPEN THE CONFIGURATION FILE */
		InputStream input = null;
		// CASE: No file specified => search in the classpath for a file having the default name "uws.properties".
		if (uwsConfPath == null)
			input = searchFile(DEFAULT_UWS_CONF_FILE, config);
		else{
			File f = new File(uwsConfPath);
			// CASE: The given path matches to an existing local file.
			if (f.exists()){
				try{
					input = new FileInputStream(f);
				}catch(IOException ioe){
					throw new ServletException("Impossible to read the UWS configuration file (" + uwsConfPath + ")!", ioe);
				}
			}
			// CASE: The given path seems to be relative to the servlet root directory.
			else
				input = searchFile(uwsConfPath, config);
		}
		// If no file has been found, cancel the servlet loading:
		if (input == null)
			throw new ServletException("Configuration file not found with the path: \"" + ((uwsConfPath == null) ? DEFAULT_UWS_CONF_FILE : uwsConfPath) + "\"! Please provide a correct file path in servlet init parameter (\"" + UWS_CONF_PARAMETER + "\") or put your configuration file named \"" + DEFAULT_UWS_CONF_FILE + "\" in a directory of the classpath or in WEB-INF or META-INF.");

		/* 3. PARSE IT INTO A PROPERTIES SET */
		Properties uwsConf = new Properties();
		try{
			uwsConf.load(input);
		}catch(IOException ioe){
			throw new ServletException("Impossible to read the UWS configuration file (" + uwsConfPath + ")!", ioe);
		}finally{
			try{
				input.close();
			}catch(IOException ioe2){
			}
		}

		/* 4. CREATE THE FILE MANAGER */
		UWSFileManager fileManager = null;
		try{
			fileManager = createFileManager(uwsConf, config.getServletContext().getRealPath(""));
		}catch(Exception ex){
			if (ex instanceof UWSException)
				throw new ServletException(ex.getMessage(), ex.getCause());
			else
				throw new ServletException("Impossible to initialize the UWS file manager!", ex);
		}

		/* 5. CREATE THE LOGGER */
		UWSLog logger = null;
		try{
			logger = createLogger(uwsConf, fileManager);
		}catch(Exception ex){
			if (ex instanceof UWSException)
				throw new ServletException(ex.getMessage(), ex.getCause());
			else
				throw new ServletException("Impossible to initialize the UWS logger!", ex);
		}

		/* 6. CREATE THE UWS SERVICE */
		try{
			uws = new UWSService(createFactory(uwsConf), fileManager, logger);
		}catch(Exception ex){
			uws = null;
			if (ex instanceof UWSException)
				throw new ServletException(ex.getMessage(), ex.getCause());
			else
				throw new ServletException("Impossible to initialize the UWS service!", ex);
		}

		/* 6Bis. SET THE HOME PAGE */
		String propValue = getProperty(uwsConf, KEY_HOME_PAGE);
		if (propValue != null){
			// If it is a class path, replace the current home page by an instance of this class:
			if (isClassName(propValue)){
				try{
					uws.replaceUWSAction(newInstance(propValue, KEY_HOME_PAGE, ShowHomePage.class, new Class<?>[]{ UWSService.class }, new Object[]{ uws }));
				}catch(UWSException te){
					throw new ServletException(te.getMessage(), te.getCause());
				}
			}
			// If it is a file URI (null, file inside WebContent, file://..., http://..., etc...):
			else{
				// ...set the given URI:
				uws.setHomePage(propValue);
				// ...and its MIME type (if any):
				propValue = getProperty(uwsConf, KEY_HOME_PAGE_MIME_TYPE);
				if (propValue != null)
					uws.setHomePageMimeType(propValue);
			}
		}

		/* 6Ter. SET ALL GENERAL SERVICE CONNECTION INFORMATION */
		uws.setName(getProperty(uwsConf, KEY_SERVICE_NAME));
		uws.setDescription(getProperty(uwsConf, KEY_SERVICE_DESCRIPTION));

		/* 7. CONFIGURE THE BACKUP MANAGER */
		initBackup(uwsConf);

		/* 8. CONFIGURE THE USER IDENTIFICATION */
		initUserIdentifier(uwsConf);

		/* 9. CREATE THE JOB LISTS */
		initJobLists(uwsConf);

		/* 10. INITIALIZE ADDITIONAL ACTIONS */
		addCustomActions(uwsConf);

		/* 11. INITIALIZE THE SERIALIZATION */
		addCustomSerializers(uwsConf);
		initXSLTStylesheet(uwsConf);
		initErrorWriter(uwsConf);

		/* 12. DEFAULT SERVLET INITIALIZATION */
		super.init(config);
	}

	/**
	 * Initialize the management of UWS service files using the given UWS configuration file.
	 *
	 * @param uwsConfig		The content of the UWS configuration file.
	 * @param webAppRootDir	The directory of the Web Application running this UWS service.
	 *                     	<em>This directory may be used only to search the root UWS directory
	 *                     	if specified with a relative path in the UWS configuration file.</em>
	 *
	 * @return	The created file manager.
	 *
	 * @throws UWSException	If a property is wrong or missing, or if an error occurs while creating the file manager.
	 */
	private UWSFileManager createFileManager(final Properties uwsConfig, final String webAppRootDir) throws UWSException{
		// Read the desired file manager:
		String fileManagerType = getProperty(uwsConfig, KEY_FILE_MANAGER);
		if (fileManagerType == null)
			throw new UWSException("The property \"" + KEY_FILE_MANAGER + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_LOCAL + " or a class name between {...}.");
		else
			fileManagerType = fileManagerType.trim();

		// LOCAL file manager:
		if (fileManagerType.equalsIgnoreCase(VALUE_LOCAL)){
			// Read the desired root path:
			String rootPath = getProperty(uwsConfig, KEY_FILE_ROOT_PATH);
			if (rootPath == null)
				throw new UWSException("The property \"" + KEY_FILE_ROOT_PATH + "\" is missing! It is required to create a TAP Service. Please provide a path toward a directory which will contain all files related to the service.");
			File rootFile = getFile(rootPath, webAppRootDir, KEY_FILE_ROOT_PATH);

			// Determine whether there should be one directory for each user:
			String propValue = getProperty(uwsConfig, KEY_DIRECTORY_PER_USER);
			boolean oneDirectoryPerUser = (propValue == null) ? DEFAULT_DIRECTORY_PER_USER : Boolean.parseBoolean(propValue);

			// Determine whether there should be one directory for each user:
			propValue = getProperty(uwsConfig, KEY_GROUP_USER_DIRECTORIES);
			boolean groupUserDirectories = (propValue == null) ? DEFAULT_GROUP_USER_DIRECTORIES : Boolean.parseBoolean(propValue);

			// Build the Local TAP File Manager:
			try{
				return new LocalUWSFileManager(rootFile, oneDirectoryPerUser, groupUserDirectories);
			}catch(UWSException e){
				throw new UWSException("The property \"" + KEY_FILE_ROOT_PATH + "\" (" + rootPath + ") is incorrect: " + e.getMessage());
			}
		}
		// CUSTOM file manager:
		else
			return newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{ Properties.class }, new Object[]{ uwsConfig });
	}

	/**
	 * Initialise the UWS logger with the given UWS configuration file.
	 *
	 * @param uwsConfig		The content of the UWS configuration file.
	 * @param fileManager	The file manager to access the log file(s)
	 *                   	(if needed).
	 *
	 * @throws UWSException	If no instance of the specified custom logger can
	 *                     	be created.
	 */
	private UWSLog createLogger(final Properties uwsConfig, final UWSFileManager fileManager) throws UWSException{
		// Create the logger:
		UWSLog logger;
		String propValue = getProperty(uwsConfig, KEY_LOGGER);
		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER))
			logger = new DefaultUWSLog(fileManager);
		else if (propValue == null || propValue.trim().equalsIgnoreCase(SLF4J_LOGGER))
			logger = new Slf4jUWSLog();
		else
			logger = newInstance(propValue, KEY_LOGGER, UWSLog.class, new Class<?>[]{ UWSFileManager.class }, new Object[]{ fileManager });

		// Set some options for the default logger:
		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER)){

			// Set the minimum log level:
			propValue = getProperty(uwsConfig, KEY_MIN_LOG_LEVEL);
			if (propValue != null){
				try{
					((DefaultUWSLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
				}catch(IllegalArgumentException iae){
				}
			}

			// Set the log rotation period, if any:
			if (fileManager instanceof LocalUWSFileManager){
				propValue = getProperty(uwsConfig, KEY_LOG_ROTATION);
				if (propValue != null)
					((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
			}
		}

		// Log the successful initialisation of the logger:
		logger.info("Logger initialized - {" + logger.getConfigString() + "}");

		return logger;
	}

	/**
	 * <p>Initialize the {@link UWSFactory} to use.</p>
	 *
	 * <p>
	 * 	The built factory is either a {@link ConfigurableUWSFactory} instance (by default) or
	 * 	an instance of the class specified in the UWS configuration file.
	 * </p>
	 *
	 * @param uwsConfig		The content of the UWS configuration file.
	 *
	 * @throws UWSException	If an error occurs while building the specified {@link UWSFactory}.
	 *
	 * @see ConfigurableUWSFactory
	 */
	private UWSFactory createFactory(final Properties uwsConfig) throws UWSException{
		String propValue = getProperty(uwsConfig, KEY_UWS_FACTORY);
		if (propValue == null)
			return new ConfigurableUWSFactory(uwsConfig);
		else if (hasConstructor(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{ Properties.class }))
			return newInstance(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{ Properties.class }, new Object[]{ uwsConfig });
		else
			return newInstance(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{}, new Object[]{});
	}

	/**
	 * Create a {@link UWSBackupManager} if needed, thanks to the configuration provided in the UWS configuration file.
	 *
	 * @param uwsConf	List of properties set in the UWS configuration file.
	 *
	 * @throws ServletException	If any error occurs when initializing the {@link UWSBackupManager}.
	 */
	private void initBackup(final Properties uwsConf) throws ServletException{
		try{
			/* Set the backup frequency: */
			String propValue = getProperty(uwsConf, KEY_BACKUP_FREQUENCY);

			// determine whether the value is a time period ; if yes, set the frequency:
			long backupFrequency;
			boolean backupByUser;
			if (propValue != null){
				try{
					backupFrequency = Long.parseLong(propValue);
					if (backupFrequency <= 0)
						backupFrequency = DEFAULT_BACKUP_FREQUENCY;
				}catch(NumberFormatException nfe){
					// if the value was not a valid numeric time period, try to identify the different textual options:
					if (propValue.equalsIgnoreCase(VALUE_NEVER))
						backupFrequency = DefaultUWSBackupManager.MANUAL;
					else if (propValue.equalsIgnoreCase(VALUE_USER_ACTION))
						backupFrequency = DefaultUWSBackupManager.AT_USER_ACTION;
					else
						throw new UWSException("Long expected for the property \"" + KEY_BACKUP_FREQUENCY + "\", instead of: \"" + propValue + "\"!");
				}
			}else
				backupFrequency = DEFAULT_BACKUP_FREQUENCY;

			// Specify whether the backup must be organized by user or not:
			propValue = getProperty(uwsConf, KEY_BACKUP_BY_USER);
			backupByUser = (propValue == null) ? DEFAULT_BACKUP_BY_USER : Boolean.parseBoolean(propValue);

			// Finally create and set the backup manager:
			uws.setBackupManager(new DefaultUWSBackupManager(uws, backupByUser, backupFrequency));

		}catch(UWSException ue){
			throw new ServletException("Impossible to initialize the Backup system (and so to restore all the last backuped jobs)!", ue);
		}
	}

	/**
	 * Initialize the UWS user identification method.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void initUserIdentifier(final Properties uwsConfig) throws ServletException{
		// Get the property value:
		String propValue = getProperty(uwsConfig, KEY_USER_IDENTIFIER);
		if (propValue != null){
			try{
				uws.setUserIdentifier(newInstance(propValue, KEY_USER_IDENTIFIER, UserIdentifier.class));
			}catch(UWSException ue){
				throw new ServletException("Impossible to initialize the user identification!", ue);
			}
		}
	}

	/**
	 * Initialize all the specified job lists.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void initJobLists(final Properties uwsConf) throws ServletException{
		String propValue = getProperty(uwsConf, KEY_JOB_LISTS);
		if (propValue != null){
			// split the list of job list names:
			String[] jlNames = propValue.split(",");
			if (jlNames == null || jlNames.length == 0)
				throw new ServletException("Missing job list name! At least one job list name must be provided. See property \"" + KEY_JOB_LISTS + "\".");

			// for each job list name:
			int nbMaxRunningJobs;
			ExecutionManager execManager;
			DestructionManager destManager;
			for(String jlName : jlNames){
				// normalize and test the name:
				jlName = jlName.trim();
				if (!jlName.matches(REGEXP_JOB_LIST_NAME))
					throw new ServletException("Incorrect job list name: \"" + jlName + "\"! It must not contain space characters, point and equal sign.");

				// configure the execution manager, if any is specified in the configuration:
				nbMaxRunningJobs = -1;
				execManager = null;
				try{
					// if an execution manager is provided, set it:
					propValue = getProperty(uwsConf, jlName + "." + KEY_EXECUTION_MANAGER);
					if (propValue != null)
						execManager = newInstance(propValue, jlName + "." + KEY_EXECUTION_MANAGER, ExecutionManager.class, new Class<?>[]{ UWSLog.class }, new Object[]{ uws.getLogger() });

					/* if none is provided, the default execution manager will be used
					 * EXCEPT if a maximum number of running jobs is specified ; in such case a QueuedExecutionManager will be used. */
					else{
						propValue = getProperty(uwsConf, jlName + "." + KEY_MAX_RUNNING_JOBS);
						if (propValue != null){
							try{
								nbMaxRunningJobs = Integer.parseInt(propValue);
								if (nbMaxRunningJobs > 0)
									execManager = new QueuedExecutionManager(uws.getLogger(), nbMaxRunningJobs);
							}catch(NumberFormatException nfe){
								uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Incorrect value for the property \"" + jlName + "." + KEY_MAX_RUNNING_JOBS + "\": \"" + propValue + "\"! It should be a positive integer value. No execution queue is set for this job list.", nfe);
							}
						}
					}
				}catch(UWSException ue){
					uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to set a custom execution manager to the job list \"" + jlName + "\"! The default one will be used.", ue);
				}

				// configure the destruction manager, if any is specified in the configuration:
				destManager = null;
				try{
					propValue = getProperty(uwsConf, jlName + "." + KEY_DESTRUCTION_MANAGER);
					if (propValue != null)
						destManager = newInstance(propValue, jlName + "." + KEY_DESTRUCTION_MANAGER, DestructionManager.class, new Class<?>[]{}, new Object[]{});
				}catch(UWSException ue){
					uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to set a custom destruction manager to the job list \"" + jlName + "\"! The default one will be used.", ue);
				}

				// add the job list to this UWS service:
				if (execManager == null)
					execManager = new DefaultExecutionManager(uws.getLogger());
				if (destManager == null)
					destManager = new DefaultDestructionManager();
				uws.addJobList(new JobList(jlName, execManager, destManager));
			}
		}else
			throw new ServletException("Missing job list name! At least one job list name must be provided. See property \"" + KEY_JOB_LISTS + "\".");
	}

	/**
	 * Add all additional custom actions listed in the configuration file.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void addCustomActions(final Properties uwsConfig) throws ServletException{
		// Get the property value:
		String propValue = getProperty(uwsConfig, KEY_ADD_UWS_ACTIONS);
		if (propValue != null){
			// Tokenise the list of classes:
			String[] actionClasses = propValue.split(",");
			if (actionClasses == null || actionClasses.length == 0)
				return;

			// For each item:
			for(String actionClass : actionClasses){
				try{
					// extract the action index, if any is provided:
					int actionIndex = actionClass.indexOf(':');
					if (actionIndex > 0){
						actionIndex = Integer.parseInt(actionClass.substring(0, actionIndex));
						actionClass = actionClass.substring(actionIndex + 1);
					}
					// create an instance of the specified action:
					UWSAction action = newInstance(actionClass, KEY_ADD_UWS_ACTIONS, UWSAction.class, new Class<?>[]{ UWSService.class }, new Object[]{ uws });
					// add or replacing depending if an action with the same name already exists or not:
					boolean added = false;
					if (uws.getUWSAction(action.getName()) != null)
						added = (uws.replaceUWSAction(action) != null);
					else if (actionIndex >= 0)
						added = uws.addUWSAction(actionIndex, action);
					else
						added = uws.addUWSAction(action);
					// log an error if the addition is not successful:
					if (!added)
						uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Failed to add the UWS action \"" + action.getName() + "\" implemented with the class \"" + actionClass + "\"! See property \"" + KEY_ADD_UWS_ACTIONS + "\".", null);
				}catch(UWSException ue){
					uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to create the UWS action \"" + actionClass + "\" specified in the property \"" + KEY_ADD_UWS_ACTIONS + "\"!", ue);
				}catch(NumberFormatException nfe){
					uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to extract the given action index for the UWS action \"" + actionClass + "\" specified in the property \"" + KEY_ADD_UWS_ACTIONS + "\"! A positive integer value is expected.", nfe);
				}
			}
		}
	}

	/**
	 * Add all additional UWS serialized listed in the configuration file.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void addCustomSerializers(final Properties uwsConfig) throws ServletException{
		// Get the property value:
		String propValue = getProperty(uwsConfig, KEY_ADD_SERIALIZERS);
		if (propValue != null){
			// Tokenise the list of classes:
			String[] serializerClasses = propValue.split(",");
			if (serializerClasses == null || serializerClasses.length == 0)
				return;

			// For each item:
			for(String serializerClass : serializerClasses){
				try{
					// create and add the specified serializer to this UWS service:
					uws.addSerializer(newInstance(serializerClass, KEY_ADD_SERIALIZERS, UWSSerializer.class, new Class<?>[]{}, new Object[]{}));
				}catch(UWSException ue){
					uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to create the UWS serializer \"" + serializerClass + "\" specified in the property \"" + KEY_ADD_SERIALIZERS + "\"!", ue);
				}
			}
		}
	}

	/**
	 * Initialize the error writer of the UWS service.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void initXSLTStylesheet(final Properties uwsConfig) throws ServletException{
		// Get the property value:
		String propValue = getProperty(uwsConfig, KEY_XSLT_STYLESHEET);
		if (propValue != null){
			try{
				if (uws.getSerializer(XMLSerializer.MIME_TYPE_XML) instanceof XMLSerializer)
					((XMLSerializer)uws.getSerializer(XMLSerializer.MIME_TYPE_XML)).setXSLTPath(propValue);
			}catch(UWSException ue){
				uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to set the specified XSLT stylesheet: \"" + propValue + "\"! Then, no XSLT stylesheet is used.", ue);
			}
		}
	}

	/**
	 * Initialize the error writer of the UWS service.
	 *
	 * @param uwsConfig	The content of the UWS configuration file.
	 *
	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
	 */
	private void initErrorWriter(final Properties uwsConfig) throws ServletException{
		// Get the property value:
		String propValue = getProperty(uwsConfig, KEY_ERROR_WRITER);
		if (propValue != null){
			try{
				uws.setErrorWriter(newInstance(propValue, KEY_ERROR_WRITER, ServiceErrorWriter.class));
			}catch(UWSException ue){
				uws.getLogger().logUWS(LogLevel.ERROR, uws, "INIT", "Impossible to initialize the error writer! Then, the default one will be used.", ue);
			}
		}
	}

	/**
	 * Search the given file name/path in the directories of the classpath, then inside WEB-INF and finally inside META-INF.
	 *
	 * @param filePath	A file name/path.
	 * @param config	Servlet configuration (containing also the context class loader - link with the servlet classpath).
	 *
	 * @return	The input stream toward the specified file, or NULL if no file can be found.
	 *
	 * @since 2.0
	 */
	protected final InputStream searchFile(String filePath, final ServletConfig config){
		InputStream input = null;

		// Try to search in the classpath (with just a file name or a relative path):
		input = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);

		// If not found, try searching in WEB-INF and META-INF (as this fileName is a file path relative to one of these directories):
		if (input == null){
			if (filePath.startsWith("/"))
				filePath = filePath.substring(1);
			// ...try at the root of WEB-INF:
			input = config.getServletContext().getResourceAsStream("/WEB-INF/" + filePath);
			// ...and at the root of META-INF:
			if (input == null)
				input = config.getServletContext().getResourceAsStream("/META-INF/" + filePath);
		}

		return input;
	}

	/**
	 * <p>Resolve the given file name/path.</p>
	 *
	 * <p>Only the URI protocol "file:" is allowed. If the protocol is different a {@link UWSException} is thrown.</p>
	 *
	 * <p>
	 * 	If not an absolute URI, the given path may be either relative or absolute. A relative path is always considered
	 * 	as relative from the Web Application directory (supposed to be given in 2nd parameter).
	 * </p>
	 *
	 * @param filePath			URI/Path/Name of the file to get.
	 * @param webAppRootPath	Web Application directory local path.
	 * @param propertyName		Name of the property which gives the given file path.
	 *
	 * @return	The specified File instance.
	 *
	 * @throws UWSException	If the given URI is malformed or if the used URI scheme is different from "file:".
	 */
	protected final File getFile(final String filePath, final String webAppRootPath, final String propertyName) throws UWSException{
		if (filePath == null)
			return null;

		try{
			URI uri = new URI(filePath);
			if (uri.isAbsolute()){
				if (uri.getScheme().equalsIgnoreCase("file"))
					return new File(uri);
				else
					throw new UWSException("Incorrect file URI for the property \"" + propertyName + "\": \"" + filePath + "\"! Only URI with the protocol \"file:\" are allowed.");
			}else{
				File f = new File(filePath);
				if (f.isAbsolute())
					return f;
				else
					return new File(webAppRootPath, filePath);
			}
		}catch(URISyntaxException use){
			throw new UWSException(UWSException.NOT_FOUND, use, "Incorrect file URI for the property \"" + propertyName + "\": \"" + filePath + "\"! Bad syntax for the given file URI.");
		}
	}

	@Override
	public void destroy(){
		// Free all resources used by UWS:
		if (uws != null){
			uws.destroy();
			uws = null;
		}
		super.destroy();
	}

	@Override
	protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException{
		if (uws != null){
			try{
				uws.executeRequest(req, resp);
			}catch(Throwable t){
				resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
			}
		}else
			resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "UWS service not yet initialized!");
	}

}
