diff --git a/src/uws/service/UWSService.java b/src/uws/service/UWSService.java
index c35cad94049d4cb4ec94b111d8b13cd82b5881f8..600cf0287ffc2910fbc3b7e4efcf4f07c4533693 100644
--- a/src/uws/service/UWSService.java
+++ b/src/uws/service/UWSService.java
@@ -2,20 +2,20 @@ 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-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
@@ -60,12 +60,13 @@ import uws.service.log.DefaultUWSLog;
 import uws.service.log.UWSLog;
 import uws.service.log.UWSLog.LogLevel;
 import uws.service.request.RequestParser;
+import uws.service.wait.BlockingPolicy;
 
 /**
  * <p>This class implements directly the interface {@link UWS} and so, it represents the core of a UWS service.</p>
- * 
+ *
  * <h3>Usage</h3>
- * 
+ *
  * <p>
  * 	Using this class is very simple! An instance must be created by providing at a factory - {@link UWSFactory} - and a file manager - {@link UWSFileManager}.
  * 	This creation must be done in the init() function of a {@link HttpServlet}. Then, still in init(), at least one job list must be created.
@@ -76,7 +77,7 @@ import uws.service.request.RequestParser;
  * <pre>
  * public class MyUWSService extends HttpServlet {
  * 	private UWS uws;
- * 
+ *
  * 	public void init(ServletConfig config) throws ServletException {
  * 		try{
  * 			// Create the UWS service:
@@ -87,12 +88,12 @@ import uws.service.request.RequestParser;
  * 			throw new ServletException("Can not initialize the UWS service!", ue);
  * 		}
  * 	}
- * 
+ *
  * 	public void destroy(){
  *		if (uws != null)
  * 			uws.destroy();
  * 	}
- * 
+ *
  * 	public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException{
  * 		try{
  *			service.executeRequest(request, response);
@@ -102,9 +103,9 @@ import uws.service.request.RequestParser;
  * 	}
  * }
  * </pre>
- * 
+ *
  * <h3>UWS actions</h3>
- * 
+ *
  * <p>
  * 	All standard UWS actions are already implemented in this class. However, it is still possible to modify their implementation and/or to
  * 	add or remove some actions.
@@ -127,18 +128,18 @@ import uws.service.request.RequestParser;
  * 	name representing the action. Thus, it is possible to replace a UWS action implementation by using the function {@link #replaceUWSAction(UWSAction)} ; this
  * 	function will replace the action having the same name as the given action.
  * </p>
- * 
+ *
  * <h3>Home page</h3>
- * 
+ *
  * <p>
  * 	In addition of all the actions listed above, a last action is automatically added: {@link ShowHomePage}. This is the action which will display the home page of
  * 	the UWS service. It is called when the root resource of the web service is asked. To change it, you can either overwrite this action
  * 	(see {@link #replaceUWSAction(UWSAction)}) or set an home page URL with the function {@link #setHomePage(String)} <i>(the parameter is a URI pointing on either
  * 	a local or a remote resource)</i> or {@link #setHomePage(URL, boolean)}.
  * </p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.2 (09/2017)
+ * @version 4.3 (11/2017)
  */
 public class UWSService implements UWS {
 
@@ -201,6 +202,16 @@ public class UWSService implements UWS {
 	/** Lets writing/formatting any exception/throwable in a HttpServletResponse. */
 	protected ServiceErrorWriter errorWriter;
 
+	/**
+	 * Strategy to use for the blocking/wait process concerning the
+	 * {@link JobSummary} action.
+	 * <p>
+	 * 	If NULL, the standard strategy will be used: wait exactly the time asked
+	 * 	by the user (or indefinitely if none is specified).
+	 * </p>
+	 * @since 4.3 */
+	protected BlockingPolicy waitPolicy = null;
+
 	/** 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 */
@@ -211,21 +222,21 @@ public class UWSService implements UWS {
 	/* ************ */
 	/**
 	 * <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>.
 	 * @throws UWSException			If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
-	 * 
+	 *
 	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
 	 */
 	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager) throws UWSException{
@@ -234,17 +245,17 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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>.
 	 * @throws UWSException			If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
 	 */
@@ -287,15 +298,15 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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{
@@ -304,14 +315,14 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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{
@@ -340,15 +351,15 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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.
-	 * 
+	 *
 	 * @throws UWSException			If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
-	 * 
+	 *
 	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog, UWSUrl)
 	 */
 	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSUrl urlInterpreter) throws UWSException{
@@ -357,12 +368,12 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
-	 * 
+	 *
 	 * @throws UWSException			If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
 	 */
 	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final UWSUrl urlInterpreter) throws UWSException{
@@ -411,7 +422,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the object used to write/format any error in a HttpServletResponse.
-	 * 
+	 *
 	 * @return The error writer/formatter.
 	 */
 	public final ServiceErrorWriter getErrorWriter(){
@@ -420,9 +431,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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){
@@ -440,7 +451,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Sets the name of this UWS.
-	 * 
+	 *
 	 * @param name	Its new name.
 	 */
 	public final void setName(String name){
@@ -454,7 +465,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Sets the description of this UWS.
-	 * 
+	 *
 	 * @param description	Its new description.
 	 */
 	public final void setDescription(String description){
@@ -463,9 +474,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the base UWS URL.
-	 * 
+	 *
 	 * @return	The base UWS URL.
-	 * 
+	 *
 	 * @see UWSUrl#getBaseURI()
 	 */
 	public final String getBaseURI(){
@@ -479,7 +490,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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){
@@ -493,7 +504,7 @@ public class UWSService implements UWS {
 	/**
 	 * <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
@@ -503,7 +514,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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){
@@ -530,7 +541,7 @@ public class UWSService implements UWS {
 	 * 	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(JobOwner)}.
 	 * </p>
-	 * 
+	 *
 	 * @param backupManager Its new backup manager.
 	 */
 	public final void setBackupManager(final UWSBackupManager backupManager){
@@ -542,12 +553,54 @@ public class UWSService implements UWS {
 		return requestParser;
 	}
 
+	/**
+	 * Get the currently used strategy for the blocking behavior of the
+	 * Job Summary action.
+	 *
+	 * <p>
+	 * 	This strategy lets decide how long a WAIT request must block a HTTP
+	 * 	request. With a such policy, the waiting time specified by the user may
+	 * 	be modified.
+	 * </p>
+	 *
+	 * @return	The WAIT strategy,
+	 *        	or NULL if the default one (i.e. wait the time specified by the
+	 *        	user) is used.
+	 *
+	 * @since 4.3
+	 */
+	public final BlockingPolicy getWaitPolicy(){
+		return waitPolicy;
+	}
+
+	/**
+	 * Set the strategy to use for the blocking behavior of the
+	 * Job Summary action.
+	 *
+	 * <p>
+	 * 	This strategy lets decide whether a WAIT request must block a HTTP
+	 * 	request and how long. With a such policy, the waiting time specified by
+	 * 	the user may be modified.
+	 * </p>
+	 *
+	 * @param waitPolicy	The WAIT strategy to use,
+	 *                  	or NULL if the default one (i.e. wait the time
+	 *                  	specified by the user ;
+	 *                  	if no time is specified the HTTP request may be
+	 *                  	blocked indefinitely) must be used.
+	 *
+	 * @since 4.3
+	 */
+	public final void setWaitPolicy(final BlockingPolicy waitPolicy){
+		this.waitPolicy = waitPolicy;
+	}
+
 	/* ******************** */
 	/* 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(){
@@ -556,7 +609,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
 	 */
@@ -566,7 +619,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
@@ -579,7 +632,7 @@ public class UWSService implements UWS {
 	/**
 	 * <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){
@@ -590,7 +643,7 @@ public class UWSService implements UWS {
 	/**
 	 * 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(){
@@ -608,16 +661,16 @@ public class UWSService implements UWS {
 
 	/**
 	 * <p>Get the MIME type of the custom home page.</p>
-	 * 
+	 *
 	 * <p>By default, it is the same as the default home page: "text/html".</p>
-	 * 
+	 *
 	 * <p><i>Note:
 	 * 	This function has a sense only if the HOME PAGE resource of this UWS service
 	 * 	is still the default home page (i.e. {@link ShowHomePage}).
 	 * </i></p>
-	 * 
+	 *
 	 * @return	MIME type of the custom home page.
-	 * 
+	 *
 	 * @since 4.2
 	 */
 	public final String getHomePageMimeType(){
@@ -626,16 +679,16 @@ public class UWSService implements UWS {
 
 	/**
 	 * <p>Set the MIME type of the custom home page.</p>
-	 * 
+	 *
 	 * <p>A NULL value will be considered as "text/html".</p>
-	 * 
+	 *
 	 * <p><i>Note:
 	 * 	This function has a sense only if the HOME PAGE resource of this UWS service
 	 * 	is still the default home page (i.e. {@link ShowHomePage}).
 	 * </i></p>
-	 * 
+	 *
 	 * @param mime	MIME type of the custom home page.
-	 * 
+	 *
 	 * @since 4.2
 	 */
 	public final void setHomePageMimeType(final String mime){
@@ -647,7 +700,7 @@ public class UWSService implements UWS {
 	/* ********************** */
 	/**
 	 * Gets the MIME type of the serializer to use by default.
-	 * 
+	 *
 	 * @return	The MIME type of the default serializer.
 	 */
 	public final String getDefaultSerializer(){
@@ -656,9 +709,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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{
@@ -672,7 +725,7 @@ public class UWSService implements UWS {
 	 * <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.
 	 */
@@ -688,9 +741,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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){
@@ -699,7 +752,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the total number of serializers available in this UWS.
-	 * 
+	 *
 	 * @return	The number of its serializers.
 	 */
 	public final int getNbSerializers(){
@@ -708,7 +761,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets an iterator of the list of all serializers available in this UWS.
-	 * 
+	 *
 	 * @return	An iterator on its serializers.
 	 */
 	public final Iterator<UWSSerializer> getSerializers(){
@@ -741,7 +794,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the serializer choosen during the last call of {@link #getSerializer(String)}.
-	 * 
+	 *
 	 * @return	The last used serializer.
 	 */
 	public final UWSSerializer getChoosenSerializer(){
@@ -750,7 +803,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
@@ -761,7 +814,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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(){
@@ -773,9 +826,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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){
@@ -792,7 +845,7 @@ public class UWSService implements UWS {
 	/* ********************* */
 	/**
 	 * An iterator on the jobs lists list.
-	 * 
+	 *
 	 * @see java.lang.Iterable#iterator()
 	 */
 	@Override
@@ -812,13 +865,13 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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(UWS)
 	 * @see UWS#addJobList(JobList)
 	 */
@@ -847,11 +900,11 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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(UWS)
 	 */
@@ -873,7 +926,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Destroys all managed jobs lists.
-	 * 
+	 *
 	 * @see #destroyJobList(String)
 	 */
 	public final void destroyAllJobLists(){
@@ -887,12 +940,12 @@ public class UWSService implements UWS {
 	/* ********************** */
 	/**
 	 * <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, JobOwner, 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){
@@ -904,12 +957,12 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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{
@@ -922,12 +975,12 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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{
@@ -940,9 +993,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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).
@@ -961,7 +1014,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the number of actions this UWS has.
-	 * 
+	 *
 	 * @return	The number of its actions.
 	 */
 	public final int getNbUWSActions(){
@@ -970,9 +1023,9 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
 	 */
@@ -986,7 +1039,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets all actions of this UWS.
-	 * 
+	 *
 	 * @return	An iterator on its actions.
 	 */
 	public final Iterator<UWSAction> getUWSActions(){
@@ -995,7 +1048,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * Gets the UWS action executed during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}.
-	 * 
+	 *
 	 * @return	The last used UWS action.
 	 */
 	public final UWSAction getExecutedAction(){
@@ -1004,11 +1057,11 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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{
@@ -1017,7 +1070,7 @@ public class UWSService implements UWS {
 
 	/**
 	 * 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.
@@ -1036,13 +1089,13 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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){
@@ -1057,7 +1110,7 @@ public class UWSService implements UWS {
 	/**
 	 * <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>
@@ -1065,15 +1118,15 @@ public class UWSService implements UWS {
 	 * 	<li>Iterate - in order - on all available actions and apply the first which matches.
 	 * 		(see {@link UWSAction#match(UWSUrl, JobOwner, HttpServletRequest)} and {@link UWSAction#apply(UWSUrl, JobOwner, 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)
@@ -1200,13 +1253,13 @@ public class UWSService implements UWS {
 
 	/**
 	 * <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.
 	 */
@@ -1224,17 +1277,17 @@ public class UWSService implements UWS {
 	 * 	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 #sendError(Throwable, HttpServletRequest, String, JobOwner, String, HttpServletResponse)
 	 */
@@ -1255,17 +1308,17 @@ public class UWSService implements UWS {
 	 * 	{@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 ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)
 	 */
 	public final void sendError(Throwable error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
diff --git a/src/uws/service/UWSServlet.java b/src/uws/service/UWSServlet.java
index e2015fb55964658945e09c3be2e7f67bfd32edbf..ebbde879ce2dd59affa044b7e61d62805f640da6 100644
--- a/src/uws/service/UWSServlet.java
+++ b/src/uws/service/UWSServlet.java
@@ -59,6 +59,7 @@ import uws.job.serializer.UWSSerializer;
 import uws.job.serializer.XMLSerializer;
 import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
+import uws.service.actions.JobSummary;
 import uws.service.actions.UWSAction;
 import uws.service.backup.UWSBackupManager;
 import uws.service.error.DefaultUWSErrorWriter;
@@ -71,6 +72,7 @@ import uws.service.log.UWSLog.LogLevel;
 import uws.service.request.RequestParser;
 import uws.service.request.UWSRequestParser;
 import uws.service.request.UploadFile;
+import uws.service.wait.BlockingPolicy;
 
 /**
  * <p>
@@ -151,7 +153,7 @@ import uws.service.request.UploadFile;
  * </p>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (10/2017)
+ * @version 4.3 (11/2017)
  */
 public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory {
 	private static final long serialVersionUID = 1L;
@@ -199,6 +201,17 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	/** Lets writing/formatting any exception/throwable in a HttpServletResponse. */
 	protected ServiceErrorWriter errorWriter;
 
+	/**
+	 * Strategy to use for the blocking/wait process concerning the
+	 * {@link #doJobSummary(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}
+	 * action.
+	 * <p>
+	 * 	If NULL, the standard strategy will be used: wait exactly the time asked
+	 * 	by the user (or indefinitely if none is specified).
+	 * </p>
+	 * @since 4.3 */
+	protected BlockingPolicy waitPolicy = null;
+
 	@Override
 	public final void init(ServletConfig config) throws ServletException{
 		super.init(config);
@@ -503,6 +516,52 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		}
 	}
 
+	/* ***************** */
+	/* BLOCKING BEHAVIOR */
+	/* ***************** */
+
+	/**
+	 * Get the currently used strategy for the blocking behavior of the
+	 * Job Summary action.
+	 *
+	 * <p>
+	 * 	This strategy lets decide how long a WAIT request must block a HTTP
+	 * 	request. With a such policy, the waiting time specified by the user may
+	 * 	be modified.
+	 * </p>
+	 *
+	 * @return	The WAIT strategy,
+	 *        	or NULL if the default one (i.e. wait the time specified by the
+	 *        	user) is used.
+	 *
+	 * @since 4.3
+	 */
+	public final BlockingPolicy getWaitPolicy(){
+		return waitPolicy;
+	}
+
+	/**
+	 * Set the strategy to use for the blocking behavior of the
+	 * Job Summary action.
+	 *
+	 * <p>
+	 * 	This strategy lets decide whether a WAIT request must block a HTTP
+	 * 	request and how long. With a such policy, the waiting time specified by
+	 * 	the user may be modified.
+	 * </p>
+	 *
+	 * @param waitPolicy	The WAIT strategy to use,
+	 *                  	or NULL if the default one (i.e. wait the time
+	 *                  	specified by the user ;
+	 *                  	if no time is specified the HTTP request may be
+	 *                  	blocked indefinitely) must be used.
+	 *
+	 * @since 4.3
+	 */
+	public final void setWaitPolicy(final BlockingPolicy waitPolicy){
+		this.waitPolicy = waitPolicy;
+	}
+
 	/* *********** */
 	/* UWS ACTIONS */
 	/* *********** */
@@ -604,6 +663,9 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		// Get the job:
 		UWSJob job = getJob(requestUrl);
 
+		// Block if necessary:
+		JobSummary.block(waitPolicy, req, job, user);
+
 		// Write the job summary:
 		UWSSerializer serializer = getSerializer(req.getHeader("Accept"));
 		resp.setContentType(serializer.getMimeType());
diff --git a/src/uws/service/actions/JobSummary.java b/src/uws/service/actions/JobSummary.java
index e35e1d80cab2d67f234f2d6044c2d0c467e47fc0..89acba1bba92383a20f16609fac77bed818be489 100644
--- a/src/uws/service/actions/JobSummary.java
+++ b/src/uws/service/actions/JobSummary.java
@@ -2,25 +2,26 @@ package uws.service.actions;
 
 /*
  * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *
+ * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
+import java.util.Enumeration;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
@@ -28,27 +29,40 @@ import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
 import uws.UWSToolBox;
+import uws.job.ExecutionPhase;
 import uws.job.UWSJob;
 import uws.job.serializer.UWSSerializer;
 import uws.job.user.JobOwner;
 import uws.service.UWSService;
 import uws.service.UWSUrl;
 import uws.service.log.UWSLog.LogLevel;
+import uws.service.wait.BlockingPolicy;
+import uws.service.wait.WaitObserver;
 
 /**
- * <p>The "Get Job" action of a UWS.</p>
- * 
- * <p><i><u>Note:</u> The corresponding name is {@link UWSAction#JOB_SUMMARY}.</i></p>
- * 
- * <p>This action returns the summary of the job specified in the given UWS URL.
- * This summary is serialized by the {@link UWSSerializer} choosed in function of the HTTP Accept header.</p>
- * 
+ * The "Get Job" action of a UWS.
+ *
+ * <p><i>Note:
+ * 	The corresponding name is {@link UWSAction#JOB_SUMMARY}.
+ * </i></p>
+ *
+ * <p>
+ * 	This action returns the summary of the job specified in the given UWS URL.
+ * 	This summary is serialized by the {@link UWSSerializer} chosen in function
+ * 	of the HTTP Accept header.
+ * </p>
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (04/2015)
+ * @version 4.3 (11/2017)
  */
 public class JobSummary extends UWSAction {
 	private static final long serialVersionUID = 1L;
 
+	/** Name of the parameter which allows the blocking behavior
+	 * (for a specified or unlimited duration) of a {@link JobSummary} request.
+	 * @since 4.3 */
+	public final static String WAIT_PARAMETER = "WAIT";
+
 	public JobSummary(UWSService u){
 		super(u);
 	}
@@ -70,12 +84,15 @@ public class JobSummary extends UWSAction {
 	/**
 	 * Checks whether:
 	 * <ul>
-	 * 	<li>a job list name is specified in the given UWS URL <i>(<u>note:</u> the existence of the jobs list is not checked)</i>,</li>
-	 * 	<li>a job ID is given in the UWS URL <i>(<u>note:</u> the existence of the job is not checked)</i>,</li>
+	 * 	<li>a job list name is specified in the given UWS URL
+	 * 		<i>(<u>note:</u> the existence of the jobs list is not checked)</i>,
+	 * 	</li>
+	 * 	<li>a job ID is given in the UWS URL
+	 * 		<i>(<u>note:</u> the existence of the job is not checked)</i>,</li>
 	 * 	<li>there is no job attribute,</li>
 	 * 	<li>the HTTP method is HTTP-GET.</li>
 	 * </ul>
-	 * 
+	 *
 	 * @see uws.service.actions.UWSAction#match(UWSUrl, JobOwner, HttpServletRequest)
 	 */
 	@Override
@@ -85,12 +102,13 @@ public class JobSummary extends UWSAction {
 
 	/**
 	 * Gets the specified job <i>(and throw an error if not found)</i>,
-	 * chooses the serializer and write the serialization of the job in the given response.
-	 * 
+	 * chooses the serializer and write the serialization of the job in the
+	 * given response.
+	 *
 	 * @see #getJob(UWSUrl)
 	 * @see UWSService#getSerializer(String)
 	 * @see UWSJob#serialize(ServletOutputStream, UWSSerializer)
-	 * 
+	 *
 	 * @see uws.service.actions.UWSAction#apply(UWSUrl, JobOwner, HttpServletRequest, HttpServletResponse)
 	 */
 	@Override
@@ -98,6 +116,9 @@ public class JobSummary extends UWSAction {
 		// Get the job:
 		UWSJob job = getJob(urlInterpreter);
 
+		// Block if necessary:
+		JobSummary.block(uws.getWaitPolicy(), request, job, user);
+
 		// Write the job summary:
 		UWSSerializer serializer = uws.getSerializer(request.getHeader("Accept"));
 		response.setContentType(serializer.getMimeType());
@@ -115,4 +136,172 @@ public class JobSummary extends UWSAction {
 		return true;
 	}
 
+	/**
+	 * Block the current thread until the specified duration (in seconds) is
+	 * elapsed or if the execution phase of the target job changes.
+	 *
+	 * <p>
+	 * 	A blocking is performed only if the given job is in an active phase
+	 * 	(i.e. PENDING, QUEUED or EXECUTING).
+	 * </p>
+	 *
+	 * <p>This function expects the 2 following HTTP-GET parameters:</p>
+	 * <ul>
+	 * 	<li><b>WAIT</b>: <i>[MANDATORY]</i> with a value (in seconds). The value
+	 * 		             must be a positive and not null integer expressing a
+	 * 		             duration in seconds or -1 (or any other negative value)
+	 * 		             for an infinite time. If a not legal value or no value
+	 * 		             is provided, the parameter will be merely ignored.
+	 * 		             <br/>
+	 * 	                 This parameter raises a flag meaning a blocking is
+	 * 		             required (except if 0 is provided) and eventually a
+	 * 		             time (in seconds) to wait before stop blocking.
+	 * 		             <br/>
+	 * 	                 If several values are provided, only the one meaning
+	 * 		             the smallest blocking waiting time will be kept.
+	 * 		             Particularly if both a negative and a positive or null
+	 * 		             value are given, only the positive or null value will
+	 * 		             be kept.</li>
+	 *
+	 * 	<li><b>PHASE</b>: <i>[OPTIONAL]</i> A legal execution phase must be
+	 * 		              provided, otherwise this parameter will be ignored.
+	 * 		              <br/>
+	 * 	                  This parameter indicates the phase in which the job
+	 * 		              must be at the time the blocking is required. If the
+	 * 		              current job phase is different from the specified one,
+	 * 		              no blocking will be performed. Note that the allowed
+	 * 		              phases are PENDING, QUEUED and EXECUTING, because only
+	 * 		              a job in one of these phases can be blocked.
+	 * 		              <br/>
+	 * 	                  If several values are provided, only the last
+	 * 		              occurrence is kept.</li>
+	 * </ul>
+	 *
+	 * <p><i>Note:
+	 * 	A waiting time of 0 will be interpreted as "no blocking".
+	 * </i></p>
+	 *
+	 * <p><i>Note:
+	 * 	This function will have no effect if the given thread, the given HTTP
+	 * 	request or the given job is NULL.
+	 * </i></p>
+	 *
+	 * @param policy		Strategy to adopt for the blocking behavior.
+	 *              		<i>If NULL, the standard blocking behavior will be
+	 *              		performed: block the duration (eventually unlimited)
+	 *              		specified by the user.</i>
+	 * @param req			The HTTP request which asked for the blocking.
+	 *           			<b>MUST NOT be NULL, otherwise no blocking will be
+	 *           			performed.</b>
+	 * @param job			The job associate with the HTTP request.
+	 *           			<b>MUST NOT be NULL, otherwise no blocking will be
+	 *           			performed.</b>
+	 * @param user			The user who asked for the blocking behavior.
+	 *            			<i>NULL if no user is logged in.</i>
+	 *
+	 * @since 4.3
+	 */
+	public static void block(final BlockingPolicy policy, final HttpServletRequest req, final UWSJob job, final JobOwner user){
+		if (req == null || job == null)
+			return;
+
+		/* No blocking if the job is not in an "active" phase: */
+		if (job.getPhase() != ExecutionPhase.PENDING && job.getPhase() != ExecutionPhase.QUEUED && job.getPhase() != ExecutionPhase.EXECUTING)
+			return;
+
+		/* Extract the parameters WAIT (only the smallest waiting time is taken
+		 * into account) and PHASE (only the last legal occurrence is taken into
+		 * account): */
+		ExecutionPhase phase = null;
+		boolean waitGiven = false;
+		long waitingTime = 0;
+		String param;
+		String[] values;
+		Enumeration<String> parameters = req.getParameterNames();
+		while(parameters.hasMoreElements()){
+			param = parameters.nextElement();
+			values = req.getParameterValues(param);
+			// CASE: WAIT parameter
+			if (param.toUpperCase().equals("WAIT")){
+				/* note: a value MUST be given for a WAIT parameter ; if it is
+				 *       missing the parameter is ignored */
+				if (values != null){
+					for(int i = 0; i < values.length; i++){
+						try{
+							if (values[i] != null && values[i].trim().length() > 0){
+								long tmp = Long.parseLong(values[i]);
+								if (tmp < 0 && !waitGiven)
+									waitingTime = tmp;
+								else if (tmp >= 0)
+									waitingTime = (waitGiven && waitingTime >= 0) ? Math.min(waitingTime, tmp) : tmp;
+								waitGiven = true;
+							}
+						}catch(NumberFormatException nfe){}
+					}
+				}
+			}
+			// CASE: PHASE parameter
+			else if (param.toUpperCase().equals("PHASE") && values != null){
+				for(int i = values.length - 1; phase == null && i >= 0; i--){
+					try{
+						if (values[i].trim().length() > 0)
+							phase = ExecutionPhase.valueOf(values[i].toUpperCase());
+					}catch(IllegalArgumentException iae){}
+				}
+			}
+		}
+
+		/* The HTTP-GET request should block until either the specified time
+		 * (or the timeout) is reached or if the job phase changed: */
+		if (waitingTime != 0 && (phase == null || job.getPhase() == phase)){
+			Thread threadToBlock = Thread.currentThread();
+			WaitObserver observer = null;
+
+			/* Eventually limit the waiting time in function of the chosen
+			 * policy: */
+			if (policy != null)
+				waitingTime = policy.block(threadToBlock, waitingTime, job, user, req);
+
+			/* Blocking ONLY IF the duration is NOT NULL (i.e. wait during 0
+			 * seconds): */
+			if (waitingTime != 0){
+				try{
+					/* Watch the job in order to detect an execution phase
+					 * modification: */
+					observer = new WaitObserver(threadToBlock);
+					job.addObserver(observer);
+
+					/* If the job is still processing, then wait the specified
+					 * time: */
+					if (job.getPhase() == ExecutionPhase.PENDING || job.getPhase() == ExecutionPhase.QUEUED || job.getPhase() == ExecutionPhase.EXECUTING){
+						synchronized(threadToBlock){
+							// Limited duration:
+							if (waitingTime > 0)
+								threadToBlock.wait(waitingTime * 1000);
+							/* "Unlimited" duration (the wait will stop only if
+							 * the job phase changes): */
+							else
+								threadToBlock.wait();
+						}
+					}
+
+				}catch(InterruptedException ie){
+					/* If the WAIT has been interrupted, the blocking
+					 * is stopped and nothing special should happen. */
+				}
+				/* Clear all retained resources. */
+				finally{
+					// Do not observe any more the job:
+					if (observer != null)
+						job.removeObserver(observer);
+
+					/* Notify the BlockingPolicy that this Thread is no longer
+					 * blocked: */
+					if (policy != null)
+						policy.unblocked(threadToBlock, job, user, req);
+				}
+			}
+		}
+	}
+
 }
diff --git a/src/uws/service/wait/BlockingPolicy.java b/src/uws/service/wait/BlockingPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..0389f77d9fc65ba90145e69e2171c473dfe0709e
--- /dev/null
+++ b/src/uws/service/wait/BlockingPolicy.java
@@ -0,0 +1,114 @@
+package uws.service.wait;
+
+/*
+ * 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 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import javax.servlet.http.HttpServletRequest;
+
+import uws.job.UWSJob;
+import uws.job.user.JobOwner;
+
+/**
+ * Implementations of this interface define the policy to apply when a blocking
+ * of a request is asked by a UWS client.
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (11/2017)
+ * @since 4.3
+ */
+public interface BlockingPolicy {
+
+	/**
+	 * Notify this {@link BlockingPolicy} that the given thread is going to
+	 * be blocked for the specified duration. This function then decides how
+	 * long the given thread must really wait before resuming.
+	 *
+	 * <p>
+	 * 	The parameter "userDuration" and the returned value are durations
+	 * 	expressed in seconds. Both follow the same rules:
+	 * </p>
+	 * <ul>
+	 * 	<li><u>If &lt; 0</u>, the request will wait theoretically
+	 * 		                  indefinitely.</li>
+	 * 	<li><u>If 0</u>, the request will return immediately ; no wait.</li>
+	 * 	<li><u>If &gt; 0</u>, the request will wait for the specified amount of
+	 * 		                  seconds.</li>
+	 * </ul>
+	 *
+	 * <p>
+	 * 	Since a timeout or another special behavior may be chosen by this
+	 * 	{@link BlockingPolicy}, the returned value may be different from the
+	 * 	user's asked duration. The value that should be taken into account is
+	 * 	obviously the returned one.
+	 * </p>
+	 *
+	 * <p><i><b>IMPORTANT:</b>
+	 * 	This function may <b>UN</b>block an older request/thread, in function of
+	 * 	the strategy chosen/implemented by this {@link BlockingPolicy}.
+	 * </i></p>
+	 *
+	 * @param thread		Thread that is going to be blocked.
+	 *                      <i>MUST NOT be NULL. If NULL this function will
+	 *                      either do nothing and return 0 (no blocking)
+	 *                      or throw a {@link NullPointerException}.</i>
+	 * @param userDuration	Waiting duration (in seconds) asked by the user.
+	 *                    	<i>&lt; 0 means indefinite, 0 means no wait and
+	 *                    	&gt; 0 means waiting for the specified amount of
+	 *                    	seconds.</i>
+	 * @param job			The job associated with the thread.
+	 *           			<i>Should not be NULL.</i>
+	 * @param user			The user who asked for the blocking behavior.
+	 *            			<i>If NULL, the request will be concerned as
+	 *            			anonymous and a decision to identify the user
+	 *            			(e.g. use the IP address) may be chosen by the
+	 *            			{@link BlockingPolicy} implementation if
+	 *            			required.</i>
+	 * @param request		The request which is going to be blocked.
+	 *               		<i>Should not be NULL.</i>
+	 *
+	 * @return	The real duration (in seconds) that the UWS service must wait
+	 *        	before returning a response to the given HTTP request.
+	 *        	<i>&lt; 0 means indefinite, 0 means no wait and &gt; 0 means
+	 *        	waiting for the specified amount of seconds.</i>
+	 *
+	 * @throws NullPointerException	If the given thread is NULL.
+	 */
+	public long block(final Thread thread, final long userDuration, final UWSJob job, final JobOwner user, final HttpServletRequest request) throws NullPointerException;
+
+	/**
+	 * Notify this {@link BlockingPolicy} that the given thread is not blocked
+	 * anymore.
+	 *
+	 * @param unblockedThread	Thread that is not blocked any more.
+	 *                       	<b>MUST be NOT NULL.</b>
+	 * @param job				The job associated with the unblocked Thread.
+	 *           				<i>Should not be NULL.</i>
+	 * @param user				The user who originally asked for the blocking
+	 *            				behavior.
+	 *            				<i>If NULL, the request will be concerned as
+	 *            				anonymous and a decision to identify the user
+	 *            				(e.g. use the IP address) may be chosen by the
+	 *            				{@link BlockingPolicy} implementation if
+	 *            				required.</i>
+	 * @param request			The request which has been unblocked.
+	 *               			<i>Should not be NULL.</i>
+	 */
+	public void unblocked(final Thread unblockedThread, final UWSJob job, final JobOwner user, final HttpServletRequest request);
+
+}
\ No newline at end of file
diff --git a/src/uws/service/wait/LimitedBlockingPolicy.java b/src/uws/service/wait/LimitedBlockingPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..82ff7090c825291df394b62a0e98f8df0029b088
--- /dev/null
+++ b/src/uws/service/wait/LimitedBlockingPolicy.java
@@ -0,0 +1,89 @@
+package uws.service.wait;
+
+/*
+ * 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 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import javax.servlet.http.HttpServletRequest;
+
+import uws.job.UWSJob;
+import uws.job.user.JobOwner;
+
+/**
+ * In this {@link BlockingPolicy}, the blocking is limited by a maximum
+ * waiting time. Thus, unlimited blocking is prevented.
+ *
+ * <p>
+ * 	More concretely, if no timeout is specified by the user, {@link #timeout} is
+ * 	returned.
+ * </p>
+ *
+ * <p>
+ * 	If at creation of this policy no timeout is specified, a default one is set:
+ * 	{@link #DEFAULT_TIMEOUT} (= {@value #DEFAULT_TIMEOUT} seconds).
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (11/2017)
+ * @since 4.3
+ */
+public class LimitedBlockingPolicy implements BlockingPolicy {
+
+	/** Default timeout (in seconds) set by this policy at creation if none is
+	 * specified. */
+	public final static long DEFAULT_TIMEOUT = 60;
+
+	/** Maximum blocking duration (in seconds).
+	 * <i>This attribute is set by default to {@link #DEFAULT_TIMEOUT}. */
+	protected long timeout = DEFAULT_TIMEOUT;
+
+	/**
+	 * Build a blocking policy with the default timeout
+	 * (= {@value #DEFAULT_TIMEOUT} seconds).
+	 */
+	public LimitedBlockingPolicy(){}
+
+	/**
+	 * Build a {@link BlockingPolicy} which will limit blocking duration to the
+	 * given value.
+	 *
+	 * <p><i>IMPORTANT:
+	 * 	If {@link #timeout} is &lt; 0, the default timeout
+	 * 	(i.e. {@value #DEFAULT_TIMEOUT}) will be set automatically by this
+	 * 	constructor.
+	 * </i></p>
+	 *
+	 * @param timeout	Maximum blocking duration (in seconds).
+	 */
+	public LimitedBlockingPolicy(final long timeout){
+		this.timeout = (timeout < 0) ? DEFAULT_TIMEOUT : timeout;
+	}
+
+	@Override
+	public long block(final Thread thread, final long userDuration, final UWSJob job, final JobOwner user, final HttpServletRequest request){
+		// Nothing should happen if no thread and/or no job is provided:
+		if (job == null || thread == null)
+			return 0;
+
+		return (userDuration < 0 || userDuration > timeout) ? timeout : userDuration;
+	}
+
+	@Override
+	public void unblocked(final Thread unblockedThread, final UWSJob job, final JobOwner user, final HttpServletRequest request){}
+
+}
\ No newline at end of file
diff --git a/src/uws/service/wait/UserLimitedBlockingPolicy.java b/src/uws/service/wait/UserLimitedBlockingPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b875ba55ebf7153256c6d14e9110108bafd70a8
--- /dev/null
+++ b/src/uws/service/wait/UserLimitedBlockingPolicy.java
@@ -0,0 +1,311 @@
+package uws.service.wait;
+
+/*
+ * 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 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import javax.servlet.http.HttpServletRequest;
+
+import uws.job.UWSJob;
+import uws.job.user.JobOwner;
+
+/**
+ * This {@link BlockingPolicy} extends the {@link LimitedBlockingPolicy}.
+ * It proposes to limit the blocking duration, but it also limits the
+ * number of blocked threads for a given job and user.
+ *
+ * <h3>Blocked per Job AND User</h3>
+ *
+ * <p>
+ * 	The limit on the number of threads is valid ONLY for a given job AND
+ * 	a given user. For example, let's assume there is a limit
+ * 	of N blocking requests per job and user. The user U1 can start maximum N
+ * 	blocking requests to access the job J1 but not more. During this
+ * 	time he can also start up to N blocking access requests to any other job.
+ * 	And since this limit is valid only per user, another user U2 can also
+ * 	start up to N blocking requests on the job J1 without being affected by the
+ * 	fact that the limit is reached by the user U1 on this same job.
+ * </p>
+ *
+ * <p><i>Note:
+ * 	If no user is identified, the IP address will be used instead.
+ * </i></p>
+ *
+ * <h3>What happens when the limit is reached?</h3>
+ *
+ * <p>In a such case, 2 strategies are proposed:</p>
+ * <ul>
+ * 	<li>unblock the oldest blocked thread and accept the new blocking</li>
+ * 	<li>do not block for the new asked blocking (then
+ * 		{@link #block(Thread, long, UWSJob, JobOwner, HttpServletRequest)} will
+ * 		return 0)</li>
+ * </ul>
+ *
+ * <p>
+ * 	The strategy to use MUST be specified at creation using
+ * 	{@link #UserLimitedBlockingPolicy(long, int, boolean)} with a third
+ * 	parameter set to <code>true</code> to unblock the oldest thread if needed,
+ * 	or <code>false</code> to prevent blocking if the limit is reached.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (11/2017)
+ * @since 4.3
+ */
+public class UserLimitedBlockingPolicy extends LimitedBlockingPolicy {
+
+	/** Default number of allowed blocked threads. */
+	public final static int DEFAULT_NB_MAX_BLOCKED = 3;
+
+	/** The maximum number of blocked threads for a given job and user. */
+	protected final int maxBlockedThreadsByUser;
+
+	/** List of all blocked threads.
+	 * <p>
+	 * 	Keys are an ID identifying a given job AND a given user
+	 * 	(basically: <code>jobId+";"+userId</code> ;  see
+	 * 	{@link #buildKey(UWSJob, JobOwner, HttpServletRequest)} for more
+	 * 	details).
+	 * </p>
+	 * <p>
+	 * 	Values are fixed-length queues of blocked threads.
+	 * </p> */
+	protected final Map<String,BlockingQueue<Thread>> blockedThreads;
+
+	/** Indicate what should happen when the maximum number of threads for a
+	 * given job and user is reached.
+	 * <p>
+	 * 	<code>true</code> to unblock the oldest blocked thread in order to allow
+	 * 	                  the new blocking.
+	 * 	<code>false</code> to forbid new blocking.
+	 * </p> */
+	protected final boolean unblockOld;
+
+	/**
+	 * Build a default {@link UserLimitedBlockingPolicy}.
+	 *
+	 * <p>
+	 * 	This instance will limit the number of blocked threads per user and job
+	 * 	to the default value (i.e. {@value #DEFAULT_NB_MAX_BLOCKED}) and will
+	 * 	limit the blocking duration to the default timeout
+	 * 	(see {@link LimitedBlockingPolicy#DEFAULT_TIMEOUT}).
+	 * </p>
+	 *
+	 * <p>
+	 * 	When the limit of threads is reached, the oldest thread is unblocked
+	 * 	in order to allow the new incoming blocking.
+	 * </p>
+	 */
+	public UserLimitedBlockingPolicy(){
+		this(DEFAULT_TIMEOUT, DEFAULT_NB_MAX_BLOCKED);
+	}
+
+	/**
+	 * Build a {@link UserLimitedBlockingPolicy} which will limit the blocking
+	 * duration to the given value and will limit the number of blocked threads
+	 * per job and user to the default value (i.e.
+	 * {@value #DEFAULT_NB_MAX_BLOCKED}).
+	 *
+	 * <p>
+	 * 	When the limit of threads is reached, the oldest thread is unblocked
+	 * 	in order to allow the new incoming blocking.
+	 * </p>
+	 *
+	 * @param timeout	Maximum blocking duration (in seconds).
+	 *               	<i>If &lt; 0, the default timeout (see
+	 *               	{@link LimitedBlockingPolicy#DEFAULT_TIMEOUT}) will be
+	 *               	set.</i>
+	 *
+	 * @see LimitedBlockingPolicy#LimitedBlockingPolicy(long)
+	 */
+	public UserLimitedBlockingPolicy(final long timeout){
+		this(timeout, DEFAULT_NB_MAX_BLOCKED);
+	}
+
+	/**
+	 * Build a {@link UserLimitedBlockingPolicy} which will limit the blocking
+	 * duration to the given value and will limit the number of blocked threads
+	 * per job and user to the given value.
+	 *
+	 * <p>
+	 * 	When the limit of threads is reached, the oldest thread is unblocked in
+	 * 	order to allow the new incoming blocking.
+	 * </p>
+	 *
+	 * @param timeout		Maximum blocking duration (in seconds).
+	 *               		<i>If &lt; 0, the default timeout (see
+	 *               		{@link LimitedBlockingPolicy#DEFAULT_TIMEOUT}) will
+	 *               		be set.</i>
+	 * @param maxNbBlocked	Maximum number of blocked threads allowed for a
+	 *                    	given job and a given user.
+	 *                    	<i>If &le; 0, this parameter will be ignored and the
+	 *                    	default value (i.e. {@value #DEFAULT_NB_MAX_BLOCKED})
+	 *                    	will be set instead.</i>
+	 */
+	public UserLimitedBlockingPolicy(final long timeout, final int maxNbBlocked){
+		this(timeout, maxNbBlocked, true);
+	}
+
+	/**
+	 * Build a {@link UserLimitedBlockingPolicy} which will limit the blocking
+	 * duration to the given value and will limit the number of blocked threads
+	 * per job and user to the given value.
+	 *
+	 * <p>
+	 * 	When the limit of threads is reached, the oldest thread is unblocked if
+	 * 	the 3rd parameter is <code>true</code>, or new incoming blocking will
+	 * 	be forbidden if this parameter is <code>false</code>.
+	 * </p>
+	 *
+	 * @param timeout		Maximum blocking duration (in seconds).
+	 *               		<i>If &lt; 0, the default timeout (see
+	 *               		{@link LimitedBlockingPolicy#DEFAULT_TIMEOUT}) will
+	 *               		be set.</i>
+	 * @param maxNbBlocked	Maximum number of blocked threads allowed for a
+	 *                    	given job and a given user.
+	 *                    	<i>If &le; 0, this parameter will be ignored and the
+	 *                    	default value (i.e. {@value #DEFAULT_NB_MAX_BLOCKED})
+	 *                    	will be set instead.</i>
+	 * @param unblockOld	Set the behavior to adopt when the maximum number of
+	 *                  	threads is reached for a given job and user.
+	 *                  	<code>true</code> to unblock the oldest thread in
+	 *                  	order to allow the new incoming blocking,
+	 *                  	<code>false</code> to forbid the new incoming
+	 *                  	blocking.
+	 */
+	public UserLimitedBlockingPolicy(final long timeout, final int maxNbBlocked, final boolean unblockOld){
+		super(timeout);
+		maxBlockedThreadsByUser = (maxNbBlocked <= 0) ? DEFAULT_NB_MAX_BLOCKED : maxNbBlocked;
+		blockedThreads = Collections.synchronizedMap(new HashMap<String,BlockingQueue<Thread>>());
+		this.unblockOld = unblockOld;
+	}
+
+	/**
+	 * Build the key for the map {@link #blockedThreads}.
+	 *
+	 * <p>The built key is: <code>jobId + ";" + userId</code>.</p>
+	 *
+	 * <p><i>Note:
+	 * 	If no user is logged in or if the user is not specified here or if it
+	 * 	does not have any ID, the IP address of the HTTP client will be used
+	 * 	instead.
+	 * </i></p>
+	 *
+	 * @param job		Job associated with the request to block.
+	 *           		<b>MUST NOT be NULL.</b>
+	 * @param user		User who asked the blocking behavior.
+	 *            		<i>If NULL (or it has a NULL ID), the IP address of the
+	 *            		HTTP client will be used.</i>
+	 * @param request	HTTP request which should be blocked.
+	 *               	<i>SHOULD NOT be NULL.</i>
+	 *
+	 * @return	The corresponding map key.
+	 *        	<i>NEVER NULL.</i>
+	 */
+	protected final String buildKey(final UWSJob job, final JobOwner user, final HttpServletRequest request){
+		if (user == null || user.getID() == null){
+			if (request == null)
+				return job.getJobId() + ";???";
+			else
+				return job.getJobId() + ";" + request.getRemoteAddr();
+		}else
+			return job.getJobId() + ";" + user.getID();
+	}
+
+	@Override
+	public long block(final Thread thread, final long userDuration, final UWSJob job, final JobOwner user, final HttpServletRequest request){
+		// Nothing should happen if no thread and/or no job is provided:
+		if (job == null || thread == null)
+			return 0;
+
+		// Get the ID of the blocking (job+user):
+		String id = buildKey(job, user, request);
+
+		// Get the corresponding queue (if any):
+		BlockingQueue<Thread> queue = blockedThreads.get(id);
+		if (queue == null)
+			queue = new ArrayBlockingQueue<Thread>(maxBlockedThreadsByUser);
+
+		// Try to add the recently blocked thread:
+		if (!queue.offer(thread)){
+			/* If it fails, 2 strategies are possible: */
+			/* 1/ Unblock the oldest blocked thread and add the given thread
+			 *    into the queue: */
+			if (unblockOld){
+				// Get the oldest blocked thread:
+				Thread old = queue.poll();
+				// Wake it up // Unblock it:
+				if (old != null){
+					synchronized(old){
+						old.notifyAll();
+					}
+				}
+				// Add the thread into the queue:
+				queue.offer(thread);
+			}
+			/* 2/ The given thread CAN NOT be blocked because too many threads
+			 *    for this job and user are already blocked => unblock it! */
+			else
+				return 0;
+		}
+
+		// Add the queue into the map:
+		blockedThreads.put(id, queue);
+
+		// Return the eventually limited duration to wait:
+		return super.block(thread, userDuration, job, user, request);
+
+	}
+
+	@Override
+	public void unblocked(final Thread unblockedThread, final UWSJob job, final JobOwner user, final HttpServletRequest request){
+		// Nothing should happen if no thread and/or no job is provided:
+		if (job == null || unblockedThread == null)
+			return;
+
+		// Get the ID of the blocking (job+user):
+		String id = buildKey(job, user, request);
+
+		// Get the corresponding queue (if any):
+		BlockingQueue<Thread> queue = blockedThreads.get(id);
+
+		if (queue != null){
+			Iterator<Thread> it = queue.iterator();
+			// Search for the corresponding item inside the queue:
+			while(it.hasNext()){
+				// When found...
+				if (it.next().equals(unblockedThread)){
+					// ...remove it from the queue:
+					it.remove();
+					// If the queue is now empty, remove the queue from the map:
+					if (queue.isEmpty())
+						blockedThreads.remove(id);
+					return;
+				}
+			}
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/service/wait/WaitObserver.java b/src/uws/service/wait/WaitObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..a6a3ac34c390a9e52f100bab9651c6e6b8898078
--- /dev/null
+++ b/src/uws/service/wait/WaitObserver.java
@@ -0,0 +1,60 @@
+package uws.service.wait;
+
+/*
+ * 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 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+import uws.job.JobObserver;
+import uws.job.UWSJob;
+
+/**
+ * Job observer that unblock (here: notify) the given thread when a change of
+ * the execution phase is detected.
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (11/2017)
+ * @since 4.3
+ */
+public class WaitObserver implements JobObserver {
+	private static final long serialVersionUID = 1L;
+
+	/** Thread to notify in case an execution phase occurs. */
+	private final Thread waitingThread;
+
+	/**
+	 * Build a {@link JobObserver} which will wake up the given thread when the
+	 * execution phase of watched jobs changes.
+	 *
+	 * @param thread	Thread to notify.
+	 */
+	public WaitObserver(final Thread thread){
+		waitingThread = thread;
+	}
+
+	@Override
+	public void update(final UWSJob job, final ExecutionPhase oldPhase, final ExecutionPhase newPhase) throws UWSException{
+		if (oldPhase != null && newPhase != null && oldPhase != newPhase){
+			synchronized(waitingThread){
+				waitingThread.notify();
+			}
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/service/actions/TestJobSummary.java b/test/uws/service/actions/TestJobSummary.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5a4fd5fa283dfc1e7abc3443f7c622474654c68
--- /dev/null
+++ b/test/uws/service/actions/TestJobSummary.java
@@ -0,0 +1,645 @@
+package uws.service.actions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+import uws.job.user.JobOwner;
+import uws.service.wait.BlockingPolicy;
+import uws.service.wait.LimitedBlockingPolicy;
+
+public class TestJobSummary {
+
+	@Before
+	public void setUp() throws Exception{}
+
+	@Test
+	public void testBlock(){
+		long waitingDuration = 1;
+
+		/* **************************************** */
+		/* NO BLOCKING POLICY (=> default behavior) */
+
+		UWSJob job = new UWSJob(new UWSParameters());
+		TestHttpServletRequest req = new TestHttpServletRequest();
+		req.addParams("WAIT", "" + waitingDuration);
+
+		// Without request and/or job => No blocking should occur:
+		TestThread t = new TestThread(null, null, null, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		t = new TestThread(null, req, null, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		t = new TestThread(null, null, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+
+		// No WAIT value or a not legal value (not integer) => no blocking:
+		req.clearParams();
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		req.addParams("WAIT", "");
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		req.clearParams();
+		req.addParams("WAIT", "foo");
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+
+		// With a job not in an "active" phase => No blocking ; immediate return:
+		req.clearParams();
+		req.addParams("WAIT", "" + waitingDuration);
+		ExecutionPhase[] nonActivePhases = new ExecutionPhase[]{ExecutionPhase.COMPLETED,ExecutionPhase.ABORTED,ExecutionPhase.ERROR,ExecutionPhase.ARCHIVED,ExecutionPhase.HELD,ExecutionPhase.SUSPENDED,ExecutionPhase.UNKNOWN};
+		for(ExecutionPhase p : nonActivePhases){
+			try{
+				job.setPhase(p, true);
+			}catch(UWSException ue){
+				fail("No error should occur when forcing a phase modification!");
+			}
+			t = new TestThread(null, req, job, null);
+			t.start();
+			waitFor(t);
+			assertFalse(t.isAlive());
+			assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		}
+
+		// With a job in one of the "active" phases:
+		ExecutionPhase[] activePhases = new ExecutionPhase[]{ExecutionPhase.PENDING,ExecutionPhase.QUEUED,ExecutionPhase.EXECUTING};
+		for(ExecutionPhase p : activePhases){
+			try{
+				job.setPhase(p, true);
+			}catch(UWSException ue){
+				fail("No error should occur when forcing a phase modification!");
+			}
+			t = new TestThread(null, req, job, null);
+			t.start();
+			waitFor(t);
+			assertFalse(t.isAlive());
+			assertEquals(waitingDuration, t.getTime());
+		}
+
+		// With a PHASE parameter:
+		req.addParams("PHASE", "EXECUTING");
+		activePhases = new ExecutionPhase[]{ExecutionPhase.PENDING,ExecutionPhase.QUEUED};
+		for(ExecutionPhase p : activePhases){
+			try{
+				job.setPhase(p, true);
+			}catch(UWSException ue){
+				fail("No error should occur when forcing a phase modification!");
+			}
+			t = new TestThread(null, req, job, null);
+			t.start();
+			waitFor(t);
+			assertFalse(t.isAlive());
+			assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		}
+		try{
+			job.setPhase(ExecutionPhase.EXECUTING, true);
+		}catch(UWSException ue){
+			fail("No error should occur when forcing a phase modification!");
+		}
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertEquals(waitingDuration, t.getTime());
+
+		// With several WAIT and PHASE parameters:
+		waitingDuration = 2;
+		req.addParams("wait", "" + waitingDuration);
+		req.addParams("PHASE", "PENDING");
+		activePhases = new ExecutionPhase[]{ExecutionPhase.EXECUTING,ExecutionPhase.QUEUED};
+		for(ExecutionPhase p : activePhases){
+			try{
+				job.setPhase(p, true);
+			}catch(UWSException ue){
+				fail("No error should occur when forcing a phase modification!");
+			}
+			t = new TestThread(null, req, job, null);
+			t.start();
+			waitFor(t);
+			assertFalse(t.isAlive());
+			assertTrue(t.getTime() >= 0 && t.getTime() < 1);
+		}
+		try{
+			job.setPhase(ExecutionPhase.PENDING, true);
+		}catch(UWSException ue){
+			fail("No error should occur when forcing a phase modification!");
+		}
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertEquals(1, t.getTime());
+
+		// With several WAIT parameters, including a negative one:
+		req.clearParams();
+		req.addParams("Wait", "-10");
+		req.addParams("wait", "1");
+		req.addParams("WAIT", "5");
+		try{
+			job.setPhase(ExecutionPhase.PENDING, true);
+		}catch(UWSException ue){
+			fail("No error should occur when forcing a phase modification!");
+		}
+		t = new TestThread(null, req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertEquals(1, t.getTime());
+
+		/* ********************** */
+		/* WITH A BLOCKING POLICY */
+		req.clearParams();
+		req.addParams("wait", "" + waitingDuration);
+		long policyDuration = 1;
+		try{
+			job.setPhase(ExecutionPhase.EXECUTING, true);
+		}catch(UWSException ue){
+			fail("No error should occur when forcing a phase modification!");
+		}
+		t = new TestThread(new LimitedBlockingPolicy(policyDuration), req, job, null);
+		t.start();
+		waitFor(t);
+		assertFalse(t.isAlive());
+		assertEquals(policyDuration, t.getTime());
+	}
+
+	protected final void waitALittle(){
+		synchronized(this){
+			try{
+				Thread.sleep(10);
+			}catch(InterruptedException e){
+				e.printStackTrace();
+			}
+		}
+	}
+
+	protected final void waitFor(final Thread t){
+		synchronized(this){
+			try{
+				t.join();
+			}catch(InterruptedException e){
+				e.printStackTrace();
+			}
+		}
+	}
+
+	protected static final class TestThread extends Thread {
+
+		private final BlockingPolicy policy;
+		private final HttpServletRequest req;
+		private final UWSJob job;
+		private final JobOwner user;
+
+		public long start = -1;
+		public long end = -1;
+
+		public TestThread(final BlockingPolicy policy, final HttpServletRequest req, final UWSJob job, final JobOwner user){
+			this.policy = policy;
+			this.req = req;
+			this.job = job;
+			this.user = user;
+		}
+
+		@Override
+		public void run(){
+			start = System.currentTimeMillis();
+			JobSummary.block(policy, req, job, user);
+			end = System.currentTimeMillis();
+		}
+
+		/**
+		 * Get execution duration in seconds.
+		 *
+		 * @return	Execution duration of this thread in seconds
+		 *        	or -1 if still alive.
+		 */
+		public final long getTime(){
+			return (isAlive() || end > -1) ? (end - start) / 1000 : -1;
+		}
+
+	}
+
+	protected final static class TestHttpServletRequest implements HttpServletRequest {
+
+		private HashMap<String,String[]> parameters = new HashMap<String,String[]>();
+
+		private static class NamesEnumeration implements Enumeration<String> {
+
+			private final Iterator<String> it;
+
+			public NamesEnumeration(final Set<String> names){
+				this.it = names.iterator();
+			}
+
+			@Override
+			public boolean hasMoreElements(){
+				return it.hasNext();
+			}
+
+			@Override
+			public String nextElement(){
+				return it.next();
+			}
+
+		}
+
+		public void addParams(final String name, final String value){
+			if (parameters.containsKey(name)){
+				String[] values = parameters.get(name);
+				values = Arrays.copyOf(values, values.length + 1);
+				values[values.length - 1] = value;
+				parameters.put(name, values);
+			}else
+				parameters.put(name, new String[]{value});
+		}
+
+		public void clearParams(){
+			parameters.clear();
+		}
+
+		@Override
+		public Enumeration<String> getParameterNames(){
+			return new NamesEnumeration(parameters.keySet());
+		}
+
+		@Override
+		public String[] getParameterValues(String name){
+			return parameters.get(name);
+		}
+
+		@Override
+		public Map<String,String[]> getParameterMap(){
+			return parameters;
+		}
+
+		@Override
+		public String getParameter(String name){
+			String[] values = parameters.get(name);
+			if (values == null || values.length == 0)
+				return null;
+			else
+				return values[0];
+		}
+
+		@Override
+		public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1){
+			return null;
+		}
+
+		@Override
+		public AsyncContext startAsync(){
+			return null;
+		}
+
+		@Override
+		public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException{
+
+		}
+
+		@Override
+		public void setAttribute(String arg0, Object arg1){
+
+		}
+
+		@Override
+		public void removeAttribute(String arg0){
+
+		}
+
+		@Override
+		public boolean isSecure(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncSupported(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncStarted(){
+			return false;
+		}
+
+		@Override
+		public ServletContext getServletContext(){
+			return null;
+		}
+
+		@Override
+		public int getServerPort(){
+			return 0;
+		}
+
+		@Override
+		public String getServerName(){
+			return null;
+		}
+
+		@Override
+		public String getScheme(){
+			return null;
+		}
+
+		@Override
+		public RequestDispatcher getRequestDispatcher(String arg0){
+			return null;
+		}
+
+		@Override
+		public int getRemotePort(){
+			return 0;
+		}
+
+		@Override
+		public String getRemoteHost(){
+			return null;
+		}
+
+		@Override
+		public String getRemoteAddr(){
+			return null;
+		}
+
+		@Override
+		public String getRealPath(String arg0){
+			return null;
+		}
+
+		@Override
+		public BufferedReader getReader() throws IOException{
+			return null;
+		}
+
+		@Override
+		public String getProtocol(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<Locale> getLocales(){
+			return null;
+		}
+
+		@Override
+		public Locale getLocale(){
+			return null;
+		}
+
+		@Override
+		public int getLocalPort(){
+			return 0;
+		}
+
+		@Override
+		public String getLocalName(){
+			return null;
+		}
+
+		@Override
+		public String getLocalAddr(){
+			return null;
+		}
+
+		@Override
+		public ServletInputStream getInputStream() throws IOException{
+			return null;
+		}
+
+		@Override
+		public DispatcherType getDispatcherType(){
+			return null;
+		}
+
+		@Override
+		public String getContentType(){
+			return null;
+		}
+
+		@Override
+		public int getContentLength(){
+			return 0;
+		}
+
+		@Override
+		public String getCharacterEncoding(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getAttributeNames(){
+			return null;
+		}
+
+		@Override
+		public Object getAttribute(String arg0){
+			return null;
+		}
+
+		@Override
+		public AsyncContext getAsyncContext(){
+			return null;
+		}
+
+		@Override
+		public void logout() throws ServletException{}
+
+		@Override
+		public void login(String arg0, String arg1) throws ServletException{}
+
+		@Override
+		public boolean isUserInRole(String arg0){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdValid(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromUrl(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromURL(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromCookie(){
+			return false;
+		}
+
+		@Override
+		public Principal getUserPrincipal(){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(boolean arg0){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(){
+			return null;
+		}
+
+		@Override
+		public String getServletPath(){
+			return null;
+		}
+
+		@Override
+		public String getRequestedSessionId(){
+			return null;
+		}
+
+		@Override
+		public StringBuffer getRequestURL(){
+			return null;
+		}
+
+		@Override
+		public String getRequestURI(){
+			return null;
+		}
+
+		@Override
+		public String getRemoteUser(){
+			return null;
+		}
+
+		@Override
+		public String getQueryString(){
+			return null;
+		}
+
+		@Override
+		public String getPathTranslated(){
+			return null;
+		}
+
+		@Override
+		public String getPathInfo(){
+			return null;
+		}
+
+		@Override
+		public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public Part getPart(String arg0) throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public String getMethod(){
+			return "GET";
+		}
+
+		@Override
+		public int getIntHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Enumeration<String> getHeaders(String arg0){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getHeaderNames(){
+			return null;
+		}
+
+		@Override
+		public String getHeader(String arg0){
+			return null;
+		}
+
+		@Override
+		public long getDateHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Cookie[] getCookies(){
+			return null;
+		}
+
+		@Override
+		public String getContextPath(){
+			return null;
+		}
+
+		@Override
+		public String getAuthType(){
+			return null;
+		}
+
+		@Override
+		public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException{
+			return false;
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/service/wait/TestLimitedBlockingPolicy.java b/test/uws/service/wait/TestLimitedBlockingPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..a44a6a80c0f0b54eeb97e2de92175315568d42cf
--- /dev/null
+++ b/test/uws/service/wait/TestLimitedBlockingPolicy.java
@@ -0,0 +1,67 @@
+package uws.service.wait;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+
+public class TestLimitedBlockingPolicy {
+
+	@Before
+	public void setUp() throws Exception{}
+
+	@Test
+	public void testLimitedBlockingPolicy(){
+		LimitedBlockingPolicy policy = new LimitedBlockingPolicy();
+		assertEquals(LimitedBlockingPolicy.DEFAULT_TIMEOUT, policy.timeout);
+	}
+
+	@Test
+	public void testLimitedBlockingPolicyLong(){
+		// Negative time => DEFAULT_TIMEOUT
+		LimitedBlockingPolicy policy = new LimitedBlockingPolicy(-1);
+		assertEquals(LimitedBlockingPolicy.DEFAULT_TIMEOUT, policy.timeout);
+
+		// Null time => 0 (meaning no blocking)
+		policy = new LimitedBlockingPolicy(0);
+		assertEquals(0, policy.timeout);
+
+		// A time LESS THAN the default one => the given time
+		policy = new LimitedBlockingPolicy(10);
+		assertEquals(10, policy.timeout);
+
+		// A time GREATER THAN the default one => the given time
+		policy = new LimitedBlockingPolicy(100);
+		assertEquals(100, policy.timeout);
+	}
+
+	@Test
+	public void testBlock(){
+		LimitedBlockingPolicy policy = new LimitedBlockingPolicy();
+		Thread thread = new Thread("1");
+		UWSJob testJob = new UWSJob(new UWSParameters());
+
+		// Nothing should happen if no job and/or thread:
+		assertEquals(0, policy.block(null, 10, null, null, null));
+		assertEquals(0, policy.block(thread, 10, null, null, null));
+		assertEquals(0, policy.block(null, 10, testJob, null, null));
+
+		// If no time is specified by the user (i.e. 0), 0 is set (meaning no blocking):
+		assertEquals(0, policy.block(thread, 0, testJob, null, null));
+
+		// If a negative time is specified by the user (meaning an unlimited waiting time), the default time is set:
+		assertEquals(policy.timeout, policy.block(thread, -1, testJob, null, null));
+
+		// If a positive time is specified by the user BUT LESS THAN the time set in the policy, the user time is set:
+		long userTime = policy.timeout - 10;
+		assertEquals(userTime, policy.block(thread, userTime, testJob, null, null));
+
+		// If a positive time is specified by the user BUT LESS THAN the time set in the policy, the user time is set:
+		userTime = policy.timeout + 10;
+		assertEquals(policy.timeout, policy.block(thread, userTime, testJob, null, null));
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/service/wait/TestUserLimitedBlockingPolicy.java b/test/uws/service/wait/TestUserLimitedBlockingPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaafb3b0a8eb8c0bc9b4c5452c098fbf0f55358e
--- /dev/null
+++ b/test/uws/service/wait/TestUserLimitedBlockingPolicy.java
@@ -0,0 +1,706 @@
+package uws.service.wait;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uws.job.JobList;
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+import uws.job.user.JobOwner;
+
+public class TestUserLimitedBlockingPolicy {
+
+	@Before
+	public void setUp() throws Exception{}
+
+	@Test
+	public void testUserLimitedBlockingPolicyLongInt(){
+		/* ****************************** */
+		/* NB MAX BLOCKED THREADS BY USER */
+
+		// Negative number of threads:
+		UserLimitedBlockingPolicy policy = new UserLimitedBlockingPolicy(10000, -1);
+		assertEquals(UserLimitedBlockingPolicy.DEFAULT_NB_MAX_BLOCKED, policy.maxBlockedThreadsByUser);
+
+		// Null number of threads:
+		policy = new UserLimitedBlockingPolicy(10000, 0);
+		assertEquals(UserLimitedBlockingPolicy.DEFAULT_NB_MAX_BLOCKED, policy.maxBlockedThreadsByUser);
+
+		// A positive number of threads LESS THAN DEFAULT_NB_MAX_BLOCKED:
+		policy = new UserLimitedBlockingPolicy(10000, 1);
+		assertEquals(1, policy.maxBlockedThreadsByUser);
+
+		// A positive number of threads GREATER THAN DEFAULT_NB_MAX_BLOCKED:
+		policy = new UserLimitedBlockingPolicy(10000, 10);
+		assertEquals(10, policy.maxBlockedThreadsByUser);
+	}
+
+	@Test
+	public void testBuildKey(){
+		UserLimitedBlockingPolicy policy = new UserLimitedBlockingPolicy();
+		UWSJob testJob = new UWSJob("123456", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null);
+
+		// With no job => ERROR!
+		try{
+			policy.buildKey(null, null, null);
+			fail("Impossible to generate a key without a job!");
+		}catch(NullPointerException npe){}
+
+		// With only a job => jobId + ";???"
+		assertEquals("123456;???", policy.buildKey(testJob, null, null));
+
+		// With a job and a user whose the ID is null => jobId + ";???"
+		assertEquals("123456;???", policy.buildKey(testJob, new TestUser(null), null));
+
+		// With a job and a user whose the ID is NOT null => jobId + ";" + UserID
+		assertEquals("123456;myID", policy.buildKey(testJob, new TestUser("myID"), null));
+
+		// With a job and a request => jobId + ";" + IPAddress
+		assertEquals("123456;1.2.3.4", policy.buildKey(testJob, null, new TestHttpServletRequest()));
+
+		// With a job, a request and a user whose the ID is null => jobId + ";" + IPAddress
+		assertEquals("123456;1.2.3.4", policy.buildKey(testJob, new TestUser(null), new TestHttpServletRequest()));
+	}
+
+	@Test
+	public void testBlock(){
+		/* ************************************* */
+		/* No Problem If No Job And/Or No Thread */
+
+		try{
+			UserLimitedBlockingPolicy policy = new UserLimitedBlockingPolicy();
+			assertEquals(0, policy.block(null, 10, null, null, null));
+			assertEquals(0, policy.block(new Thread("0"), 10, null, null, null));
+			assertEquals(0, policy.block(null, 10, new UWSJob(new UWSParameters()), null, null));
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("Nothing should happen if no job and/or thread is asked to be blocked!");
+		}
+
+		JobOwner user = new TestUser("myId");
+		UWSJob testJob1 = new UWSJob("123456", (new Date()).getTime(), user, new UWSParameters(), -1, -1, -1, null, null);
+		UWSJob testJob2 = new UWSJob("654321", (new Date()).getTime(), user, new UWSParameters(), -1, -1, -1, null, null);
+		final String key1 = "123456;myId", key2 = "654321;myId";
+
+		/* *************************** */
+		/* NO OLD BLOCKING REPLACEMENT */
+
+		// Test with just one job and only one allowed access => OK!
+		UserLimitedBlockingPolicy policy = new UserLimitedBlockingPolicy(10, 1, false);
+		assertEquals(5, policy.block(new Thread("1"), 5, testJob1, user, null));
+		assertEquals(key1, policy.buildKey(testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("1", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// Test with the same job and same user => Rejected!
+		assertEquals(0, policy.block(new Thread("2"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("1", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// Test with a second job => OK!
+		assertEquals(5, policy.block(new Thread("3"), 5, testJob2, user, null));
+		assertEquals(key2, policy.buildKey(testJob2, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key2));
+		assertEquals(1, policy.blockedThreads.get(key2).size());
+		assertEquals("3", policy.blockedThreads.get(key2).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key2).remainingCapacity());
+
+		/* ************************ */
+		/* OLD BLOCKING REPLACEMENT */
+
+		// 1st test with just one job and only one allowed access => OK!
+		policy = new UserLimitedBlockingPolicy(10, 1, true);
+		assertEquals(5, policy.block(new Thread("1"), 5, testJob1, user, null));
+		assertEquals(key1, policy.buildKey(testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("1", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// 2nd test with the same job and same user => OK!
+		assertEquals(5, policy.block(new Thread("2"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("2", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// Test with a second job => OK!
+		assertEquals(5, policy.block(new Thread("3"), 5, testJob2, user, null));
+		assertEquals(key2, policy.buildKey(testJob2, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key2));
+		assertEquals(1, policy.blockedThreads.get(key2).size());
+		assertEquals("3", policy.blockedThreads.get(key2).peek().getName());
+		assertEquals(0, policy.blockedThreads.get(key2).remainingCapacity());
+
+		/* ************************************************************************* */
+		/* MORE THAN 1 OF CAPACITY (i.e. nb access for a given job and a given user) */
+
+		/* WITH NO old blocking replacement */
+
+		// 1st test with just one job and 2 allowed accesses => OK!
+		policy = new UserLimitedBlockingPolicy(10, 2, false);
+		assertEquals(5, policy.block(new Thread("1"), 5, testJob1, user, null));
+		assertEquals(key1, policy.buildKey(testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("1", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(1, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// 2nd test with the same job and same user => OK!
+		assertEquals(5, policy.block(new Thread("2"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(2, policy.blockedThreads.get(key1).size());
+		Iterator<Thread> it = policy.blockedThreads.get(key1).iterator();
+		assertEquals("1", it.next().getName());
+		assertEquals("2", it.next().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// 3rd test with the same job and same user => Rejected!
+		assertEquals(0, policy.block(new Thread("3"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(2, policy.blockedThreads.get(key1).size());
+		it = policy.blockedThreads.get(key1).iterator();
+		assertEquals("1", it.next().getName());
+		assertEquals("2", it.next().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		/* WITH old blocking replacement */
+
+		// 1st test with just one job and 2 allowed accesses => OK!
+		policy = new UserLimitedBlockingPolicy(10, 2, true);
+		assertEquals(5, policy.block(new Thread("1"), 5, testJob1, user, null));
+		assertEquals(key1, policy.buildKey(testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(1, policy.blockedThreads.get(key1).size());
+		assertEquals("1", policy.blockedThreads.get(key1).peek().getName());
+		assertEquals(1, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// 2nd test with the same job and same user => OK!
+		assertEquals(5, policy.block(new Thread("2"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(2, policy.blockedThreads.get(key1).size());
+		it = policy.blockedThreads.get(key1).iterator();
+		assertEquals("1", it.next().getName());
+		assertEquals("2", it.next().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+
+		// 3rd test with the same job and same user => OK!
+		assertEquals(5, policy.block(new Thread("3"), 5, testJob1, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key1));
+		assertEquals(2, policy.blockedThreads.get(key1).size());
+		it = policy.blockedThreads.get(key1).iterator();
+		assertEquals("2", it.next().getName());
+		assertEquals("3", it.next().getName());
+		assertEquals(0, policy.blockedThreads.get(key1).remainingCapacity());
+	}
+
+	@Test
+	public void testUnblocked(){
+		JobOwner user = new TestUser("myId");
+		Thread thread1 = new Thread("1"), thread2 = new Thread("2");
+		UWSJob testJob = new UWSJob("123456", (new Date()).getTime(), user, new UWSParameters(), -1, -1, -1, null, null);
+		final String key = "123456;myId";
+
+		// Block 2 jobs:
+		UserLimitedBlockingPolicy policy = new UserLimitedBlockingPolicy(10, 2, false);
+		assertEquals(5, policy.block(thread1, 5, testJob, user, null));
+		assertEquals(5, policy.block(thread2, 5, testJob, user, null));
+		assertEquals(key, policy.buildKey(testJob, user, null));
+		assertTrue(policy.blockedThreads.containsKey(key));
+		assertEquals(2, policy.blockedThreads.get(key).size());
+		Iterator<Thread> it = policy.blockedThreads.get(key).iterator();
+		assertEquals("1", it.next().getName());
+		assertEquals("2", it.next().getName());
+		assertEquals(0, policy.blockedThreads.get(key).remainingCapacity());
+
+		// Unblock one:
+		policy.unblocked(thread1, testJob, user, null);
+		assertTrue(policy.blockedThreads.containsKey(key));
+		assertEquals(1, policy.blockedThreads.get(key).size());
+		assertEquals("2", policy.blockedThreads.get(key).peek().getName());
+		assertEquals(1, policy.blockedThreads.get(key).remainingCapacity());
+
+		// Unblock the second one:
+		policy.unblocked(thread2, testJob, user, null);
+		assertFalse(policy.blockedThreads.containsKey(key));
+
+		// Try unblocking a not-blocked thread:
+		try{
+			policy.unblocked(new Thread("3"), testJob, user, null);
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("Nothing should happen if the given thread is not blocked!");
+		}
+
+		// Nothing should happen if no job and/or thread:
+		try{
+			policy.unblocked(null, null, null, null);
+			policy.unblocked(new Thread("0"), null, null, null);
+			policy.unblocked(null, new UWSJob(new UWSParameters()), null, null);
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("Nothing should happen if no job and/or thread is asked to be unblocked!");
+		}
+	}
+
+	protected final static class TestUser implements JobOwner {
+
+		private final String id;
+
+		public TestUser(final String ID){
+			id = ID;
+		}
+
+		@Override
+		public String getID(){
+			return id;
+		}
+
+		@Override
+		public String getPseudo(){
+			return null;
+		}
+
+		@Override
+		public boolean hasReadPermission(JobList jl){
+			return false;
+		}
+
+		@Override
+		public boolean hasWritePermission(JobList jl){
+			return false;
+		}
+
+		@Override
+		public boolean hasReadPermission(UWSJob job){
+			return false;
+		}
+
+		@Override
+		public boolean hasWritePermission(UWSJob job){
+			return false;
+		}
+
+		@Override
+		public boolean hasExecutePermission(UWSJob job){
+			return false;
+		}
+
+		@Override
+		public Map<String,Object> getDataToSave(){
+			return null;
+		}
+
+		@Override
+		public void restoreData(Map<String,Object> data){}
+
+	}
+
+	protected final static class TestHttpServletRequest implements HttpServletRequest {
+
+		private HashMap<String,String[]> parameters = new HashMap<String,String[]>();
+
+		private static class NamesEnumeration implements Enumeration<String> {
+
+			private final Iterator<String> it;
+
+			public NamesEnumeration(final Set<String> names){
+				this.it = names.iterator();
+			}
+
+			@Override
+			public boolean hasMoreElements(){
+				return it.hasNext();
+			}
+
+			@Override
+			public String nextElement(){
+				return it.next();
+			}
+
+		}
+
+		@Override
+		public String getRemoteAddr(){
+			return "1.2.3.4";
+		}
+
+		public void addParams(final String name, final String value){
+			if (parameters.containsKey(name)){
+				String[] values = parameters.get(name);
+				values = Arrays.copyOf(values, values.length + 1);
+				values[values.length - 1] = value;
+				parameters.put(name, values);
+			}else
+				parameters.put(name, new String[]{value});
+		}
+
+		public void clearParams(){
+			parameters.clear();
+		}
+
+		@Override
+		public Enumeration<String> getParameterNames(){
+			return new NamesEnumeration(parameters.keySet());
+		}
+
+		@Override
+		public String[] getParameterValues(String name){
+			return parameters.get(name);
+		}
+
+		@Override
+		public Map<String,String[]> getParameterMap(){
+			return parameters;
+		}
+
+		@Override
+		public String getParameter(String name){
+			String[] values = parameters.get(name);
+			if (values == null || values.length == 0)
+				return null;
+			else
+				return values[0];
+		}
+
+		@Override
+		public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1){
+			return null;
+		}
+
+		@Override
+		public AsyncContext startAsync(){
+			return null;
+		}
+
+		@Override
+		public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException{
+
+		}
+
+		@Override
+		public void setAttribute(String arg0, Object arg1){
+
+		}
+
+		@Override
+		public void removeAttribute(String arg0){
+
+		}
+
+		@Override
+		public boolean isSecure(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncSupported(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncStarted(){
+			return false;
+		}
+
+		@Override
+		public ServletContext getServletContext(){
+			return null;
+		}
+
+		@Override
+		public int getServerPort(){
+			return 0;
+		}
+
+		@Override
+		public String getServerName(){
+			return null;
+		}
+
+		@Override
+		public String getScheme(){
+			return null;
+		}
+
+		@Override
+		public RequestDispatcher getRequestDispatcher(String arg0){
+			return null;
+		}
+
+		@Override
+		public int getRemotePort(){
+			return 0;
+		}
+
+		@Override
+		public String getRemoteHost(){
+			return null;
+		}
+
+		@Override
+		public String getRealPath(String arg0){
+			return null;
+		}
+
+		@Override
+		public BufferedReader getReader() throws IOException{
+			return null;
+		}
+
+		@Override
+		public String getProtocol(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<Locale> getLocales(){
+			return null;
+		}
+
+		@Override
+		public Locale getLocale(){
+			return null;
+		}
+
+		@Override
+		public int getLocalPort(){
+			return 0;
+		}
+
+		@Override
+		public String getLocalName(){
+			return null;
+		}
+
+		@Override
+		public String getLocalAddr(){
+			return null;
+		}
+
+		@Override
+		public ServletInputStream getInputStream() throws IOException{
+			return null;
+		}
+
+		@Override
+		public DispatcherType getDispatcherType(){
+			return null;
+		}
+
+		@Override
+		public String getContentType(){
+			return null;
+		}
+
+		@Override
+		public int getContentLength(){
+			return 0;
+		}
+
+		@Override
+		public String getCharacterEncoding(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getAttributeNames(){
+			return null;
+		}
+
+		@Override
+		public Object getAttribute(String arg0){
+			return null;
+		}
+
+		@Override
+		public AsyncContext getAsyncContext(){
+			return null;
+		}
+
+		@Override
+		public void logout() throws ServletException{}
+
+		@Override
+		public void login(String arg0, String arg1) throws ServletException{}
+
+		@Override
+		public boolean isUserInRole(String arg0){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdValid(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromUrl(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromURL(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromCookie(){
+			return false;
+		}
+
+		@Override
+		public Principal getUserPrincipal(){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(boolean arg0){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(){
+			return null;
+		}
+
+		@Override
+		public String getServletPath(){
+			return null;
+		}
+
+		@Override
+		public String getRequestedSessionId(){
+			return null;
+		}
+
+		@Override
+		public StringBuffer getRequestURL(){
+			return null;
+		}
+
+		@Override
+		public String getRequestURI(){
+			return null;
+		}
+
+		@Override
+		public String getRemoteUser(){
+			return null;
+		}
+
+		@Override
+		public String getQueryString(){
+			return null;
+		}
+
+		@Override
+		public String getPathTranslated(){
+			return null;
+		}
+
+		@Override
+		public String getPathInfo(){
+			return null;
+		}
+
+		@Override
+		public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public Part getPart(String arg0) throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public String getMethod(){
+			return "GET";
+		}
+
+		@Override
+		public int getIntHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Enumeration<String> getHeaders(String arg0){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getHeaderNames(){
+			return null;
+		}
+
+		@Override
+		public String getHeader(String arg0){
+			return null;
+		}
+
+		@Override
+		public long getDateHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Cookie[] getCookies(){
+			return null;
+		}
+
+		@Override
+		public String getContextPath(){
+			return null;
+		}
+
+		@Override
+		public String getAuthType(){
+			return null;
+		}
+
+		@Override
+		public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException{
+			return false;
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/service/wait/TestWaitObserver.java b/test/uws/service/wait/TestWaitObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1529fc606d8e198475a5f119fe6dd55b7840f4b
--- /dev/null
+++ b/test/uws/service/wait/TestWaitObserver.java
@@ -0,0 +1,77 @@
+package uws.service.wait;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+
+public class TestWaitObserver {
+
+	@Before
+	public void setUp() throws Exception{}
+
+	protected final void waitALittle(){
+		synchronized(this){
+			try{
+				Thread.sleep(10);
+			}catch(InterruptedException e){
+				e.printStackTrace();
+			}
+		}
+	}
+
+	@Test
+	public void testUpdate(){
+		Thread thread = new TestThread();
+		WaitObserver obs = new WaitObserver(thread);
+
+		thread.start();
+
+		try{
+			// No phase => Thread still blocked!
+			obs.update(null, null, null);
+			waitALittle();
+			assertTrue(thread.isAlive());
+			obs.update(null, ExecutionPhase.PENDING, null);
+			waitALittle();
+			assertTrue(thread.isAlive());
+			obs.update(null, null, ExecutionPhase.PENDING);
+			waitALittle();
+			assertTrue(thread.isAlive());
+
+			// Same phase => Thread still blocked!
+			obs.update(null, ExecutionPhase.PENDING, ExecutionPhase.PENDING);
+			waitALittle();
+			assertTrue(thread.isAlive());
+
+			// Different phase => Thread UNblocked!
+			obs.update(null, ExecutionPhase.PENDING, ExecutionPhase.EXECUTING);
+			waitALittle();
+			assertFalse(thread.isAlive());
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("No error should have happened while calling WaitObserver.update()!");
+		}
+
+	}
+
+	protected final static class TestThread extends Thread {
+		@Override
+		public void run(){
+			synchronized(this){
+				try{
+					wait();
+				}catch(InterruptedException e){
+					e.printStackTrace();
+				}
+			}
+		}
+
+	}
+
+}
\ No newline at end of file