Skip to content
Snippets Groups Projects
Select Git revision
  • 9aa13889e2ebb4447c34351bccdf73997d85596e
  • master default protected
  • ia2
  • adql2.1-ia2
  • private_rows
5 results

UWSService.java

Blame
  • UWSService.java 43.80 KiB
    package uws.service;
    
    /*
     * 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 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
     *                       Astronomisches Rechen Institut (ARI)
     */
    
    import java.io.IOException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Vector;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import uws.AcceptHeader;
    import uws.UWSException;
    import uws.job.ExecutionPhase;
    import uws.job.JobList;
    import uws.job.UWSJob;
    import uws.job.manager.DefaultExecutionManager;
    import uws.job.serializer.JSONSerializer;
    import uws.job.serializer.UWSSerializer;
    import uws.job.serializer.XMLSerializer;
    import uws.job.user.JobOwner;
    import uws.service.actions.AddJob;
    import uws.service.actions.DestroyJob;
    import uws.service.actions.GetJobParam;
    import uws.service.actions.JobSummary;
    import uws.service.actions.ListJobs;
    import uws.service.actions.SetJobParam;
    import uws.service.actions.ShowHomePage;
    import uws.service.actions.UWSAction;
    import uws.service.backup.UWSBackupManager;
    import uws.service.error.DefaultUWSErrorWriter;
    import uws.service.error.ServiceErrorWriter;
    import uws.service.file.UWSFileManager;
    import uws.service.log.DefaultUWSLog;
    import uws.service.log.UWSLog;
    import uws.service.log.UWSLog.LogLevel;
    
    /**
     * <h3>General description</h3>
     * 
     * <p>An abstract facility to implement the <b>U</b>niversal <b>W</b>orker <b>S</b>ervice pattern.</p>
     * 
     * <p>It can manage several jobs lists (create new, get and remove).</p>
     * 
     * <p>It also interprets {@link HttpServletRequest}, applies the action specified in its given URL and parameters
     * <i>(according to the <a href="http://www.ivoa.net/Documents/UWS/20100210">IVOA Proposed Recommendation of 2010-02-10</a>)</i>
     * and returns the corresponding response in a {@link HttpServletResponse}.</p>
     * 
     * <h3>The UWS URL interpreter</h3>
     * 
     * <p>Any subclass of {@link UWSService} has one object called the UWS URL interpreter. It is stored in the field {@link #urlInterpreter}.
     * It lets interpreting the URL of any received request. Thus you can know on which jobs list, job and/or job attribute(s)
     * the request applies.</p>
     * 
     * <p>This interpreter must be initialized with the base URL/URI of this UWS. By using the default constructor (the one with no parameter),
     * the URL interpreter will be built at the first request (see {@link UWSUrl#UWSUrl(HttpServletRequest)}) and so the base URI is
     * extracted directly from the request).</p>
     * 
     * <p>You want to set another base URI or to use a custom URL interpreter, you have to set yourself the interpreter
     * by using the method {@link #setUrlInterpreter(UWSUrl)}.</p>
     * 
     * <h3>Create a job</h3>
     * 
     * <p>The most important abstract function of this class is {@link UWSService#createJob(Map)}. It allows to create an instance
     * of the type of job which is managed by this UWS. The only parameter is a map of a job attributes. It is the same map that
     * take the functions {@link UWSJob#UWSJob(Map)} and {@link UWSJob#addOrUpdateParameters(Map)}.</p>
     * 
     * <p>There are two convenient implementations of this abstract method in {@link BasicUWS} and {@link ExtendedUWS}. These two implementations
     * are based on the Java Reflection.</p>
     * 
     * <h3>UWS actions</h3>
     * 
     * <p>All the actions described in the IVOA recommendation are already managed. Each of these actions are defined in
     * an instance of {@link UWSAction}:</p>
     * <ul>
     * 	<li>{@link UWSAction#LIST_JOBS LIST_JOBS}: see the class {@link ListJobs}</li>
     * 	<li>{@link UWSAction#ADD_JOB ADD_JOB}: see the class {@link AddJob}</li>
     * 	<li>{@link UWSAction#DESTROY_JOB DESTROY_JOB}: see the class {@link DestroyJob}</li>
     * 	<li>{@link UWSAction#JOB_SUMMARY JOB_SUMMARY}: see the class {@link JobSummary}</li>
     * 	<li>{@link UWSAction#GET_JOB_PARAM GET_JOB_PARAM}: see the class {@link GetJobParam}</li>
     * 	<li>{@link UWSAction#SET_JOB_PARAM SET_JOB_PARAM}: see the class {@link SetJobParam}</li>
     * 	<li>{@link UWSAction#HOME_PAGE HOME_PAGE}: see the class {@link ShowHomePage}</li>
     * </ul>
     * 
     * <p><b>However you can add your own UWS actions !</b> To do that you just need to implement the abstract class {@link UWSAction}
     * and to call the method {@link #addUWSAction(UWSAction)} with an instance of this implementation.</p>
     * 
     * <p><b><u>IMPORTANT:</u> You must be careful when you override the function {@link UWSAction#match(UWSUrl, String, HttpServletRequest)}
     * so that your test is as precise as possible ! Indeed the order in which the actions of a UWS are evaluated is very important !<br />
     * <u>If you want to be sure your custom UWS action is always evaluated before any other UWS action you can use the function
     * {@link #addUWSAction(int, UWSAction)} with 0 as first parameter !</u></b></p>
     * 
     * <p><i><u>Note:</u> You can also replace an existing UWS action thanks to the method {@link #replaceUWSAction(UWSAction)} or
     * {@link #setUWSAction(int, UWSAction)} !</i></p>
     * 
     * <h3>User identification</h3>
     * 
     * <p>Some UWS actions need to know the current user so that they can adapt their response (i.e. LIST_JOBS must return the jobs of only
     * one user: the current one). Thus, before executing a UWS action (and even before choosing the good action in function of the request)
     * the function {@link UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)} is called. Its goal
     * is to identify the current user in function of the received request.</p>
     * 
     * <p>
     * 	<i><u>Notes:</u>
     * 		<ul>
     * 			<li>If this function returns NULL, the UWS actions must be executed on all jobs, whatever is their owner !</li>
     * 			<li>{@link UserIdentifier} is an interface. So you must implement it and then set its extension to this UWS
     * 				by using {@link #setUserIdentifier(UserIdentifier)}.</li>
     *		</ul>
     * 	</i></p>
     * </p>
     * 
     * <h3>Queue management</h3>
     * 
     * <p>One of the goals of a UWS is to manage an execution queue for all managed jobs. This task is given to an instance
     * of {@link DefaultExecutionManager}, stored in the field {@link #executionManager}. Each time a job is created,
     * the UWS sets it the execution manager (see {@link AddJob}). Thus the {@link UWSJob#start()} method will ask to the manager
     * whether it can execute now or whether it must be put in a {@link ExecutionPhase#QUEUED QUEUED} phase until enough resources are available for its execution.</p>
     * 
     * <p>By extending the class {@link DefaultExecutionManager} and by overriding {@link DefaultExecutionManager#isReadyForExecution(UWSJob)}
     * you can change the condition which puts a job in the {@link ExecutionPhase#EXECUTING EXECUTING} or in the {@link ExecutionPhase#QUEUED QUEUED} phase. By default, a job is put
     * in the {@link ExecutionPhase#QUEUED QUEUED} phase if there are more running jobs than a given number.</p>
     * 
     * <p>With this manager it is also possible to list all running jobs in addition of all queued jobs, thanks to the methods:
     * {@link DefaultExecutionManager#getRunningJobs()}, {@link DefaultExecutionManager#getQueuedJobs()}, {@link DefaultExecutionManager#getNbRunningJobs()}
     * and {@link DefaultExecutionManager#getNbQueuedJobs()}.</p>
     * 
     * <h3>Serializers & MIME types</h3>
     * 
     * <p>According to the IVOA recommendation, the XML format is the default format in which each UWS resource must be returned. However it
     * is told that other formats can also be managed. To allow that, {@link UWSService} manages a list of {@link UWSSerializer} and
     * lets define which is the default one to use. <i>By default, there are two serializers: {@link XMLSerializer} (the default choice)
     * and {@link JSONSerializer}.</i></p>
     * 
     * <p>One proposed way to choose automatically the format to use is to look at the Accept header of a HTTP-Request. This header field is
     * a list of MIME types (each one with a quality - a sort of priority). Thus each {@link UWSSerializer} is associated with a MIME type so
     * that {@link UWSService} can choose automatically the preferred format and so, the serializer to use.</p>
     * 
     * <p><b><u>WARNING:</u> Only one {@link UWSSerializer} can be associated with a given MIME type in an {@link UWSService} instance !
     * Thus, if you add a {@link UWSSerializer} to a UWS, and this UWS has already a serializer for the same MIME type,
     * it will be replaced by the added one.</b></p>
     * 
     * <p><i><u>Note:</u> A XML document can be linked to a XSLT style-sheet. By using the method {@link XMLSerializer#setXSLTPath(String)}
     * you can define the path/URL of the XLST to link to each UWS resource. <br />
     * <u>Since the {@link XMLSerializer} is the default format for a UWS resource you can also use the function
     * {@link UWSService#setXsltURL(String)} !</u></i></p>
     * 
     * <h3>The UWS Home page</h3>
     * 
     * <p>As for a job or a jobs list, a UWS is also a UWS resource. That's why it can also be serialized !</p>
     * 
     * <p>However in some cases it could more interesting to substitute this resource by a home page of the whole UWS by using the function:
     * {@link #setHomePage(String)} or {@link #setHomePage(URL, boolean)}.
     * </p>
     * 
     * <p><i><u>Note:</u> To go back to the UWS serialization (that is to say to abort a call to {@link #setHomePage(String)}),
     * use the method {@link #setDefaultHomePage()} !</i></p>
     * 
     * 
     * @author Gr&eacute;gory Mantelet (CDS;ARI)
     * @version 4.1 (09/2014)
     */
    public class UWSService implements UWS {
    
    	/** Name of this UWS. */
    	protected String name = null;
    
    	/** Description of this UWS. */
    	protected String description = null;
    
    	/** List of all managed jobs lists. <i>(it is a LinkedHashMap so that jobs lists are ordered by insertion)</i> */
    	protected final Map<String,JobList> mapJobLists;
    
    	/** The "interpreter" of UWS URLs. */
    	protected UWSUrl urlInterpreter = null;
    
    	/** List of available serializers. */
    	protected final Map<String,UWSSerializer> serializers;
    
    	/** The MIME type of the default serialization format. */
    	protected String defaultSerializer = null;
    
    	/** The serializer chosen during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}. */
    	protected UWSSerializer choosenSerializer = null;
    
    	/** URL of the home page. (<i>NULL if there is no home page</i>) */
    	protected String homePage = null;
    
    	/** Indicates whether the home page must be a copy or a redirection to the given URL. */
    	protected boolean homeRedirection = false;
    
    	/** List of UWS actions (i.e. to list jobs, to get a job, to set a job parameter, etc...). */
    	protected final Vector<UWSAction> uwsActions;
    
    	/** The action executed during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}. */
    	protected UWSAction executedAction = null;
    
    	/** The object to use to extract the user ID from the received request. */
    	protected UserIdentifier userIdentifier = null;
    
    	/** Factory which lets creating the UWS jobs and their thread. */
    	protected final UWSFactory factory;
    
    	/** Lets managing all UWS files (i.e. log, result, backup, ...). */
    	protected final UWSFileManager fileManager;
    
    	/** Lets saving and/or restoring the whole UWS.  */
    	protected UWSBackupManager backupManager;
    
    	/** Lets logging info/debug/warnings/errors about this UWS. */
    	protected UWSLog logger;
    
    	/** Lets writing/formatting any exception/throwable in a HttpServletResponse. */
    	protected ServiceErrorWriter errorWriter;
    
    	/** Last generated request ID. If the next generated request ID is equivalent to this one,
    	 * a new one will generate in order to ensure the unicity.
    	 * @since 4.1 */
    	protected static String lastRequestID = null;
    
    	/* ************ */
    	/* CONSTRUCTORS */
    	/* ************ */
    	/**
    	 * <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
    	 * 
    	 * <p>
    	 * 	By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
    	 * 	All the default actions of a UWS are also already implemented.
    	 * 	However, you still have to create at least one job list !
    	 * </p>
    	 * 
    	 * <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * 
    	 * @throws NullPointerException	If at least one of the parameters is <i>null</i>.
    	 * 
    	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager){
    		this(jobFactory, fileManager, (UWSLog)null);
    	}
    
    	/**
    	 * <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
    	 * 
    	 * <p>
    	 * 	By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
    	 * 	All the default actions of a UWS are also already implemented.
    	 * 	However, you still have to create at least one job list !
    	 * </p>
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * @param logger		Object which lets printing any message (error, info, debug, warning).
    	 * 
    	 * @throws NullPointerException	If at least one of the parameters is <i>null</i>.
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger){
    		if (jobFactory == null)
    			throw new NullPointerException("Missing UWS factory! Can not create a UWSService.");
    		factory = jobFactory;
    
    		if (fileManager == null)
    			throw new NullPointerException("Missing UWS file manager! Can not create a UWSService.");
    		this.fileManager = fileManager;
    
    		this.logger = (logger == null) ? new DefaultUWSLog(this) : logger;
    		errorWriter = new DefaultUWSErrorWriter(this.logger);
    
    		// Initialize the list of jobs:
    		mapJobLists = new LinkedHashMap<String,JobList>();
    
    		// Initialize the list of available serializers:
    		serializers = new HashMap<String,UWSSerializer>();
    		addSerializer(new XMLSerializer());
    		addSerializer(new JSONSerializer());
    
    		// Initialize the list of UWS actions:
    		uwsActions = new Vector<UWSAction>();
    
    		// Load the default UWS actions:
    		uwsActions.add(new ShowHomePage(this));
    		uwsActions.add(new ListJobs(this));
    		uwsActions.add(new AddJob(this));
    		uwsActions.add(new DestroyJob(this));
    		uwsActions.add(new JobSummary(this));
    		uwsActions.add(new GetJobParam(this));
    		uwsActions.add(new SetJobParam(this));
    	}
    
    	/**
    	 * <p>Builds a UWS with its base UWS URI.</p>
    	 * 
    	 * <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * @param baseURI		Base UWS URI.
    	 * 
    	 * @throws UWSException	If the given URI is <i>null</i> or empty.
    	 * 
    	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog, String)
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final String baseURI) throws UWSException{
    		this(jobFactory, fileManager, null, baseURI);
    	}
    
    	/**
    	 * Builds a UWS with its base UWS URI.
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * @param logger		Object which lets printing any message (error, info, debug, warning).
    	 * @param baseURI		Base UWS URI.
    	 * 
    	 * @throws UWSException	If the given URI is <i>null</i> or empty.
    	 * 
    	 * @see UWSUrl#UWSUrl(String)
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final String baseURI) throws UWSException{
    		this(jobFactory, fileManager, logger);
    
    		// Extract the name of the UWS:
    		try{
    			// Set the URL interpreter:
    			urlInterpreter = new UWSUrl(baseURI);
    
    			// ...and the name of this service:
    			name = urlInterpreter.getUWSName();
    
    			// Log the successful initialization:
    			logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized!", null);
    
    		}catch(NullPointerException ex){
    			// Log the exception:
    			// (since the first constructor has already been called successfully, the logger is now NOT NULL):
    			logger.logUWS(LogLevel.FATAL, null, "INIT", "Invalid base UWS URI: " + baseURI + "! You should check the configuration of the service.", ex);
    
    			// Throw a new UWSException with a more understandable message:
    			throw new UWSException(UWSException.BAD_REQUEST, ex, "Invalid base UWS URI (" + baseURI + ")!");
    		}
    	}
    
    	/**
    	 * <p>Builds a UWS with the given UWS URL interpreter.</p>
    	 * 
    	 * <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * @param urlInterpreter	The UWS URL interpreter to use in this UWS.
    	 * 
    	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog, UWSUrl)
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSUrl urlInterpreter){
    		this(jobFactory, fileManager, null, urlInterpreter);
    	}
    
    	/**
    	 * Builds a UWS with the given UWS URL interpreter.
    	 * 
    	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
    	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
    	 * @param logger		Object which lets printing any message (error, info, debug, warning).
    	 * @param urlInterpreter	The UWS URL interpreter to use in this UWS.
    	 */
    	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final UWSUrl urlInterpreter){
    		this(jobFactory, fileManager, logger);
    		setUrlInterpreter(urlInterpreter);
    		if (this.urlInterpreter != null)
    			logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
    	}
    
    	/* ************** */
    	/* LOG MANAGEMENT */
    	/* ************** */
    	@Override
    	public UWSLog getLogger(){
    		return logger;
    	}
    
    	/**
    	 * Gets the object used to write/format any error in a HttpServletResponse.
    	 * 
    	 * @return The error writer/formatter.
    	 */
    	public final ServiceErrorWriter getErrorWriter(){
    		return errorWriter;
    	}
    
    	/**
    	 * <p>Sets the object used to write/format any error in a HttpServletResponse.</p>
    	 * 
    	 * <p><i><u>Note:</u> Nothing is done if the given writer is NULL !</i></p>
    	 * 
    	 * @param errorWriter The new error writer/formatter.
    	 */
    	public final void setErrorWriter(ServiceErrorWriter errorWriter){
    		if (errorWriter != null)
    			this.errorWriter = errorWriter;
    	}
    
    	/* ***************** */
    	/* GETTERS & SETTERS */
    	/* ***************** */
    	@Override
    	public final String getName(){
    		return name;
    	}
    
    	/**
    	 * Sets the name of this UWS.
    	 * 
    	 * @param name	Its new name.
    	 */
    	public final void setName(String name){
    		this.name = name;
    	}
    
    	@Override
    	public final String getDescription(){
    		return description;
    	}
    
    	/**
    	 * Sets the description of this UWS.
    	 * 
    	 * @param description	Its new description.
    	 */
    	public final void setDescription(String description){
    		this.description = description;
    	}
    
    	/**
    	 * Gets the base UWS URL.
    	 * 
    	 * @return	The base UWS URL.
    	 * 
    	 * @see UWSUrl#getBaseURI()
    	 */
    	public final String getBaseURI(){
    		return (urlInterpreter == null) ? null : urlInterpreter.getBaseURI();
    	}
    
    	@Override
    	public final UWSUrl getUrlInterpreter(){
    		return urlInterpreter;
    	}
    
    	/**
    	 * Sets the UWS URL interpreter to use in this UWS.
    	 * 
    	 * @param urlInterpreter	Its new UWS URL interpreter (may be <i>null</i>. In this case, it will be created from the next request ; see {@link #executeRequest(HttpServletRequest, HttpServletResponse)}).
    	 */
    	public final void setUrlInterpreter(UWSUrl urlInterpreter){
    		this.urlInterpreter = urlInterpreter;
    		if (name == null && urlInterpreter != null)
    			name = urlInterpreter.getUWSName();
    	}
    
    	/**
    	 * <p>Gets the object which lets extracting the user ID from a HTTP request.</p>
    	 * <p><i><u>note:</u>If the returned user identifier is NULL, no job should have an owner.</i></p>
    	 * 
    	 * @return	The used UserIdentifier (MAY BE NULL).
    	 */
    	@Override
    	public final UserIdentifier getUserIdentifier(){
    		return userIdentifier;
    	}
    
    	/**
    	 * Sets the object which lets extracting the use ID from a received request.
    	 * 
    	 * @param identifier	The UserIdentifier to use (may be <i>null</i>).
    	 */
    	public final void setUserIdentifier(UserIdentifier identifier){
    		userIdentifier = identifier;
    	}
    
    	@Override
    	public final UWSFactory getFactory(){
    		return factory;
    	}
    
    	@Override
    	public final UWSFileManager getFileManager(){
    		return fileManager;
    	}
    
    	@Override
    	public final UWSBackupManager getBackupManager(){
    		return backupManager;
    	}
    
    	/**
    	 * <p>
    	 * 	Sets its backup manager.
    	 * 	This manager will be called at each user action to save only its own jobs list by calling {@link UWSBackupManager#saveOwner(String)}.
    	 * </p>
    	 * 
    	 * @param backupManager Its new backup manager.
    	 */
    	public final void setBackupManager(final UWSBackupManager backupManager){
    		this.backupManager = backupManager;
    	}
    
    	/* ******************** */
    	/* HOME PAGE MANAGEMENT */
    	/* ******************** */
    	/**
    	 * Gets the URL of the resource which must be used as home page of this UWS.
    	 * 
    	 * @return	The URL of the home page.
    	 */
    	public final String getHomePage(){
    		return homePage;
    	}
    
    	/**
    	 * Tells whether a redirection to the specified home page must be done or not.
    	 * 
    	 * @return	<i>true</i> if a redirection to the specified resource must be done
    	 * 			or <i>false</i> to copy it.
    	 */
    	public final boolean isHomePageRedirection(){
    		return homeRedirection;
    	}
    
    	/**
    	 * Sets the URL of the resource which must be used as home page of this UWS.
    	 * 
    	 * @param homePageUrl	The URL of the home page (may be <i>null</i>).
    	 * @param redirect		<i>true</i> if a redirection to the specified resource must be done
    	 * 						or <i>false</i> to copy it.
    	 */
    	public final void setHomePage(URL homePageUrl, boolean redirect){
    		homePage = homePageUrl.toString();
    		homeRedirection = redirect;
    	}
    
    	/**
    	 * <p>Sets the URI of the resource which must be used as home page of this UWS.</p>
    	 * <i>A redirection will always be done on the specified resource.</i>
    	 * 
    	 * @param homePageURI	The URL of the home page.
    	 */
    	public final void setHomePage(String homePageURI){
    		homePage = homePageURI;
    		homeRedirection = true;
    	}
    
    	/**
    	 * Indicates whether the current home page is the default one (the UWS serialization)
    	 * or if it has been specified manually using {@link UWSService#setHomePage(URL, boolean)}.
    	 * 
    	 * @return	<i>true</i> if it is the default home page, <i>false</i> otherwise.
    	 */
    	public final boolean isDefaultHomePage(){
    		return homePage == null;
    	}
    
    	/**
    	 * Forgets the home page specified by using {@link UWSService#setHomePage(URL, boolean)} - if any -
    	 * and go back to the default home page (XML format).
    	 */
    	public final void setDefaultHomePage(){
    		homePage = null;
    		homeRedirection = false;
    	}
    
    	/* ********************** */
    	/* SERIALIZERS MANAGEMENT */
    	/* ********************** */
    	/**
    	 * Gets the MIME type of the serializer to use by default.
    	 * 
    	 * @return	The MIME type of the default serializer.
    	 */
    	public final String getDefaultSerializer(){
    		return defaultSerializer;
    	}
    
    	/**
    	 * Sets the MIME of the serializer to use by default.
    	 * 
    	 * @param mimeType		The MIME type (only one).
    	 * 
    	 * @throws UWSException If there is no serializer with this MIME type available in this UWS.
    	 */
    	public final void setDefaultSerializer(String mimeType) throws UWSException{
    		if (serializers.containsKey(mimeType))
    			defaultSerializer = mimeType;
    		else
    			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeType + "! The default serializer won't be set.");
    	}
    
    	/**
    	 * <p>Adds a serializer to this UWS</p>
    	 * <p><b><u>WARNING:</u> If there is already a serializer with the same MIME type (see {@link UWSSerializer#getMimeType()}) in this UWS ,
    	 * it should be replaced by the given one !</b></p>
    	 * 
    	 * @param serializer	The serializer to add.
    	 * @return				<i>true</i> if the serializer has been successfully added, <i>false</i> otherwise.
    	 */
    	public final boolean addSerializer(UWSSerializer serializer){
    		if (serializer != null){
    			serializers.put(serializer.getMimeType(), serializer);
    			if (serializers.size() == 1)
    				defaultSerializer = serializer.getMimeType();
    			return true;
    		}
    		return false;
    	}
    
    	/**
    	 * Tells whether this UWS has already a serializer with the given MIME type.
    	 * 
    	 * @param mimeType	A MIME type (only one).
    	 * 
    	 * @return			<i>true</i> if a serializer exists with the given MIME type, <i>false</i> otherwise.
    	 */
    	public final boolean hasSerializerFor(String mimeType){
    		return serializers.containsKey(mimeType);
    	}
    
    	/**
    	 * Gets the total number of serializers available in this UWS.
    	 * 
    	 * @return	The number of its serializers.
    	 */
    	public final int getNbSerializers(){
    		return serializers.size();
    	}
    
    	/**
    	 * Gets an iterator of the list of all serializers available in this UWS.
    	 * 
    	 * @return	An iterator on its serializers.
    	 */
    	public final Iterator<UWSSerializer> getSerializers(){
    		return serializers.values().iterator();
    	}
    
    	@Override
    	public final UWSSerializer getSerializer(String mimeTypes) throws UWSException{
    		choosenSerializer = null;
    
    		if (mimeTypes != null){
    			// Parse the given MIME types list:
    			AcceptHeader accept = new AcceptHeader(mimeTypes);
    			ArrayList<String> lstMimeTypes = accept.getOrderedMimeTypes();
    
    			// Try each of them and stop at the first which match with an existing serializer:
    			for(int i = 0; choosenSerializer == null && i < lstMimeTypes.size(); i++)
    				choosenSerializer = serializers.get(lstMimeTypes.get(i));
    		}
    
    		// If no serializer has been found for each given mime type, return the default one:
    		if (choosenSerializer == null){
    			choosenSerializer = serializers.get(defaultSerializer);
    			if (choosenSerializer == null)
    				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "No UWS Serializer available neither for \"" + mimeTypes + "\" (given MIME types) nor \"" + defaultSerializer + "\" (default serializer MIME type) !");
    		}
    
    		return choosenSerializer;
    	}
    
    	/**
    	 * Gets the serializer choosen during the last call of {@link #getSerializer(String)}.
    	 * 
    	 * @return	The last used serializer.
    	 */
    	public final UWSSerializer getChoosenSerializer(){
    		return choosenSerializer;
    	}
    
    	/**
    	 * Removes the serializer whose the MIME type is the same as the given one.
    	 * 
    	 * @param mimeType	MIME type of the serializer to remove.
    	 * @return			The removed serializer
    	 * 					or <i>null</i> if no corresponding serializer has been found.
    	 */
    	public final UWSSerializer removeSerializer(String mimeType){
    		return serializers.remove(mimeType);
    	}
    
    	/**
    	 * Gets the URL of the XSLT style-sheet that the XML serializer of this UWS is using.
    	 * 
    	 * @return	The used XSLT URL.
    	 */
    	public final String getXsltURL(){
    		XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML);
    		if (serializer != null)
    			return serializer.getXSLTPath();
    		return null;
    	}
    
    	/**
    	 * Sets the URL of the XSLT style-sheet that the XML serializer of this UWS must use.
    	 * 
    	 * @param xsltPath	The new XSLT URL.
    	 * 
    	 * @return			<i>true</i> if the given path/url has been successfully set, <i>false</i> otherwise.
    	 */
    	public final boolean setXsltURL(String xsltPath){
    		XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML);
    		if (serializer != null){
    			serializer.setXSLTPath(xsltPath);
    			return true;
    		}
    		return false;
    	}
    
    	/* ********************* */
    	/* JOBS LISTS MANAGEMENT */
    	/* ********************* */
    	/**
    	 * An iterator on the jobs lists list.
    	 * 
    	 * @see java.lang.Iterable#iterator()
    	 */
    	@Override
    	public final Iterator<JobList> iterator(){
    		return mapJobLists.values().iterator();
    	}
    
    	@Override
    	public final JobList getJobList(String name){
    		return mapJobLists.get(name);
    	}
    
    	@Override
    	public final int getNbJobList(){
    		return mapJobLists.size();
    	}
    
    	/**
    	 * Adds a jobs list to this UWS.
    	 * 
    	 * @param jl	The jobs list to add.
    	 * 
    	 * @return		<i>true</i> if the jobs list has been successfully added,
    	 * 				<i>false</i> if the given jobs list is <i>null</i> or if a jobs list with this name already exists
    	 * 				or if a UWS is already associated with another UWS.
    	 * 
    	 * @see JobList#setUWS(AbstractUWS)
    	 * @see UWS#addJobList(JobList)
    	 */
    	@Override
    	public final boolean addJobList(JobList jl){
    		if (jl == null)
    			return false;
    		else if (mapJobLists.containsKey(jl.getName()))
    			return false;
    
    		try{
    			jl.setUWS(this);
    			mapJobLists.put(jl.getName(), jl);
    		}catch(IllegalStateException ise){
    			logger.logUWS(LogLevel.ERROR, jl, "ADD_JOB_LIST", "The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + ": it may already be associated with one!", ise);
    			return false;
    		}
    
    		return true;
    	}
    
    	@Override
    	public final boolean destroyJobList(String name){
    		return destroyJobList(mapJobLists.get(name));
    	}
    
    	/**
    	 * Destroys the given jobs list.
    	 * 
    	 * @param jl	The jobs list to destroy.
    	 * 
    	 * @return	<i>true</i> if the given jobs list has been destroyed, <i>false</i> otherwise.
    	 * 
    	 * @see JobList#clear()
    	 * @see JobList#setUWS(UWSService)
    	 */
    	public boolean destroyJobList(JobList jl){
    		if (jl == null)
    			return false;
    
    		jl = mapJobLists.remove(jl.getName());
    		if (jl != null){
    			try{
    				jl.clear();
    				jl.setUWS(null);
    			}catch(IllegalStateException ise){
    				logger.logUWS(LogLevel.WARNING, jl, "DESTROY_JOB_LIST", "Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\"!", ise);
    			}
    		}
    		return jl != null;
    	}
    
    	/**
    	 * Destroys all managed jobs lists.
    	 * 
    	 * @see #destroyJobList(String)
    	 */
    	public final void destroyAllJobLists(){
    		ArrayList<String> jlNames = new ArrayList<String>(mapJobLists.keySet());
    		for(String jlName : jlNames)
    			destroyJobList(jlName);
    	}
    
    	/* ********************** */
    	/* UWS ACTIONS MANAGEMENT */
    	/* ********************** */
    	/**
    	 * <p>Lets adding the given action to this UWS.</p>
    	 * 
    	 * <p><b><u>WARNING:</u> The action will be added at the end of the actions list of this UWS. That means, it will be evaluated (call of
    	 * the method {@link UWSAction#match(UWSUrl, String, HttpServletRequest)}) lastly !</b></p>
    	 * 
    	 * @param action	The UWS action to add.
    	 * 
    	 * @return			<i>true</i> if the given action has been successfully added, <i>false</i> otherwise.
    	 */
    	public final boolean addUWSAction(UWSAction action){
    		if (!uwsActions.contains(action))
    			return uwsActions.add(action);
    		else
    			return false;
    	}
    
    	/**
    	 * <p>Lets inserting the given action at the given position in the actions list of this UWS.</p>
    	 * 
    	 * @param indAction							The index where the given action must be inserted.
    	 * @param action							The action to add.
    	 * 
    	 * @return									<i>true</i> if the given action has been successfully added, <i>false</i> otherwise.
    	 * 
    	 * @throws ArrayIndexOutOfBoundsException	If the given index is incorrect (index < 0 || index >= uwsActions.size()).
    	 */
    	public final boolean addUWSAction(int indAction, UWSAction action) throws ArrayIndexOutOfBoundsException{
    		if (!uwsActions.contains(action)){
    			uwsActions.add(indAction, action);
    			return true;
    		}
    		return false;
    	}
    
    	/**
    	 * Replaces the specified action by the given action.
    	 * 
    	 * @param indAction							Index of the action to replace.
    	 * @param action							The replacer.
    	 * 
    	 * @return									<i>true</i> if the replacement has been a success, <i>false</i> otherwise.
    	 * 
    	 * @throws ArrayIndexOutOfBoundsException	If the index is incorrect (index < 0 || index >= uwsActions.size()).
    	 */
    	public final boolean setUWSAction(int indAction, UWSAction action) throws ArrayIndexOutOfBoundsException{
    		if (!uwsActions.contains(action)){
    			uwsActions.set(indAction, action);
    			return true;
    		}
    		return false;
    	}
    
    	/**
    	 * Replaces the action which has the same name that the given action.
    	 * 
    	 * @param action	The replacer.
    	 * 
    	 * @return			The replaced action
    	 * 					or <i>null</i> if the given action is <i>null</i>
    	 * 									or if there is no action with the same name (in this case, the given action is not added).
    	 */
    	public final UWSAction replaceUWSAction(UWSAction action){
    		if (action == null)
    			return null;
    		else{
    			for(int i = 0; i < uwsActions.size(); i++){
    				if (uwsActions.get(i).equals(action))
    					return uwsActions.set(i, action);
    			}
    			return null;
    		}
    	}
    
    	/**
    	 * Gets the number of actions this UWS has.
    	 * 
    	 * @return	The number of its actions.
    	 */
    	public final int getNbUWSActions(){
    		return uwsActions.size();
    	}
    
    	/**
    	 * Gets the action of this UWS which has the same name as the given one.
    	 * 
    	 * @param actionName	The name of the searched action.
    	 * 
    	 * @return				The corresponding action
    	 * 						or <i>null</i> if there is no corresponding action.
    	 */
    	public final UWSAction getUWSAction(String actionName){
    		for(int i = 0; i < uwsActions.size(); i++){
    			if (uwsActions.get(i).getName().equals(actionName))
    				return uwsActions.get(i);
    		}
    		return null;
    	}
    
    	/**
    	 * Gets all actions of this UWS.
    	 * 
    	 * @return	An iterator on its actions.
    	 */
    	public final Iterator<UWSAction> getUWSActions(){
    		return uwsActions.iterator();
    	}
    
    	/**
    	 * Gets the UWS action executed during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}.
    	 * 
    	 * @return	The last used UWS action.
    	 */
    	public final UWSAction getExecutedAction(){
    		return executedAction;
    	}
    
    	/**
    	 * Removes the specified action from this UWS.
    	 * 
    	 * @param indAction							The index of the UWS action to remove.
    	 * 
    	 * @return									The removed action.
    	 * 
    	 * @throws ArrayIndexOutOfBoundsException	If the given index is incorrect (index < 0 || index >= uwsActions.size()).
    	 */
    	public final UWSAction removeUWSAction(int indAction) throws ArrayIndexOutOfBoundsException{
    		return uwsActions.remove(indAction);
    	}
    
    	/**
    	 * Removes the action of this UWS which has the same name as the given one.
    	 * 
    	 * @param actionName	The name of the UWS to remove.
    	 * @return				The removed action
    	 * 						or <i>null</i> if there is no corresponding action.
    	 */
    	public final UWSAction removeUWSAction(String actionName){
    		for(int i = 0; i < uwsActions.size(); i++){
    			if (uwsActions.get(i).getName().equals(actionName))
    				return uwsActions.remove(i);
    		}
    		return null;
    	}
    
    	/* ********************** */
    	/* UWS MANAGEMENT METHODS */
    	/* ********************** */
    
    	/**
    	 * <p>Generate a unique ID for the given request.</p>
    	 * 
    	 * <p>By default, a timestamp is returned.</p>
    	 * 
    	 * @param request	Request whose an ID is asked.
    	 * 
    	 * @return	The ID of the given request.
    	 * 
    	 * @since 4.1
    	 */
    	protected synchronized String generateRequestID(final HttpServletRequest request){
    		String id;
    		do{
    			id = System.currentTimeMillis() + "";
    		}while(lastRequestID != null && lastRequestID.startsWith(id));
    		lastRequestID = id;
    		return id;
    	}
    
    	/**
    	 * <p>Executes the given request according to the <a href="http://www.ivoa.net/Documents/UWS/20100210/">IVOA Proposed Recommendation of 2010-02-10</a>.
    	 * The result is returned in the given response.</p>
    	 * 
    	 * <p>Here is the followed algorithm:</p>
    	 * <ol>
    	 * 	<li>Load the request in the UWS URL interpreter (see {@link UWSUrl#load(HttpServletRequest)})</li>
    	 * 	<li>Extract the user ID (see {@link UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)})</li>
    	 * 	<li>Iterate - in order - on all available actions and apply the first which matches.
    	 * 		(see {@link UWSAction#match(UWSUrl, String, HttpServletRequest)} and {@link UWSAction#apply(UWSUrl, String, HttpServletRequest, HttpServletResponse)})</li>
    	 * </ol>
    	 * 
    	 * @param request		The UWS request.
    	 * @param response		The response of this request which will be edited by the found UWS actions.
    	 * 
    	 * @return				<i>true</i> if the request has been executed successfully, <i>false</i> otherwise.
    	 * 
    	 * @throws UWSException	If no action matches or if any error has occurred while applying the found action.
    	 * @throws IOException	If it is impossible to write in the given {@link HttpServletResponse}.
    	 * 
    	 * @see UWSUrl#UWSUrl(HttpServletRequest)
    	 * @see UWSUrl#load(HttpServletRequest)
    	 * @see UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)
    	 * @see UWSAction#match(UWSUrl, String, HttpServletRequest)
    	 * @see UWSAction#apply(UWSUrl, String, HttpServletRequest, HttpServletResponse)
    	 */
    	public boolean executeRequest(HttpServletRequest request, HttpServletResponse response) throws UWSException, IOException{
    		if (request == null || response == null)
    			return false;
    
    		// Generate a unique ID for this request execution (for log purpose only):
    		final String reqID = generateRequestID(request);
    
    		// Log the reception of the request:
    		logger.logHttp(LogLevel.INFO, request, reqID, null, null);
    
    		boolean actionApplied = false;
    		UWSAction action = null;
    		JobOwner user = null;
    
    		try{
    			if (urlInterpreter == null){
    				// Initialize the URL interpreter if not already done:
    				setUrlInterpreter(new UWSUrl(request));
    
    				// Log the successful initialization:
    				logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
    			}
    
    			// Update the UWS URL interpreter:
    			urlInterpreter.load(request);
    
    			// Identify the user:
    			user = (userIdentifier == null) ? null : userIdentifier.extractUserId(urlInterpreter, request);
    
    			// Apply the appropriate UWS action:
    			for(int i = 0; action == null && i < uwsActions.size(); i++){
    				if (uwsActions.get(i).match(urlInterpreter, user, request)){
    					action = uwsActions.get(i);
    					choosenSerializer = null;
    					actionApplied = action.apply(urlInterpreter, user, request, response);
    				}
    			}
    
    			// If no corresponding action has been found, throw an error:
    			if (action == null)
    				throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
    
    			response.flushBuffer();
    
    			// Log the successful execution of the action:
    			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
    
    		}catch(UWSException ex){
    			// If redirection, flag the action as executed with success:
    			if (ex.getHttpErrorCode() == UWSException.SEE_OTHER)
    				actionApplied = true;
    			sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
    		}catch(Exception ex){
    			sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
    		}finally{
    			executedAction = action;
    		}
    
    		return actionApplied;
    	}
    
    	/**
    	 * <p>Sends a redirection (with the HTTP status code 303) to the given URL/URI into the given response.</p>
    	 * 
    	 * @param url		The redirection URL/URI.
    	 * @param request	The {@link HttpServletRequest} which may be used to make a redirection.
    	 * @param user		The user which executes the given request.
    	 * @param uwsAction	The UWS action corresponding to the given request.
    	 * @param response	The {@link HttpServletResponse} which must contain all information to make a redirection.
    	 * 
    	 * @throws IOException	If there is an error during the redirection.
    	 * @throws UWSException	If there is any other error.
    	 */
    	public void redirect(String url, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
    		response.setStatus(HttpServletResponse.SC_SEE_OTHER);
    		response.setContentType(request.getContentType());
    		response.setHeader("Location", url);
    		response.flushBuffer();
    	}
    
    	/**
    	 * <p>
    	 * 	Fills the response with the given error. The HTTP status code is set in function of the error code of the given UWSException.
    	 * 	If the error code is {@link UWSException#SEE_OTHER} this method calls {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)}.
    	 * 	Otherwise the function {@link HttpServletResponse#sendError(int, String)} is called.
    	 * </p>
    	 * 
    	 * @param error			The error to send/display.
    	 * @param request		The request which has caused the given error <i>(not used by default)</i>.
    	 * @param reqID			ID of the request.
    	 * @param user			The user which executes the given request.
    	 * @param uwsAction	The UWS action corresponding to the given request.
    	 * @param response		The response in which the error must be published.
    	 * 
    	 * @throws IOException	If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)} or {@link HttpServletResponse#sendError(int, String)}.
    	 * @throws UWSException	If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse))}.
    	 * 
    	 * @see #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)
    	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
    	 */
    	public final void sendError(UWSException error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
    		if (error.getHttpErrorCode() == UWSException.SEE_OTHER){
    			// Log the redirection, if any:
    			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.SEE_OTHER + " [Redirection toward " + error.getMessage() + "] - Action \"" + uwsAction + "\" successfully executed.", null);
    			// Apply the redirection:
    			redirect(error.getMessage(), request, user, uwsAction, response);
    		}else
    			sendError((Exception)error, request, reqID, user, uwsAction, response);
    	}
    
    	/**
    	 * <p>
    	 * 	Fills the response with the given error.
    	 * 	The stack trace of the error is printed on the standard output and then the function
    	 * 	{@link HttpServletResponse#sendError(int, String)} is called with the HTTP status code is {@link UWSException#INTERNAL_SERVER_ERROR}
    	 * 	and the message of the given exception.
    	 * </p>
    	 * 
    	 * 
    	 * @param error			The error to send/display.
    	 * @param request		The request which has caused the given error <i>(not used by default)</i>.
    	 * @param reqID			ID of the request.
    	 * @param user			The user which executes the given request.
    	 * @param uwsAction	The UWS action corresponding to the given request.
    	 * @param response		The response in which the error must be published.
    	 * 
    	 * @throws IOException	If there is an error when calling {@link HttpServletResponse#sendError(int, String)}.
    	 * 
    	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)}
    	 */
    	public final void sendError(Exception error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
    		// Write the error in the response and return the appropriate HTTP status code:
    		errorWriter.writeError(error, response, request, reqID, user, uwsAction);
    		// Log the error:
    		logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
    	}
    
    }