diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 82c105a5aa1986e5ce91542f95530fe5f5e64998..7acfb281c2ab71dc842a9054746cc1d760bf4935 100644
--- a/src/tap/ADQLExecutor.java
+++ b/src/tap/ADQLExecutor.java
@@ -22,7 +22,6 @@ package tap;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.sql.SQLException;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -40,12 +39,12 @@ import tap.upload.Uploader;
 import uws.UWSException;
 import uws.job.JobThread;
 import uws.job.Result;
+import uws.service.log.UWSLog.LogLevel;
 import adql.parser.ADQLParser;
 import adql.parser.ADQLQueryFactory;
 import adql.parser.ParseException;
 import adql.parser.QueryChecker;
 import adql.query.ADQLQuery;
-import adql.translator.TranslationException;
 
 /**
  * <p>Let process completely an ADQL query.</p>
@@ -101,11 +100,11 @@ import adql.translator.TranslationException;
  * <p>
  * 	There is no way in this executor to customize the upload. However, it does not mean it can not be customized.
  * 	Indeed you can do it easily by extending {@link Uploader} and by providing the new class inside your {@link TAPFactory} implementation
- * 	(see {@link TAPFactory#createUploader(DBConnection)}).
+ * (see {@link TAPFactory#createUploader(DBConnection)}).
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  */
 public class ADQLExecutor {
 
@@ -206,8 +205,8 @@ public class ADQLExecutor {
 	 * 
 	 * @return	The resulting execution report.
 	 * 
-	 * @throws UWSException
-	 * @throws InterruptedException
+	 * @throws UWSException			If any error occurs while executing the ADQL query.
+	 * @throws InterruptedException	If the job has been interrupted (by the user or a time-out).
 	 * 
 	 * @see #start()
 	 */
@@ -222,7 +221,11 @@ public class ADQLExecutor {
 		this.report = new TAPExecutionReport(tapJob.getJobId(), false, tapParams);
 		this.response = null;
 
-		return start();
+		try{
+			return start();
+		}catch(TAPException te){
+			throw new UWSException(te.getHttpErrorCode(), te);
+		}
 	}
 
 	/**
@@ -237,8 +240,8 @@ public class ADQLExecutor {
 	 * 
 	 * @return	The resulting execution report.
 	 * 
-	 * @throws TAPException
-	 * @throws InterruptedException
+	 * @throws TAPException			If any error occurs while executing the ADQL query.
+	 * @throws InterruptedException	If the job has been interrupted (by the user or a time-out).
 	 * 
 	 * @see #start()
 	 */
@@ -251,7 +254,11 @@ public class ADQLExecutor {
 		this.report = new TAPExecutionReport(jobId, true, tapParams);
 		this.response = response;
 
-		return start();
+		try{
+			return start();
+		}catch(UWSException ue){
+			throw new TAPException(ue, ue.getHttpErrorCode());
+		}
 	}
 
 	/**
@@ -277,14 +284,11 @@ public class ADQLExecutor {
 	 * 
 	 * @return	The updated execution report.
 	 * 
-	 * @throws TAPException
-	 * @throws UWSException
-	 * @throws InterruptedException
-	 * @throws ParseException
-	 * @throws TranslationException
-	 * @throws SQLException
+	 * @throws TAPException			If any error occurs while executing the ADQL query.
+	 * @throws UWSException			If any error occurs while executing the ADQL query.
+	 * @throws InterruptedException	If the job has been interrupted (by the user or a time-out).
 	 */
-	protected final TAPExecutionReport start() throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{
+	protected final TAPExecutionReport start() throws TAPException, UWSException, InterruptedException{
 		// Save the start time (for reporting usage):
 		long start = System.currentTimeMillis();
 
@@ -307,7 +311,15 @@ public class ADQLExecutor {
 			// 2. PARSE THE ADQL QUERY:
 			startStep(ExecutionProgression.PARSING);
 			// Parse the query:
-			ADQLQuery adqlQuery = parseADQL();
+			ADQLQuery adqlQuery = null;
+			try{
+				adqlQuery = parseADQL();
+			}catch(ParseException pe){
+				if (report.synchronous)
+					throw new TAPException("Incorrect ADQL query: " + pe.getMessage(), pe, UWSException.BAD_REQUEST, tapParams.getQuery(), progression);
+				else
+					throw new UWSException(UWSException.BAD_REQUEST, pe, "Incorrect ADQL query: " + pe.getMessage());
+			}
 			// List all resulting columns (it will be useful later to format the result):
 			report.resultingColumns = adqlQuery.getResultingColumns();
 			endStep();
@@ -329,7 +341,6 @@ public class ADQLExecutor {
 			endStep();
 
 			// Report the COMPLETED status:
-			logger.info("JOB " + report.jobID + " COMPLETED");
 			tapParams.remove(TAPJob.PARAM_PROGRESSION);
 			report.success = true;
 
@@ -340,7 +351,7 @@ public class ADQLExecutor {
 				try{
 					queryResult.close();
 				}catch(DataReadException dre){
-					logger.error("JOB " + report.jobID + "\tCan not close the database query result!", dre);
+					logger.logTAP(LogLevel.ERROR, report, "END_EXEC", "Can not close the database query result!", dre);
 				}
 			}
 
@@ -348,7 +359,7 @@ public class ADQLExecutor {
 			try{
 				dropUploadedTables();
 			}catch(TAPException e){
-				logger.error("JOB " + report.jobID + "\tCan not drop the uploaded tables from the database!", e);
+				logger.logTAP(LogLevel.ERROR, report, "END_EXEC", "Can not drop the uploaded tables from the database!", e);
 			}
 
 			// Free the connection (so that giving it back to a pool, if any, otherwise, just free resources):
@@ -359,7 +370,6 @@ public class ADQLExecutor {
 
 			// Set the total duration in the report:
 			report.setTotalDuration(System.currentTimeMillis() - start);
-			logger.queryFinished(report);
 		}
 	}
 
@@ -389,7 +399,8 @@ public class ADQLExecutor {
 		try{
 			tapParams.set(TAPJob.PARAM_PROGRESSION, this.progression);
 		}catch(UWSException ue){
-			logger.warning("Can not set/update the informative job parameter \"" + TAPJob.PARAM_PROGRESSION + "\" (this parameter would be just for notification purpose about the execution progression)! CAUSE: " + ue.getClass().getName() + " - " + ue.getMessage());
+			// should not happen, but just in case...
+			logger.logTAP(LogLevel.WARNING, report, "START_STEP", "Can not set/update the informative job parameter \"" + TAPJob.PARAM_PROGRESSION + "\" (this parameter would be just for notification purpose about the execution progression)!", ue);
 		}
 	}
 
@@ -434,7 +445,7 @@ public class ADQLExecutor {
 
 		// Upload them, if needed:
 		if (tables.length > 0){
-			logger.info("JOB " + report.jobID + "\tLoading uploaded tables (" + tables.length + ")...");
+			logger.logTAP(LogLevel.INFO, report, "UPLOAD", "Loading uploaded tables (" + tables.length + ")...", null);
 			try{
 				uploadSchema = service.getFactory().createUploader(dbConn).upload(tables);
 			}finally{
@@ -509,18 +520,17 @@ public class ADQLExecutor {
 	 * @return	The result of the query,
 	 *        	or NULL if the query execution has failed.
 	 * 
-	 * @throws SQLException			If the query execution has failed ; the database is not able to execute this query.
 	 * @throws InterruptedException	If the thread has been interrupted.
 	 * @throws TAPException			If the {@link DBConnection} has failed to deal with the given ADQL query.
 	 * 
 	 * @see {@link DBConnection#executeQuery(ADQLQuery)}
 	 */
-	protected TableIterator executeADQL(final ADQLQuery adql) throws SQLException, InterruptedException, TAPException{
+	protected TableIterator executeADQL(final ADQLQuery adql) throws InterruptedException, TAPException{
 		TableIterator result = dbConn.executeQuery(adql);
 		if (result == null)
-			logger.info("JOB " + report.jobID + " - QUERY ABORTED AFTER " + (System.currentTimeMillis() - startStep) + " MS !");
+			logger.logTAP(LogLevel.INFO, report, "END_QUERY", "Query execution aborted after " + (System.currentTimeMillis() - startStep) + "ms!", null);
 		else
-			logger.info("JOB " + report.jobID + " - QUERY SUCCESFULLY EXECUTED IN " + (System.currentTimeMillis() - startStep) + " MS !");
+			logger.logTAP(LogLevel.INFO, report, "END_QUERY", "Query successfully executed in " + (System.currentTimeMillis() - startStep) + "ms!", null);
 		return result;
 	}
 
@@ -555,7 +565,7 @@ public class ADQLExecutor {
 				writeResult(queryResult, formatter, response.getOutputStream());
 
 			}catch(IOException ioe){
-				throw new TAPException("Impossible to get the output stream of the HTTP request to write the result of the job " + report.jobID + " !", ioe);
+				throw new TAPException("Impossible to get the HTTP response, so the result of the job " + report.jobID + " can not be written!", ioe, UWSException.INTERNAL_SERVER_ERROR);
 			}
 		}
 		// CASE ASYNCHRONOUS:
@@ -579,7 +589,7 @@ public class ADQLExecutor {
 				jobThread.publishResult(result);
 
 			}catch(IOException ioe){
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to get the output stream of the result file to write the result of the job " + report.jobID + " !");
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to access the file into the result of the job " + report.jobID + " must be written!");
 			}
 		}
 	}
@@ -621,7 +631,7 @@ public class ADQLExecutor {
 				try{
 					dbConn.dropUploadedTable(t);
 				}catch(DBException dbe){
-					logger.error("JOB " + report.jobID + "\tCan not drop the uploaded table \"" + t.getDBName() + "\" (in adql \"" + t.getADQLName() + "\") from the database!", dbe);
+					logger.logTAP(LogLevel.ERROR, report, "DROP_UPLOAD", "Can not drop the uploaded table \"" + t.getDBName() + "\" (in adql \"" + t.getADQLName() + "\") from the database!", dbe);
 				}
 			}
 		}
diff --git a/src/tap/AbstractTAPFactory.java b/src/tap/AbstractTAPFactory.java
index 8825fc96afc2e4cb14c63712463cdcc3b3869204..1241c92e58d07db8aa3fb51c8b0474ea839554bc 100644
--- a/src/tap/AbstractTAPFactory.java
+++ b/src/tap/AbstractTAPFactory.java
@@ -260,11 +260,11 @@ public abstract class AbstractTAPFactory extends TAPFactory {
 	 * @see tap.TAPFactory#createTAPParameters(javax.servlet.http.HttpServletRequest)
 	 */
 	@Override
-	protected TAPParameters createTAPParameters(final HttpServletRequest request) throws UWSException{
+	public TAPParameters createTAPParameters(final HttpServletRequest request) throws TAPException{
 		try{
 			return new TAPParameters(request, service, getExpectedAdditionalParameters(), getInputParamControllers());
-		}catch(TAPException te){
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, te);
+		}catch(UWSException ue){
+			throw new TAPException(ue);
 		}
 	}
 
@@ -281,11 +281,11 @@ public abstract class AbstractTAPFactory extends TAPFactory {
 	 * @see tap.TAPFactory#createTAPParameters(javax.servlet.http.HttpServletRequest)
 	 */
 	@Override
-	protected TAPParameters createTAPParameters(final Map<String,Object> params) throws UWSException{
+	public TAPParameters createTAPParameters(final Map<String,Object> params) throws TAPException{
 		try{
 			return new TAPParameters(service, params, getExpectedAdditionalParameters(), getInputParamControllers());
-		}catch(TAPException te){
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, te);
+		}catch(UWSException ue){
+			throw new TAPException(ue);
 		}
 	}
 
diff --git a/src/tap/AsyncThread.java b/src/tap/AsyncThread.java
index 65c2c37a565eeedce210ad7d97cbcb8bc9eda454..00f1f2d0151141970000567f5941452ea631283c 100644
--- a/src/tap/AsyncThread.java
+++ b/src/tap/AsyncThread.java
@@ -21,8 +21,6 @@ package tap;
 
 import uws.UWSException;
 import uws.job.JobThread;
-import adql.parser.ParseException;
-import adql.translator.TranslationException;
 
 public class AsyncThread extends JobThread {
 
@@ -41,12 +39,6 @@ public class AsyncThread extends JobThread {
 			throw ie;
 		}catch(UWSException ue){
 			throw ue;
-		}catch(TAPException te){
-			throw new UWSException(te.getHttpErrorCode(), te, te.getMessage());
-		}catch(ParseException pe){
-			throw new UWSException(UWSException.BAD_REQUEST, pe, pe.getMessage());
-		}catch(TranslationException te){
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, te, te.getMessage());
 		}catch(Exception ex){
 			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ex, "Error while processing the ADQL query of the job " + job.getJobId() + " !");
 		}finally{
diff --git a/src/tap/TAPException.java b/src/tap/TAPException.java
index 89a4feeef6723558f5d32f9203862329310ec361..162d8ea98f37ac9fc0d24a3823a76936cbc46a8a 100644
--- a/src/tap/TAPException.java
+++ b/src/tap/TAPException.java
@@ -21,6 +21,12 @@ package tap;
 
 import uws.UWSException;
 
+/**
+ * TODO JAVADOC OF THE WHOLE CLASS TO WRITE!!!
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class TAPException extends Exception {
 
 	private static final long serialVersionUID = 1L;
@@ -60,7 +66,7 @@ public class TAPException extends Exception {
 	}
 
 	public TAPException(UWSException ue){
-		this(ue.getMessage(), ue.getCause(), ue.getHttpErrorCode());
+		this(ue.getMessage(), (ue.getCause() == null ? ue : ue.getCause()), ue.getHttpErrorCode());
 	}
 
 	public TAPException(UWSException cause, int httpErrorCode){
diff --git a/src/tap/TAPFactory.java b/src/tap/TAPFactory.java
index aac9b3ec88fb405daba92b777c3243cc5d88fcca..22158cde3ac67d5287bda257dfae1a49a5853f50 100644
--- a/src/tap/TAPFactory.java
+++ b/src/tap/TAPFactory.java
@@ -58,7 +58,7 @@ import adql.query.ADQLQuery;
  * </ul>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  */
 public abstract class TAPFactory extends AbstractUWSFactory {
 
@@ -373,7 +373,14 @@ public abstract class TAPFactory extends AbstractUWSFactory {
 	 */
 	@Override
 	public final UWSParameters createUWSParameters(HttpServletRequest request) throws UWSException{
-		return createTAPParameters(request);
+		try{
+			return createTAPParameters(request);
+		}catch(TAPException te){
+			if (te.getCause() != null && te.getCause() instanceof UWSException && te.getMessage().equals(te.getCause().getMessage()))
+				throw (UWSException)te.getCause();
+			else
+				throw new UWSException(te.getHttpErrorCode(), te);
+		}
 	}
 
 	/**
@@ -387,9 +394,9 @@ public abstract class TAPFactory extends AbstractUWSFactory {
 	 * 
 	 * @return	An object gathering all successfully extracted TAP parameters.
 	 * 
-	 * @throws UWSException	If any error occurs while extracting the parameters.
+	 * @throws TAPException	If any error occurs while extracting the parameters. 
 	 */
-	protected abstract TAPParameters createTAPParameters(final HttpServletRequest request) throws UWSException;
+	public abstract TAPParameters createTAPParameters(final HttpServletRequest request) throws TAPException;
 
 	/**
 	 * <p>Identify and gather all identified parameters of the given map inside a {@link TAPParameters} object.</p>
@@ -404,7 +411,14 @@ public abstract class TAPFactory extends AbstractUWSFactory {
 	 */
 	@Override
 	public final UWSParameters createUWSParameters(Map<String,Object> params) throws UWSException{
-		return createTAPParameters(params);
+		try{
+			return createTAPParameters(params);
+		}catch(TAPException te){
+			if (te.getCause() != null && te.getCause() instanceof UWSException && te.getMessage().equals(te.getCause().getMessage()))
+				throw (UWSException)te.getCause();
+			else
+				throw new UWSException(te.getHttpErrorCode(), te);
+		}
 	}
 
 	/**
@@ -417,9 +431,9 @@ public abstract class TAPFactory extends AbstractUWSFactory {
 	 * @param params	Map containing all parameters.
 	 * 
 	 * @return	An object gathering all successfully identified TAP parameters.
-	 * 
-	 * @throws UWSException	If any error occurs while creating the {@link TAPParameters} object.
+	 *
+	 * @throws TAPException	If any error occurs while creating the {@link TAPParameters} object.
 	 */
-	protected abstract TAPParameters createTAPParameters(final Map<String,Object> params) throws UWSException;
+	public abstract TAPParameters createTAPParameters(final Map<String,Object> params) throws TAPException;
 
 }
diff --git a/src/tap/TAPJob.java b/src/tap/TAPJob.java
index 52510c5b6290ba737cf07049cf62892d99a3a29c..eae3fe4e297a46ee94c23669ff21b2d8758f9437 100644
--- a/src/tap/TAPJob.java
+++ b/src/tap/TAPJob.java
@@ -23,13 +23,10 @@ import java.util.List;
 
 import tap.parameters.TAPParameters;
 import tap.upload.TableLoader;
-
 import uws.UWSException;
-
 import uws.job.ErrorSummary;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
 
 public class TAPJob extends UWSJob {
@@ -62,7 +59,7 @@ public class TAPJob extends UWSJob {
 
 	protected final TAPParameters tapParams;
 
-	public TAPJob(final JobOwner owner, final TAPParameters tapParams) throws UWSException, TAPException{
+	public TAPJob(final JobOwner owner, final TAPParameters tapParams) throws TAPException{
 		super(owner, tapParams);
 		this.tapParams = tapParams;
 		tapParams.check();
@@ -70,7 +67,7 @@ public class TAPJob extends UWSJob {
 		//loadTAPParams(tapParams);
 	}
 
-	public TAPJob(final String jobID, final JobOwner owner, final TAPParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws UWSException, TAPException{
+	public TAPJob(final String jobID, final JobOwner owner, final TAPParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws TAPException{
 		super(jobID, owner, params, quote, startTime, endTime, results, error);
 		this.tapParams = params;
 		this.tapParams.check();
diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java
index 2a87027c858427046a3ec66a230f4f1da4f265f8..016b0da4f3803b52c4ee551bf3e349a060cdf07d 100644
--- a/src/tap/TAPSyncJob.java
+++ b/src/tap/TAPSyncJob.java
@@ -16,7 +16,8 @@ package tap;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Date;
@@ -26,25 +27,65 @@ import javax.servlet.http.HttpServletResponse;
 import tap.parameters.TAPParameters;
 import uws.UWSException;
 import uws.job.JobThread;
+import uws.service.log.UWSLog.LogLevel;
 
+/**
+ * <p>This class represent a TAP synchronous job.
+ * A such job must execute an ADQL query and return immediately its result.</p>
+ * 
+ * <h3>Timeout</h3>
+ * 
+ * <p>
+ * 	The execution of a such job is limited to a short time. Once this time elapsed, the job is stopped.
+ * 	For a longer job, an asynchronous job should be used.
+ * </p>
+ * 
+ * <h3>Error management</h3>
+ * 
+ * <p>
+ * 	If an error occurs it must be propagated ; it will be written later in the HTTP response on a top level.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class TAPSyncJob {
 
 	/** The time (in ms) to wait the end of the thread after an interruption. */
 	protected long waitForStop = 1000;
 
+	/** Last generated ID of a synchronous job. */
 	protected static String lastId = null;
 
+	/** Description of the TAP service in charge of this synchronous job. */
 	protected final ServiceConnection service;
 
+	/** ID of this job. This ID is also used to identify the thread. */
 	protected final String ID;
+
+	/** Parameters of the execution. It mainly contains the ADQL query to execute. */
 	protected final TAPParameters tapParams;
 
+	/** The thread in which the query execution will be done. */
 	protected SyncThread thread;
 
+	/** Report of the query execution. It stays NULL until the execution ends. */
 	protected TAPExecutionReport execReport = null;
 
+	/** Date at which this synchronous job has really started. It is NULL when the job has never been started.
+	 * 
+	 * <p><i>Note: A synchronous job can be run just once ; so if an attempt of executing it again, the start date will be tested:
+	 * if NULL, the second starting is not considered and an exception is thrown.</i></p> */
 	private Date startedAt = null;
 
+	/**
+	 * Create a synchronous TAP job.
+	 * 
+	 * @param service	Description of the TAP service which is in charge of this synchronous job.
+	 * @param params	Parameters of the query to execute. It must mainly contain the ADQL query to execute.  
+	 * 
+	 * @throws NullPointerException	If one of the parameters is NULL.
+	 */
 	public TAPSyncJob(final ServiceConnection service, final TAPParameters params) throws NullPointerException{
 		if (params == null)
 			throw new NullPointerException("Missing TAP parameters ! => Impossible to create a synchronous TAP job.");
@@ -79,92 +120,202 @@ public class TAPSyncJob {
 		return generatedId;
 	}
 
+	/**
+	 * Get the ID of this synchronous job.
+	 * 
+	 * @return	The job ID.
+	 */
 	public final String getID(){
 		return ID;
 	}
 
+	/**
+	 * Get the TAP parameters provided by the user and which will be used for the execution of this job.
+	 * 
+	 * @return	Job parameters.
+	 */
 	public final TAPParameters getTapParams(){
 		return tapParams;
 	}
 
+	/**
+	 * Get the report of the execution of this job.
+	 * This report is NULL if the execution has not yet started.
+	 * 
+	 * @return	Report of this job execution.
+	 */
 	public final TAPExecutionReport getExecReport(){
 		return execReport;
 	}
 
-	public synchronized boolean start(final HttpServletResponse response) throws IllegalStateException, UWSException, TAPException{
+	/**
+	 * <p>Start the execution of this job in order to execute the given ADQL query.</p>
+	 * 
+	 * <p>The execution itself will be processed by an {@link ADQLExecutor} inside a thread ({@link SyncThread}).</p>
+	 * 
+	 * <p><b>Important:</b>
+	 * 	No error should be written in this function. If any error occurs it should be thrown, in order to be manager on a top level.
+	 * </p>
+	 * 
+	 * @param response	Response in which the result must be written.
+	 * 
+	 * @return	<i>true</i> if the execution was successful, <i>false</i> otherwise.
+	 * 
+	 * @throws IllegalStateException	If this synchronous job has already been started before.
+	 * @throws TAPException				If any error occurs while executing the ADQL query.
+	 * 
+	 * @see SyncThread
+	 */
+	public synchronized boolean start(final HttpServletResponse response) throws IllegalStateException, TAPException{
 		if (startedAt != null)
 			throw new IllegalStateException("Impossible to restart a synchronous TAP query !");
 
+		// Log the start of this sync job:
+		service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_START", "Synchronous job " + ID + " is starting!", null);
+
+		// Create the object having the knowledge about how to execute an ADQL query:
 		ADQLExecutor executor;
 		try{
 			executor = service.getFactory().createADQLExecutor();
 		}catch(TAPException e){
-			// TODO Log this error !
-			return true;
+			service.getLogger().logTAP(LogLevel.ERROR, this, "SYNC_START", "An error has prevented the synchronous thread " + getID() + " to start!", e);
+			return false;
 		}
+
+		// Give to a thread which will execute the query:
 		thread = new SyncThread(executor, ID, tapParams, response);
 		thread.start();
-		boolean timeout = false;
 
+		// Wait the end of the thread until the maximum execution duration is reached:
+		boolean timeout = false;
 		try{
+			// wait the end:
 			thread.join(tapParams.getExecutionDuration());
+			// if still alive after this duration, interrupt it:
 			if (thread.isAlive()){
 				timeout = true;
 				thread.interrupt();
 				thread.join(waitForStop);
 			}
 		}catch(InterruptedException ie){
-			;
+			/* Having a such exception here, is not surprising, because we may have interrupt the thread! */
 		}finally{
+			// Whatever the way the execution stops (normal, cancel or error), an execution report must be fulfilled:
 			execReport = thread.getExecutionReport();
 		}
 
-		if (!thread.isSuccess()){
+		// Report any error that may have occurred while the thread execution:
+		Throwable error = thread.getError();
+		// CASE: TIMEOUT
+		if (timeout && error != null && error instanceof InterruptedException){
+			// Log the timeout:
 			if (thread.isAlive())
-				throw new TAPException("Time out (=" + tapParams.getExecutionDuration() + "ms) ! However, the thread (synchronous query) can not be stopped !", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-			else if (timeout)
-				throw new TAPException("Time out ! The execution of this synchronous TAP query was limited to " + tapParams.getExecutionDuration() + "ms.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+				service.getLogger().logTAP(LogLevel.WARNING, this, "SYNC_TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ", but the thread can not be interrupted!", null);
+			else
+				service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ".", null);
+
+			// Report the timeout to the user:
+			throw new TAPException("Time out! The execution of this synchronous TAP query was limited to " + tapParams.getExecutionDuration() + "ms. You should try again but in asynchronous execution.", UWSException.ACCEPTED_BUT_NOT_COMPLETE);
+		}
+		// CASE: ERRORS
+		else if (!thread.isSuccess()){
+			// INTERRUPTION:
+			if (error instanceof InterruptedException){
+				// log the unexpected interruption (unexpected because not caused by a timeout):
+				service.getLogger().logTAP(LogLevel.WARNING, this, "SYNC_END", "The execution of the synchronous job " + ID + " has been unexpectedly interrupted!", error);
+				// report the unexpected interruption to the user:
+				throw new TAPException("The execution of this synchronous job " + ID + " has been unexpectedly aborted!", UWSException.ACCEPTED_BUT_NOT_COMPLETE);
+			}
+			// TAP EXCEPTION:
+			else if (error instanceof TAPException){
+				// log the error:
+				service.getLogger().logTAP(LogLevel.ERROR, this, "SYNC_END", "An error occured while executing the query of the synchronous job " + ID + ": " + error.getMessage(), error);
+				// report the error to the user:
+				throw (TAPException)error;
+			}
+			// ANY OTHER EXCEPTION:
 			else{
-				Throwable t = thread.getError();
-				if (t instanceof InterruptedException)
-					throw new TAPException("The execution of this synchronous TAP query has been unexpectedly aborted !");
-				else if (t instanceof UWSException)
-					throw (UWSException)t;
-				else
-					throw new TAPException(t);
+				// log the error:
+				service.getLogger().logTAP(LogLevel.FATAL, this, "SYNC_END", "An unexpected error has stopped the execution of the synchronous job " + ID + ": " + error.getMessage(), error);
+				// report the error to the user:
+				throw new TAPException(error);
 			}
-		}
+		}else
+			service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_END", "The synchronous job " + ID + " successfully ended.", null);
 
-		return thread.isInterrupted();
+		return thread.isSuccess();
 	}
 
-	public class SyncThread extends Thread {
+	/**
+	 * <p>Thread which will process the job execution.</p>
+	 * 
+	 * <p>
+	 * 	Actually, it will basically just call {@link ADQLExecutor#start(Thread, String, TAPParameters, HttpServletResponse)}
+	 * 	with the given {@link ADQLExecutor} and TAP parameters (containing the ADQL query to execute).
+	 * </p>
+	 * 
+	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
+	 * @version 2.0 (09/2014)
+	 */
+	protected class SyncThread extends Thread {
 
-		private final String taskDescription;
-		public final ADQLExecutor executor;
+		/** Object knowing how to execute an ADQL query and which will execute it by calling {@link ADQLExecutor#start(Thread, String, TAPParameters, HttpServletResponse)}. */
+		protected final ADQLExecutor executor;
+		/** Response in which the query result must be written. No error should be written in it directly at this level ;
+		 * the error must be propagated and it will be written in this HTTP response later on a top level. */
 		protected final HttpServletResponse response;
+		/** ID of this thread. It is also the ID of the synchronous job owning this thread. */
 		protected final String ID;
+		/** Parameters containing the ADQL query to execute and other execution parameters/options. */
 		protected final TAPParameters tapParams;
+
+		/** Exception that occurs while executing this thread. NULL if the execution was a success. */
 		protected Throwable exception = null;
+		/** Query execution report. NULL if the execution has not yet started. */
 		protected TAPExecutionReport report = null;
 
+		/**
+		 * Create a thread that will run the given executor with the given parameters.
+		 * 
+		 * @param executor	Object to execute and which knows how to execute an ADQL query.
+		 * @param ID		ID of the synchronous job owning this thread.
+		 * @param tapParams	TAP parameters to use to get the query to execute and the execution parameters.
+		 * @param response	HTTP response in which the ADQL query result must be written.
+		 */
 		public SyncThread(final ADQLExecutor executor, final String ID, final TAPParameters tapParams, final HttpServletResponse response){
 			super(JobThread.tg, ID);
-			taskDescription = "Executing the synchronous TAP query " + ID;
 			this.executor = executor;
 			this.ID = ID;
 			this.tapParams = tapParams;
 			this.response = response;
 		}
 
+		/**
+		 * Tell whether the execution has ended with success.
+		 * 
+		 * @return	<i>true</i> if the query has been successfully executed,
+		 *        	<i>false</i> otherwise (or if this thread is still executed).
+		 */
 		public final boolean isSuccess(){
 			return !isAlive() && report != null && exception == null;
 		}
 
+		/**
+		 * Get the error that has interrupted/stopped this thread.
+		 * This function returns NULL if the query has been successfully executed.
+		 * 
+		 * @return	Error that occurs while executing the query
+		 *        	or NULL if the execution was a success.
+		 */
 		public final Throwable getError(){
 			return exception;
 		}
 
+		/**
+		 * Get the report of the query execution.
+		 * 
+		 * @return	Query execution report.
+		 */
 		public final TAPExecutionReport getExecutionReport(){
 			return report;
 		}
@@ -172,17 +323,27 @@ public class TAPSyncJob {
 		@Override
 		public void run(){
 			// Log the start of this thread:
-			executor.getLogger().threadStarted(this, taskDescription);
+			executor.getLogger().logThread(LogLevel.INFO, thread, "START", "Synchronous thread \"" + ID + "\" started.", null);
 
 			try{
+				// Execute the ADQL query:
 				report = executor.start(this, ID, tapParams, response);
-				executor.getLogger().threadFinished(this, taskDescription);
+
+				// Log the successful end of this thread:
+				executor.getLogger().logThread(LogLevel.INFO, thread, "END", "Synchronous thread \"" + ID + "\" successfully ended.", null);
+
 			}catch(Throwable e){
+
+				// Save the exception for later reporting:
 				exception = e;
-				if (e instanceof InterruptedException){
-					// Log the abortion:
-					executor.getLogger().threadInterrupted(this, taskDescription, e);
-				}
+
+				// Log the end of the job:
+				if (e instanceof InterruptedException)
+					// Abortion:
+					executor.getLogger().logThread(LogLevel.INFO, this, "END", "Synchronous thread \"" + getId() + "\" cancelled.", null);
+				else
+					// Error:
+					executor.getLogger().logThread(LogLevel.ERROR, this, "END", "Synchronous thread \"" + getId() + "\" ended with an error.", exception);
 			}
 		}
 
diff --git a/src/tap/backup/DefaultTAPBackupManager.java b/src/tap/backup/DefaultTAPBackupManager.java
index f84e066a0066bd0398526fb6a536daa757033c95..714c8174e0970a49d0226dcf46b754aa634839c5 100644
--- a/src/tap/backup/DefaultTAPBackupManager.java
+++ b/src/tap/backup/DefaultTAPBackupManager.java
@@ -30,6 +30,7 @@ import uws.UWSException;
 import uws.job.UWSJob;
 import uws.service.UWS;
 import uws.service.backup.DefaultUWSBackupManager;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Let backup all TAP asynchronous jobs.</p>
@@ -144,14 +145,14 @@ public class DefaultTAPBackupManager extends DefaultUWSBackupManager {
 							else if (key.equalsIgnoreCase("totalduration"))
 								execReport.setTotalDuration(jsonExecReport.getLong(key));
 							else
-								getLogger().warning("The execution report attribute '" + key + "' of the job \"" + job.getJobId() + "\" has been ignored because unknown !");
+								getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "The execution report attribute '" + key + "' of the job \"" + job.getJobId() + "\" has been ignored because unknown!", null);
 						}catch(JSONException je){
-							getLogger().error("[restoration] Incorrect JSON format for the execution report serialization of the job \"" + job.getJobId() + "\" (attribute: \"" + key + "\") !", je);
+							getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for the execution report serialization of the job \"" + job.getJobId() + "\" (attribute: \"" + key + "\")!", je);
 						}
 					}
 					tapJob.setExecReport(execReport);
 				}else if (!(obj instanceof JSONObject))
-					getLogger().warning("[restoration] Impossible to restore the execution report of the job \"" + job.getJobId() + "\" because the stored object is not a JSONObject !");
+					getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "Impossible to restore the execution report of the job \"" + job.getJobId() + "\" because the stored object is not a JSONObject!", null);
 			}
 		}
 	}
diff --git a/src/tap/db/DBConnection.java b/src/tap/db/DBConnection.java
index f83e15114806f415256763161b1d99b4eff51587..45931a6478299c5de7010f04b203d9fb22c1dc15 100644
--- a/src/tap/db/DBConnection.java
+++ b/src/tap/db/DBConnection.java
@@ -26,7 +26,6 @@ import tap.data.TableIterator;
 import tap.metadata.TAPColumn;
 import tap.metadata.TAPMetadata;
 import tap.metadata.TAPTable;
-import uws.service.log.UWSLogType;
 import adql.query.ADQLQuery;
 import adql.translator.ADQLTranslator;
 
@@ -44,14 +43,10 @@ import adql.translator.ADQLTranslator;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public interface DBConnection {
 
-	/** Log type specific to the database activity.
-	 * @see UWSLogType#createCustomLogType(String) */
-	public final static UWSLogType LOG_TYPE_DB_ACTIVITY = UWSLogType.createCustomLogType("DBActivity");
-
 	/**
 	 * <p>Get any identifier for this connection.</p>
 	 * 
@@ -185,9 +180,11 @@ public interface DBConnection {
 	/**
 	 * Add the defined and given table inside the TAP_UPLOAD schema.
 	 * 
+	 * <p>If the TAP_UPLOAD schema does not already exist, it will be created.</p>
+	 * 
 	 * <p><i>note: A table of TAP_UPLOAD MUST be transient and persistent only for the lifetime of the query.
 	 * So, this function should always be used with {@link #dropUploadedTable(String)}, which is called at
-	 * the end of each query execution.</i></p> 
+	 * the end of each query execution.</i></p>
 	 * 
 	 * @param tableDef	Definition of the table to upload (list of all columns and of their type).
 	 * @param data		Rows and columns of the table to upload.
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index 018724688a00cb0f4ad5c4164e0d83d8da42a91d..f3393e37bf0082954d5ee2205cb57bee2871cf26 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -20,7 +20,6 @@ package tap.db;
  *                       Astronomishes Rechen Institut (ARI)
  */
 
-import java.io.PrintStream;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
@@ -48,6 +47,7 @@ import tap.metadata.TAPTable;
 import tap.metadata.TAPTable.TableType;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
+import uws.service.log.UWSLog.LogLevel;
 import adql.query.ADQLQuery;
 import adql.query.IdentifierField;
 import adql.translator.ADQLTranslator;
@@ -112,7 +112,7 @@ import adql.translator.TranslationException;
  * </i></p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  * @since 2.0
  */
 public class JDBCConnection implements DBConnection {
@@ -328,28 +328,29 @@ public class JDBCConnection implements DBConnection {
 		ResultSet result = null;
 		try{
 			// 1. Translate the ADQL query into SQL:
-			log(0, "Translating ADQL : " + adqlQuery.toADQL(), null);
+			logger.logDB(LogLevel.INFO, this, "TRANSLATE", "Translating ADQL: " + adqlQuery.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
 			sql = translator.translate(adqlQuery);
 
 			// 2. Execute the SQL query:
 			Statement stmt = connection.createStatement();
-			log(0, "Executing SQL : " + sql, null);
+			logger.logDB(LogLevel.INFO, this, "EXECUTE", "Executing translated query: " + sql.replaceAll("(\t|\r?\n)+", " "), null);
 			result = stmt.executeQuery(sql);
 
 			// 3. Return the result through a TableIterator object:
-			log(0, "Returning result...", null);
+			logger.logDB(LogLevel.INFO, this, "RESULT", "Returning result", null);
 			return new ResultSetTableIterator(result, dbms, adqlQuery.getResultingColumns());
 
 		}catch(SQLException se){
 			close(result);
-			log(2, "Unexpected error while EXECUTING SQL query!", se);
+			logger.logDB(LogLevel.ERROR, this, "EXECUTE", "Unexpected error while EXECUTING SQL query!", se);
 			throw new DBException("Unexpected error while executing a SQL query: " + se.getMessage(), se);
 		}catch(TranslationException te){
 			close(result);
-			log(2, "Unexpected error while TRANSLATING ADQL into SQL!", te);
+			logger.logDB(LogLevel.ERROR, this, "TRANSLATE", "Unexpected error while TRANSLATING ADQL into SQL!", te);
 			throw new DBException("Unexpected error while translating ADQL into SQL: " + te.getMessage(), te);
 		}catch(DataReadException dre){
 			close(result);
+			logger.logDB(LogLevel.ERROR, this, "RESULT", "Unexpected error while reading the query result!", dre);
 			throw new DBException("Impossible to read the query result, because: " + dre.getMessage(), dre);
 		}
 	}
@@ -430,23 +431,23 @@ public class JDBCConnection implements DBConnection {
 			stmt = connection.createStatement();
 
 			// load all schemas from TAP_SCHEMA.schemas:
-			log(0, "Loading TAP_SCHEMA.schemas.", null);
+			logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.schemas.", null);
 			loadSchemas(tap_schema.getTable(STDTable.SCHEMAS.label), metadata, stmt);
 
 			// load all tables from TAP_SCHEMA.tables:
-			log(0, "Loading TAP_SCHEMA.tables.", null);
+			logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.tables.", null);
 			List<TAPTable> lstTables = loadTables(tap_schema.getTable(STDTable.TABLES.label), metadata, stmt);
 
 			// load all columns from TAP_SCHEMA.columns:
-			log(0, "Loading TAP_SCHEMA.columns.", null);
+			logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.columns.", null);
 			loadColumns(tap_schema.getTable(STDTable.COLUMNS.label), lstTables, stmt);
 
 			// load all foreign keys from TAP_SCHEMA.keys and TAP_SCHEMA.key_columns:
-			log(0, "Loading TAP_SCHEMA.keys and TAP_SCHEMA.key_columns.", null);
+			logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.keys and TAP_SCHEMA.key_columns.", null);
 			loadKeys(tap_schema.getTable(STDTable.KEYS.label), tap_schema.getTable(STDTable.KEY_COLUMNS.label), lstTables, stmt);
 
 		}catch(SQLException se){
-			log(2, "Impossible to create a Statement!", se);
+			logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create a Statement!", se);
 			throw new DBException("Can not create a Statement!", se);
 		}finally{
 			close(stmt);
@@ -496,7 +497,7 @@ public class JDBCConnection implements DBConnection {
 				metadata.addSchema(newSchema);
 			}
 		}catch(SQLException se){
-			log(2, "Impossible to load schemas from TAP_SCHEMA.schemas!", se);
+			logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load schemas from TAP_SCHEMA.schemas!", se);
 			throw new DBException("Impossible to load schemas from TAP_SCHEMA.schemas!", se);
 		}finally{
 			close(rs);
@@ -549,7 +550,7 @@ public class JDBCConnection implements DBConnection {
 				// get the schema:
 				TAPSchema schema = metadata.getSchema(schemaName);
 				if (schema == null){
-					log(2, "Impossible to find the schema of the table \"" + tableName + "\": \"" + schemaName + "\"!", null);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the schema of the table \"" + tableName + "\": \"" + schemaName + "\"!", null);
 					throw new DBException("Impossible to find the schema of the table \"" + tableName + "\": \"" + schemaName + "\"!");
 				}
 
@@ -575,7 +576,7 @@ public class JDBCConnection implements DBConnection {
 
 			return lstTables;
 		}catch(SQLException se){
-			log(2, "Impossible to load tables from TAP_SCHEMA.tables!", se);
+			logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load tables from TAP_SCHEMA.tables!", se);
 			throw new DBException("Impossible to load tables from TAP_SCHEMA.tables!", se);
 		}finally{
 			close(rs);
@@ -626,7 +627,7 @@ public class JDBCConnection implements DBConnection {
 				// get the table:
 				TAPTable table = searchTable(tableName, lstTables.iterator());
 				if (table == null){
-					log(2, "Impossible to find the table of the column \"" + columnName + "\": \"" + tableName + "\"!", null);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the table of the column \"" + columnName + "\": \"" + tableName + "\"!", null);
 					throw new DBException("Impossible to find the table of the column \"" + columnName + "\": \"" + tableName + "\"!");
 				}
 
@@ -655,7 +656,7 @@ public class JDBCConnection implements DBConnection {
 				table.addColumn(newColumn);
 			}
 		}catch(SQLException se){
-			log(2, "Impossible to load columns from TAP_SCHEMA.columns!", se);
+			logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se);
 			throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se);
 		}finally{
 			close(rs);
@@ -709,12 +710,12 @@ public class JDBCConnection implements DBConnection {
 				// get the two tables (source and target):
 				TAPTable sourceTable = searchTable(from_table, lstTables.iterator());
 				if (sourceTable == null){
-					log(2, "Impossible to find the source table of the foreign key \"" + key_id + "\": \"" + from_table + "\"!", null);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the source table of the foreign key \"" + key_id + "\": \"" + from_table + "\"!", null);
 					throw new DBException("Impossible to find the source table of the foreign key \"" + key_id + "\": \"" + from_table + "\"!");
 				}
 				TAPTable targetTable = searchTable(target_table, lstTables.iterator());
 				if (targetTable == null){
-					log(2, "Impossible to find the target table of the foreign key \"" + key_id + "\": \"" + target_table + "\"!", null);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the target table of the foreign key \"" + key_id + "\": \"" + target_table + "\"!", null);
 					throw new DBException("Impossible to find the target table of the foreign key \"" + key_id + "\": \"" + target_table + "\"!");
 				}
 
@@ -727,7 +728,7 @@ public class JDBCConnection implements DBConnection {
 					while(rsKeyCols.next())
 						columns.put(rsKeyCols.getString(1), rsKeyCols.getString(2));
 				}catch(SQLException se){
-					log(2, "Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se);
 					throw new DBException("Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se);
 				}finally{
 					close(rsKeyCols);
@@ -737,12 +738,12 @@ public class JDBCConnection implements DBConnection {
 				try{
 					sourceTable.addForeignKey(key_id, targetTable, columns, nullifyIfNeeded(description), nullifyIfNeeded(utype));
 				}catch(Exception ex){
-					log(2, "Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex);
+					logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex);
 					throw new DBException("Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex);
 				}
 			}
 		}catch(SQLException se){
-			log(2, "Impossible to load columns from TAP_SCHEMA.columns!", se);
+			logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se);
 			throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se);
 		}finally{
 			close(rs);
@@ -788,25 +789,26 @@ public class JDBCConnection implements DBConnection {
 			stmt = connection.createStatement();
 
 			// 1. Ensure TAP_SCHEMA exists and drop all its standard TAP tables:
-			log(0, "Cleaning TAP_SCHEMA.", null);
+			logger.logDB(LogLevel.INFO, this, "CLEAN_TAP_SCHEMA", "Cleaning TAP_SCHEMA.", null);
 			resetTAPSchema(stmt, stdTables);
 
 			// 2. Create all standard TAP tables:
-			log(0, "Creating TAP_SCHEMA tables.", null);
+			logger.logDB(LogLevel.INFO, this, "CREATE_TAP_SCHEMA", "Creating TAP_SCHEMA tables.", null);
 			for(TAPTable table : stdTables)
 				createTAPSchemaTable(table, stmt);
 
 			// C. FILL THE NEW TABLE USING THE GIVEN DATA ITERATOR:
-			log(0, "Filling TAP_SCHEMA tables.", null);
+			logger.logDB(LogLevel.INFO, this, "CREATE_TAP_SCHEMA", "Filling TAP_SCHEMA tables.", null);
 			fillTAPSchema(metadata);
 
 			// D. CREATE THE INDEXES OF ALL STANDARD TAP TABLES:
-			log(0, "Creating TAP_SCHEMA tables' indexes.", null);
+			logger.logDB(LogLevel.INFO, this, "CREATE_TAP_SCHEMA", "Creating TAP_SCHEMA tables' indexes.", null);
 			for(TAPTable table : stdTables)
 				createTAPTableIndexes(table, stmt);
 
 			commit();
 		}catch(SQLException se){
+			logger.logDB(LogLevel.ERROR, this, "CREATE_TAP_SCHEMA", "Impossible to SET TAP_SCHEMA in DB!", se);
 			rollback();
 			throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se);
 		}finally{
@@ -1447,19 +1449,10 @@ public class JDBCConnection implements DBConnection {
 	 * </i></p>
 	 * 
 	 * <p><i><b>Important note:</b>
-	 * 	This function may modify the given {@link TAPTable} object in the following cases:.
+	 * 	This function may modify the given {@link TAPTable} object if schemas are not supported by this connection.
+	 * 	In this case, this function will prefix the table's DB name by the schema's DB name directly inside the given {@link TAPTable} object
+	 * 	(building the prefix with {@link #getTablePrefix(String)}). Then the DB name of the schema will be set to NULL.
 	 * </i></p>
-	 * <ul>
-	 * 	<li><i>
-	 * 		If no schema is set to the given {@link TAPTable} object, one will be set automatically by this function.
-	 * 		This schema will have the same ADQL and DB name: {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
-	 * 	</i></li>
-	 * 	<li><i>
-	 * 		If schemas are not supported by this connection, this function will prefix the table DB name by the schema DB name directly
-	 * 		inside the given {@link TAPTable} object (building the prefix with {@link #getTablePrefix(String)}). Then the DB name
-	 * 		of the schema will be set to NULL.
-	 * 	</i></li>
-	 * </ul>
 	 * 
 	 * <p><i>Note:
 	 * 	If the upload schema does not already exist in the database, it will be created.
@@ -1492,8 +1485,11 @@ public class JDBCConnection implements DBConnection {
 				stmt.executeUpdate("CREATE SCHEMA " + translator.getQualifiedSchemaName(tableDef) + ";");
 
 			// 1bis. Ensure the table does not already exist and if it is the case, throw an understandable exception:
-			else if (isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), dbMeta))
-				throw new DBException("Impossible to create the user uploaded table in the database: " + translator.getQualifiedTableName(tableDef) + "! This table already exists.");
+			else if (isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), dbMeta)){
+				DBException de = new DBException("Impossible to create the user uploaded table in the database: " + translator.getQualifiedTableName(tableDef) + "! This table already exists.");
+				logger.logDB(LogLevel.ERROR, this, "ADD_UPLOAD_TABLE", de.getMessage(), de);
+				throw de;
+			}
 
 			// 2. Create the table:
 			// ...build the SQL query:
@@ -1524,7 +1520,7 @@ public class JDBCConnection implements DBConnection {
 
 		}catch(SQLException se){
 			rollback();
-			log(1, "Impossible to create the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
+			logger.logDB(LogLevel.WARNING, this, "ADD_UPLOAD_TABLE", "Impossible to create the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
 			throw new DBException("Impossible to create the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
 		}catch(DBException de){
 			rollback();
@@ -1604,19 +1600,10 @@ public class JDBCConnection implements DBConnection {
 	 * </i></p>
 	 * 
 	 * <p><i><b>Important note:</b>
-	 * 	This function may modify the given {@link TAPTable} object in the following cases:.
+	 * 	This function may modify the given {@link TAPTable} object if schemas are not supported by this connection.
+	 * 	In this case, this function will prefix the table's DB name by the schema's DB name directly inside the given {@link TAPTable} object
+	 * 	(building the prefix with {@link #getTablePrefix(String)}). Then the DB name of the schema will be set to NULL.
 	 * </i></p>
-	 * <ul>
-	 * 	<li><i>
-	 * 		If no schema is set to the given {@link TAPTable} object, one will be set automatically by this function.
-	 * 		This schema will have the same ADQL and DB name: {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
-	 * 	</i></li>
-	 * 	<li><i>
-	 * 		If schemas are not supported by this connection, this function will prefix the table DB name by the schema DB name directly
-	 * 		inside the given {@link TAPTable} object (building the prefix with {@link #getTablePrefix(String)}). Then the DB name
-	 * 		of the schema will be set to NULL.
-	 * 	</i></li>
-	 * </ul>
 	 * 
 	 * <p><i>Note:
 	 * 	This implementation is able to drop only one uploaded table. So if this function finds more than one table matching to the given one,
@@ -1650,7 +1637,7 @@ public class JDBCConnection implements DBConnection {
 			return (cnt == 0);
 
 		}catch(SQLException se){
-			log(1, "Impossible to drop the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
+			logger.logDB(LogLevel.WARNING, this, "DROP_UPLOAD_TABLE", "Impossible to drop the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
 			throw new DBException("Impossible to drop the uploaded table: " + translator.getQualifiedTableName(tableDef) + "!", se);
 		}finally{
 			close(stmt);
@@ -1663,11 +1650,7 @@ public class JDBCConnection implements DBConnection {
 	 * <p>Thus, the following cases are taken into account:</p>
 	 * <ul>
 	 * 	<li>
-	 * 		If no schema is set to the given {@link TAPTable} object, one will be set automatically by this function.
-	 * 		This schema will have the same ADQL and DB name: {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
-	 * 	</li>
-	 * 	<li>
-	 * 		The schema name of the given table , if provided, MUST be {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD") in ADQL.
+	 * 		The schema name of the given table MUST be {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD") in ADQL.
 	 * 		If it has another ADQL name, an exception will be thrown. Of course, the DB name of this schema MAY be different.
 	 * 	</li>
 	 * 	<li>
@@ -1679,16 +1662,12 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @param tableDef	Definition of the table to create/drop.
 	 * 
-	 * @throws DBException	If the ADQL schema name of the given table is not {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
+	 * @throws DBException	If the given table is not in a schema
+	 *                    	or if the ADQL name of this schema is not {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
 	 */
 	protected void checkUploadedTableDef(final TAPTable tableDef) throws DBException{
-		// If no schema is provided, consider it is the default and standard upload schema - TAP_UPLOAD: 
-		if (tableDef.getSchema() == null){
-			TAPSchema uploadSchema = new TAPSchema(STDSchema.UPLOADSCHEMA.label, "Schema for tables uploaded by users.");
-			uploadSchema.addTable(tableDef);
-		}
-		// But if the ADQL name of the provided schema is not TAP_UPLOAD, throw an exception:
-		else if (!tableDef.getSchema().getADQLName().equals(STDSchema.UPLOADSCHEMA.label))
+		// If the table has no defined schema or if the ADQL name of the schema is not TAP_UPLOAD, throw an exception:
+		if (tableDef.getSchema() == null || !tableDef.getSchema().getADQLName().equals(STDSchema.UPLOADSCHEMA.label))
 			throw new DBException("Missing upload schema! An uploaded table must be inside a schema whose the ADQL name is strictly equals to \"" + STDSchema.UPLOADSCHEMA.label + "\" (but the DB name may be different).");
 
 		// If schemas are not supported, prefix the table name and set to NULL the DB schema name:
@@ -1702,32 +1681,6 @@ public class JDBCConnection implements DBConnection {
 	/* TOOL FUNCTIONS */
 	/* ************** */
 
-	/**
-	 * <p>Log the given message and/or exception with the given level.</p>
-	 * 
-	 * <p><i>Note:
-	 * 	If no logger has been provided, only the WARNING and ERROR messages are printed in the standard error output stream.
-	 * </i></p>
-	 * 
-	 * @param level		<=0: INFO, 1: WARNING, >=2: ERROR 
-	 * @param message	Message to log. <i>may be NULL</i>
-	 * @param ex		Exception to log. <i>may be NULL</i>
-	 */
-	protected void log(final int level, String message, final Exception ex){
-		//if (logger != null){
-		if (message != null)
-			message = message.replaceAll("(\t|\r?\n)+", " ");
-		else
-			message = (ex != null ? ex.getClass().getName() : "");
-		message = "{" + getID() + "} " + message;
-		PrintStream out = (level <= 0) ? System.out : System.err;
-		out.println((level <= 0 ? "[INFO] " : (level == 1 ? "[WARNING] " : "[ERROR] ")) + message + (ex != null ? " CAUSED BY: " + ex.getMessage() : "")); // TODO rmb debug msg
-		if (ex != null)
-			ex.printStackTrace(out);
-		// TODO JDBCConnection.log(int, String, Exception)
-		//}
-	}
-
 	/**
 	 * <p>Get the DBMS compatible datatype corresponding to the given column TAPType.</p>
 	 * 
@@ -1860,11 +1813,11 @@ public class JDBCConnection implements DBConnection {
 		try{
 			if (supportsTransaction){
 				connection.setAutoCommit(false);
-				log(0, "Transaction STARTED.", null);
+				logger.logDB(LogLevel.INFO, this, "START_TRANSACTION", "Transaction STARTED.", null);
 			}
 		}catch(SQLException se){
 			supportsTransaction = false;
-			log(2, "Transaction STARTing impossible!", se);
+			logger.logDB(LogLevel.ERROR, this, "START_TRANSACTION", "Transaction STARTing impossible!", se);
 			throw new DBException("Transaction STARTing impossible!", se);
 		}
 	}
@@ -1891,11 +1844,11 @@ public class JDBCConnection implements DBConnection {
 		try{
 			if (supportsTransaction){
 				connection.commit();
-				log(0, "Transaction COMMITED.", null);
+				logger.logDB(LogLevel.INFO, this, "COMMIT", "Transaction COMMITED.", null);
 			}
 		}catch(SQLException se){
 			supportsTransaction = false;
-			log(2, "Transaction COMMIT impossible!", se);
+			logger.logDB(LogLevel.ERROR, this, "COMMIT", "Transaction COMMIT impossible!", se);
 			throw new DBException("Transaction COMMIT impossible!", se);
 		}
 	}
@@ -1922,11 +1875,11 @@ public class JDBCConnection implements DBConnection {
 		try{
 			if (supportsTransaction){
 				connection.rollback();
-				log(0, "Transaction ROLLBACKED.", null);
+				logger.logDB(LogLevel.INFO, this, "ROLLBACK", "Transaction ROLLBACKED.", null);
 			}
 		}catch(SQLException se){
 			supportsTransaction = false;
-			log(2, "Transaction ROLLBACK impossible!", se);
+			logger.logDB(LogLevel.ERROR, this, "ROLLBACK", "Transaction ROLLBACK impossible!", se);
 		}
 	}
 
@@ -1951,11 +1904,11 @@ public class JDBCConnection implements DBConnection {
 		try{
 			if (supportsTransaction){
 				connection.setAutoCommit(true);
-				log(0, "Transaction ENDED.", null);
+				logger.logDB(LogLevel.INFO, this, "END_TRANSACTION", "Transaction ENDED.", null);
 			}
 		}catch(SQLException se){
 			supportsTransaction = false;
-			log(2, "Transaction ENDing impossible!", se);
+			logger.logDB(LogLevel.ERROR, this, "END_TRANSACTION", "Transaction ENDing impossible!", se);
 		}
 	}
 
@@ -1976,7 +1929,7 @@ public class JDBCConnection implements DBConnection {
 			if (rs != null)
 				rs.close();
 		}catch(SQLException se){
-			log(1, "Can not close a ResultSet!", null);
+			logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a ResultSet!", null);
 		}
 	}
 
@@ -1997,7 +1950,7 @@ public class JDBCConnection implements DBConnection {
 			if (stmt != null)
 				stmt.close();
 		}catch(SQLException se){
-			log(1, "Can not close a Statement!", null);
+			logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a Statement!", null);
 		}
 	}
 
@@ -2202,7 +2155,7 @@ public class JDBCConnection implements DBConnection {
 			}
 
 			if (cnt > 1){
-				log(2, "More than one table match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + "))!", null);
+				logger.logDB(LogLevel.ERROR, this, "TABLE_EXIST", "More than one table match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + "))!", null);
 				throw new DBException("More than one table match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + "))!");
 			}
 
@@ -2314,9 +2267,9 @@ public class JDBCConnection implements DBConnection {
 				 * and must stop the whole TAP_SCHEMA initialization.
 				 */
 				if (indRow == 1)
-					log(1, "BATCH query impossible => TRYING AGAIN IN A NORMAL EXECUTION (executeUpdate())!", se);
+					logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "BATCH query impossible => TRYING AGAIN IN A NORMAL EXECUTION (executeUpdate())!", se);
 				else{
-					log(2, "BATCH query impossible!", se);
+					logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH query impossible!", se);
 					throw new DBException("BATCH query impossible!", se);
 				}
 			}
@@ -2330,7 +2283,7 @@ public class JDBCConnection implements DBConnection {
 
 			// Check the row has been inserted with success:
 			if (nbRowsWritten != 1){
-				log(2, "ROW " + indRow + " not inserted!", null);
+				logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "ROW " + indRow + " not inserted!", null);
 				throw new DBException("ROW " + indRow + " not inserted!");
 			}
 		}
@@ -2368,7 +2321,7 @@ public class JDBCConnection implements DBConnection {
 				rows = stmt.executeBatch();
 			}catch(SQLException se){
 				supportsBatchUpdates = false;
-				log(2, "BATCH execution impossible!", se);
+				logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH execution impossible!", se);
 				throw new DBException("BATCH execution impossible!", se);
 			}
 
@@ -2376,7 +2329,7 @@ public class JDBCConnection implements DBConnection {
 			try{
 				stmt.clearBatch();
 			}catch(SQLException se){
-				log(1, "CLEAR BATCH impossible!", se);
+				logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "CLEAR BATCH impossible!", se);
 			}
 
 			// Count the updated rows:
@@ -2386,7 +2339,7 @@ public class JDBCConnection implements DBConnection {
 
 			// Check all given rows have been inserted with success:
 			if (nbRowsUpdated != nbRows){
-				log(2, "ROWS not all update (" + nbRows + " to update ; " + nbRowsUpdated + " updated)!", null);
+				logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "ROWS not all update (" + nbRows + " to update ; " + nbRowsUpdated + " updated)!", null);
 				throw new DBException("ROWS not all updated (" + nbRows + " to update ; " + nbRowsUpdated + " updated)!");
 			}
 		}
diff --git a/src/tap/error/DefaultTAPErrorWriter.java b/src/tap/error/DefaultTAPErrorWriter.java
index 28c8602423691a108a7111138f68b2460f3c3e06..e6e725663efb27cae05b0536f943f76df19efc95 100644
--- a/src/tap/error/DefaultTAPErrorWriter.java
+++ b/src/tap/error/DefaultTAPErrorWriter.java
@@ -21,69 +21,129 @@ package tap.error;
  */
 
 import java.io.IOException;
+import java.util.HashMap;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import tap.ServiceConnection;
+import tap.TAPException;
+import tap.formatter.VOTableFormat;
+import tap.log.DefaultTAPLog;
+import tap.log.TAPLog;
 import uws.UWSException;
 import uws.job.ErrorType;
 import uws.job.user.JobOwner;
-import uws.service.error.AbstractServiceErrorWriter;
+import uws.service.error.DefaultUWSErrorWriter;
 import uws.service.error.ServiceErrorWriter;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Default implementation of {@link ServiceErrorWriter} for a TAP service.</p>
  * 
- * <p>All errors are written using the function {@link #formatError(Throwable, boolean, uws.job.ErrorType, int, String, JobOwner, HttpServletResponse)}
- * of the abstract implementation of the error writer: {@link AbstractServiceErrorWriter}.</p>
+ * <p>
+ * 	On the contrary to the UWS standard, all errors must be formatted in VOTable.
+ * 	So, all errors given to this {@link ServiceErrorWriter} are formatted in VOTable using the structure defined by the IVOA.
+ * 	To do that, this class will use the function {@link VOTableFormat#writeError(String, java.util.Map, java.io.PrintWriter)}.
+ * </p>
  * 
- * <p>A {@link UWSException} may precise the HTTP error code to apply. That's why, {@link #writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
- * has been overridden: to get this error code and submit it to the {@link #formatError(Throwable, boolean, uws.job.ErrorType, int, String, JobOwner, HttpServletResponse)}
- * function. Besides, the stack trace of {@link UWSException}s is not printed (except if the message is NULL or empty).
- * And this error will be logged only if its error code is {@link UWSException#INTERNAL_SERVER_ERROR}.</p>
+ * <p>
+ * 	{@link UWSException}s and {@link TAPException}s may precise the HTTP error code to apply,
+ * 	which will be used to set the HTTP status of the response. If it is a different kind of exception,
+ * 	the HTTP status 500 (INTERNAL SERVER ERROR) will be used.
+ * </p>
  * 
- * <p>2 formats are managed by this implementation: HTML (default) and JSON. That means the writer will format and
- * write a given error in the best appropriate format. This format is chosen thanks to the "Accept" header of the HTTP request.
- * If no request is provided or if there is no known format, the HTML format is chosen by default.</p>
+ * <p>
+ * 	Besides, all exceptions except {@link UWSException} and {@link TAPException} will be logged as FATAL in the TAP context
+ * 	(with no event and no object). Thus the full stack trace is available to the administrator so that the error can
+ * 	be understood as easily and quickly as possible.
+ * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  * 
- * @see AbstractServiceErrorWriter
+ * @see DefaultUWSErrorWriter
  */
 public class DefaultTAPErrorWriter implements ServiceErrorWriter {
 
-	protected final ServiceConnection service;
+	/** Logger to use to report any unexpected error. */
+	protected final TAPLog logger;
 
-	public DefaultTAPErrorWriter(final ServiceConnection service){
-		this.service = service;
+	/** Object to use to format an error message in VOTable. */
+	protected final VOTableFormat formatter;
+
+	/**
+	 * <p>Build an error writer for TAP.</p>
+	 * 
+	 * <p>
+	 * 	On the contrary to the UWS standard, TAP standard defines a format for error reporting.
+	 * 	Errors should be reported as VOTable document with a defined structure.  This one is well
+	 * 	managed by {@link VOTableFormat} which is actually called by this class when an error must
+	 * 	be written.
+	 * </p>
+	 * 
+	 * @param service	Description of the TAP service.
+	 * 
+	 * @throws NullPointerException	If no service description is provided.
+	 */
+	public DefaultTAPErrorWriter(final ServiceConnection service) throws NullPointerException{
+		if (service == null)
+			throw new NullPointerException("Missing description of this TAP service! Can not build a ServiceErrorWriter.");
+
+		this.logger = (service.getLogger() == null) ? new DefaultTAPLog(System.err) : service.getLogger();
+		this.formatter = new VOTableFormat(service);
 	}
 
 	@Override
-	public void writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, JobOwner user, String action) throws IOException{
-		if (t instanceof UWSException){
-			UWSException ue = (UWSException)t;
-			formatError(ue, (ue.getMessage() == null || ue.getMessage().trim().isEmpty()), ue.getUWSErrorType(), ue.getHttpErrorCode(), action, user, response, (request != null) ? request.getHeader("Accept") : null);
-			if (ue.getHttpErrorCode() == UWSException.INTERNAL_SERVER_ERROR)
-				getLogger().error(ue);
-			getLogger().httpRequest(request, user, action, ue.getHttpErrorCode(), ue.getMessage(), ue);
-		}else{
-			if (t != null && response != null){
-				formatError(t, true, ErrorType.FATAL, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, action, user, response, (request != null) ? request.getHeader("Accept") : null);
-				getLogger().error(t);
-				String errorMsg = t.getMessage();
-				if (errorMsg == null || errorMsg.trim().isEmpty())
-					errorMsg = t.getClass().getName() + " (no error message)";
-				getLogger().httpRequest(request, user, action, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMsg, t);
-			}
+	public void writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException{
+		if (t == null || response == null)
+			return;
+
+		// If expected error, just write it in VOTable:
+		if (t instanceof UWSException || t instanceof TAPException){
+			// get the error type:
+			ErrorType type = (t instanceof UWSException) ? ((UWSException)t).getUWSErrorType() : ErrorType.FATAL;
+			// get the HTTP error code:
+			int httpErrorCode = (t instanceof UWSException) ? ((UWSException)t).getHttpErrorCode() : ((TAPException)t).getHttpErrorCode();
+			// write the VOTable error:
+			writeError(t.getMessage(), type, httpErrorCode, response, request, reqID, user, action);
+		}
+		// Otherwise, log it and write a message to the user:
+		else{
+			// log the error as GRAVE/FATAL (because unexpected/unmanaged):
+			logger.logTAP(LogLevel.FATAL, null, null, "[REQUEST N°" + reqID + "] " + t.getMessage(), t);
+			// write a message to the user:
+			writeError("INTERNAL SERVER ERROR! Sorry, this error is unexpected and no explaination can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following REQ_ID).", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
 		}
 	}
 
 	@Override
-	public void writeError(String message, ErrorType type, int httpErrorCode, HttpServletResponse response, HttpServletRequest request, JobOwner user, String action) throws IOException{
-		// TODO ServiceErrorWriter.writeError
+	public void writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException{
+		if (message == null || response == null)
+			return;
+
+		// Erase anything written previously in the HTTP response:
+		response.reset();
+
+		// Set the HTTP status:
+		response.setStatus((httpErrorCode <= 0) ? 500 : httpErrorCode);
+
+		// Set the MIME type of the answer (XML for a VOTable document):
+		response.setContentType("text/xml");
+
+		// List any additional information useful to report to the user:
+		HashMap<String,String> addInfos = new HashMap<String,String>();
+		if (reqID != null)
+			addInfos.put("REQ_ID", reqID);
+		if (type != null)
+			addInfos.put("ERROR_TYPE", type.toString());
+		if (user != null)
+			addInfos.put("USER", user.getID() + ((user.getPseudo() == null) ? "" : " (" + user.getPseudo() + ")"));
+		if (action != null)
+			addInfos.put("ACTION", action);
 
+		// Format the error in VOTable and write the document in the given HTTP response:
+		formatter.writeError(message, addInfos, response.getWriter());
 	}
 
 }
diff --git a/src/tap/file/LocalTAPFileManager.java b/src/tap/file/LocalTAPFileManager.java
index 2e9beecb5e191f07f0e46b4fd695407287d5171f..f7555c36be2593d5928b163935c5e74fe1397106 100644
--- a/src/tap/file/LocalTAPFileManager.java
+++ b/src/tap/file/LocalTAPFileManager.java
@@ -16,14 +16,13 @@ package tap.file;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.File;
 
-import tap.db.DBConnection;
 import uws.UWSException;
-
 import uws.service.file.DefaultOwnerGroupIdentifier;
 import uws.service.file.LocalUWSFileManager;
 import uws.service.file.OwnerGroupIdentifier;
@@ -37,8 +36,8 @@ import uws.service.file.OwnerGroupIdentifier;
  * 	All files are written in the local machine, into the given directory.
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
  * 
  * @see LocalUWSFileManager
  */
@@ -47,9 +46,6 @@ public class LocalTAPFileManager extends LocalUWSFileManager implements TAPFileM
 	/** Default name of the upload directory. */
 	public final static String DEFAULT_UPLOAD_DIRECTORY_NAME = "Upload";
 
-	/** Default name of the DB activity log file. */
-	public final static String DEFAULT_DB_ACTIVITY_LOG_FILE_NAME = "service_db_activity.log";
-
 	/** Local directory in which all uploaded files will be kept until they are read or ignored (in this case, they will be deleted). */
 	private final File uploadDirectory;
 
@@ -118,14 +114,6 @@ public class LocalTAPFileManager extends LocalUWSFileManager implements TAPFileM
 		uploadDirectory = new File(rootDirectory, getUploadDirectoryName());
 	}
 
-	@Override
-	protected String getLogFileName(final String logTypeGroup){
-		if (logTypeGroup != null && logTypeGroup.equals(DBConnection.LOG_TYPE_DB_ACTIVITY.getCustomType()))
-			return DEFAULT_DB_ACTIVITY_LOG_FILE_NAME;
-		else
-			return super.getLogFileName(logTypeGroup);
-	}
-
 	/**
 	 * <p>Gets the name of the directory in which all uploaded files will be saved.</p>
 	 * 
diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java
index 5437854eb09438063763e5890ed21b52880cf6b8..fbde5576cc657399ad43d6250bc94dabc7a4ef1b 100644
--- a/src/tap/formatter/JSONFormat.java
+++ b/src/tap/formatter/JSONFormat.java
@@ -35,13 +35,14 @@ import tap.metadata.TAPColumn;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
 import tap.metadata.VotType;
+import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 
 /**
  * Format any given query (table) result into JSON.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class JSONFormat implements OutputFormat {
 
@@ -126,7 +127,7 @@ public class JSONFormat implements OutputFormat {
 
 			// Report stats about the result writing:
 			if (logFormatReport)
-				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in JSON ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + " ms !");
+				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in JSON ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + "ms!", null);
 
 		}catch(JSONException je){
 			throw new TAPException("Error while writing a query result in JSON !", je);
diff --git a/src/tap/formatter/SVFormat.java b/src/tap/formatter/SVFormat.java
index 81e25c10eb3b189709f5b5d8fa9fd425e8400f38..88ddb095c51f830020d0ba8a1e67f0ca11c897cf 100644
--- a/src/tap/formatter/SVFormat.java
+++ b/src/tap/formatter/SVFormat.java
@@ -28,13 +28,14 @@ import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
 import tap.data.TableIterator;
+import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 
 /**
  * Format any given query (table) result into CSV or TSV (or with custom separator).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class SVFormat implements OutputFormat {
 
@@ -178,10 +179,10 @@ public class SVFormat implements OutputFormat {
 
 			// Report stats about the result writing:
 			if (logFormatReport)
-				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in SV[" + delimitStr + "] ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !");
+				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in SV[" + delimitStr + "] ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + "ms!", null);
 
 		}catch(Exception ex){
-			service.getLogger().error("While formatting in (T/C)SV !", ex);
+			service.getLogger().logTAP(LogLevel.ERROR, execReport, "FORMAT", "Error while formatting in (T/C)SV (delimiter: " + delimitStr + ")!", ex);
 		}
 	}
 
diff --git a/src/tap/formatter/TextFormat.java b/src/tap/formatter/TextFormat.java
index 35aeb21ec42883aaf6daca92d09d8a74219d4daf..65f8247f9f5d0b7c11e60b7a357d661136364d05 100644
--- a/src/tap/formatter/TextFormat.java
+++ b/src/tap/formatter/TextFormat.java
@@ -27,6 +27,7 @@ import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
 import tap.data.TableIterator;
+import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 import cds.util.AsciiTable;
 
@@ -35,7 +36,7 @@ import cds.util.AsciiTable;
  * (columns' width are adjusted so that all columns are well aligned and of the same width).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class TextFormat implements OutputFormat {
 
@@ -111,10 +112,10 @@ public class TextFormat implements OutputFormat {
 
 			// Report stats about the result writing:
 			if (logFormatReport)
-				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in text ; " + nbRows + " rows ; " + ((execReport != null && execReport.resultingColumns != null) ? "?" : execReport.resultingColumns.length) + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !");
+				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in text ; " + nbRows + " rows ; " + ((execReport != null && execReport.resultingColumns != null) ? "?" : execReport.resultingColumns.length) + " columns) in " + (System.currentTimeMillis() - startTime) + "ms!", null);
 
 		}catch(Exception ex){
-			service.getLogger().error("While formatting in text/plain !", ex);
+			service.getLogger().logTAP(LogLevel.ERROR, execReport, "FORMAT", "Error while formatting in text/plain!", ex);
 		}
 	}
 
diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java
index 16d5e321ad79819e94586ae4a6298a503b6679cb..d5e07c8bae09e3b9150a083517e6f4a4efcc3d11 100644
--- a/src/tap/formatter/VOTableFormat.java
+++ b/src/tap/formatter/VOTableFormat.java
@@ -25,12 +25,15 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
 
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
 import tap.data.DataReadException;
 import tap.data.TableIterator;
+import tap.error.DefaultTAPErrorWriter;
 import tap.metadata.TAPColumn;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
@@ -45,6 +48,7 @@ import uk.ac.starlink.table.StarTable;
 import uk.ac.starlink.votable.DataFormat;
 import uk.ac.starlink.votable.VOSerializer;
 import uk.ac.starlink.votable.VOTableVersion;
+import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 
 /**
@@ -63,8 +67,14 @@ import adql.db.DBColumn;
  * 	<li>QUERY = the ADQL query at the origin of this result.</li>
  * </ul>
  * 
+ * <p>
+ * 	Furthermore, this formatter provides a function to format an error in VOTable: {@link #writeError(String, Map, PrintWriter)}.
+ * 	This is useful for TAP which requires to return in VOTable any error that occurs while any operation.
+ * 	<i>See {@link DefaultTAPErrorWriter} for more details.</i>
+ * </p>
+ * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class VOTableFormat implements OutputFormat {
 
@@ -168,6 +178,78 @@ public class VOTableFormat implements OutputFormat {
 		return "xml";
 	}
 
+	/**
+	 * <p>Write the given error message as VOTable document.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	In the TAP protocol, all errors must be returned as VOTable. The class {@link DefaultTAPErrorWriter} is in charge of the management
+	 * 	and reporting of all errors. It is calling this function while the error message to display to the user is ready and
+	 * 	must be written in the HTTP response.
+	 * </i></p>
+	 * 
+	 * <p>Here is the XML format of this VOTable error:</p>
+	 * <pre>
+	 * 	&lt;VOTABLE version="..." xmlns="..." &gt;
+	 * 		&lt;RESOURCE type="results"&gt;
+	 * 			&lt;INFO name="QUERY_STATUS" value="ERROR&gt;
+	 * 				...
+	 * 			&lt;/INFO&gt;
+	 * 			&lt;INFO name="PROVIDER" value="..."&gt;...&lt;/INFO&gt;
+	 * 			&lt;!-- other optional INFOs (e.g. request parameters) --&gt;
+	 * 		&lt;/RESOURCE&gt;
+	 * 	&lt;/VOTABLE&gt;
+	 * </pre>
+	 * 
+	 * @param message	Error message to display to the user.
+	 * @param otherInfo	List of other additional information to display. <i>optional</i>
+	 * @param out		Stream in which the VOTable error must be written.
+	 * 
+	 * @throws IOException	If any error occurs while writing in the given output.
+	 * 
+	 * @since 2.0
+	 */
+	public void writeError(final String message, final Map<String,String> otherInfo, final PrintWriter writer) throws IOException{
+		BufferedWriter out = new BufferedWriter(writer);
+
+		// Set the root VOTABLE node:
+		out.write("<VOTABLE" + VOSerializer.formatAttribute("version", votVersion.getVersionNumber()) + VOSerializer.formatAttribute("xmlns", votVersion.getXmlNamespace()) + ">");
+		out.newLine();
+
+		// The RESOURCE note MUST have a type "results":	[REQUIRED]
+		out.write("<RESOURCE type=\"results\">");
+		out.newLine();
+
+		// Indicate that the query has been successfully processed:	[REQUIRED]
+		out.write("<INFO name=\"QUERY_STATUS\" value=\"ERROR\">" + VOSerializer.formatText(message) + "</INFO>");
+		out.newLine();
+
+		// Append the PROVIDER information (if any):	[OPTIONAL]
+		if (service.getProviderName() != null){
+			out.write("<INFO name=\"PROVIDER\"" + VOSerializer.formatAttribute("value", service.getProviderName()) + ">" + ((service.getProviderDescription() == null) ? "" : VOSerializer.formatText(service.getProviderDescription())) + "</INFO>");
+			out.newLine();
+		}
+
+		// Append the ADQL query at the origin of this result:	[OPTIONAL]
+		if (otherInfo != null){
+			Iterator<Map.Entry<String,String>> it = otherInfo.entrySet().iterator();
+			while(it.hasNext()){
+				Map.Entry<String,String> entry = it.next();
+				out.write("<INFO " + VOSerializer.formatAttribute("name", entry.getKey()) + ">" + VOSerializer.formatText(entry.getValue()) + "</INFO>");
+				out.newLine();
+			}
+		}
+
+		out.flush();
+
+		/* Write footer. */
+		out.write("</RESOURCE>");
+		out.newLine();
+		out.write("</VOTABLE>");
+		out.newLine();
+
+		out.flush();
+	}
+
 	@Override
 	public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{
 		try{
@@ -204,7 +286,7 @@ public class VOTableFormat implements OutputFormat {
 			out.flush();
 
 			if (logFormatReport)
-				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in VOTable ; " + table.getNbReadRows() + " rows ; " + table.getColumnCount() + " columns) in " + (System.currentTimeMillis() - start) + " ms !");
+				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in VOTable ; " + table.getNbReadRows() + " rows ; " + table.getColumnCount() + " columns) in " + (System.currentTimeMillis() - start) + "ms!", null);
 		}catch(IOException ioe){
 			throw new TAPException("Error while writing a query result in VOTable !", ioe);
 		}
diff --git a/src/tap/log/DefaultTAPLog.java b/src/tap/log/DefaultTAPLog.java
index ec5b35a87f464e2421a0f4481be101fe35cb2597..441a8f0ccb1d1eadf669bcf90ccc5187170fa89d 100644
--- a/src/tap/log/DefaultTAPLog.java
+++ b/src/tap/log/DefaultTAPLog.java
@@ -24,143 +24,97 @@ import java.io.OutputStream;
 import java.io.PrintWriter;
 
 import tap.TAPExecutionReport;
+import tap.TAPSyncJob;
 import tap.db.DBConnection;
 import tap.file.TAPFileManager;
-import tap.metadata.TAPMetadata;
-import tap.metadata.TAPTable;
+import tap.parameters.TAPParameters;
 import uws.service.log.DefaultUWSLog;
 
 /**
  * Default implementation of the {@link TAPLog} interface which lets logging any message about a TAP service.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  * 
  * @see DefaultUWSLog
  */
 public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 
-	public DefaultTAPLog(TAPFileManager fm){
+	/**
+	 * <p>Builds a {@link TAPLog} which will use the given file
+	 * manager to get the log output (see {@link TAPFileManager#getLogOutput(LogLevel, String)}).</p>
+	 * 
+	 * <p><i><u>note 1</u>: This constructor is particularly useful if the way of managing log output may change in the given file manager.
+	 * Indeed, the output may change in function of the type of message to log ({@link LogLevel}).</i></p>
+	 * 
+	 * <p><i><u>note 2</u> If no output can be found in the file manager the standard error output ({@link System#err})
+	 * will be chosen automatically for all log messages.</i></p>
+	 * 
+	 * @param fm	A TAP file manager.
+	 * 
+	 * @see DefaultUWSLog#DefaultUWSLog(uws.service.file.UWSFileManager)
+	 */
+	public DefaultTAPLog(final TAPFileManager fm){
 		super(fm);
 	}
 
-	public DefaultTAPLog(OutputStream output){
+	/**
+	 * <p>Builds a {@link TAPLog} which will print all its
+	 * messages into the given stream.</p>
+	 * 
+	 * <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link LogLevel}).</i></p>
+	 * 
+	 * @param output	An output stream.
+	 * 
+	 * @see DefaultUWSLog#DefaultUWSLog(OutputStream)
+	 */
+	public DefaultTAPLog(final OutputStream output){
 		super(output);
 	}
 
-	public DefaultTAPLog(PrintWriter writer){
+	/**
+	 * <p>Builds a {@link TAPLog} which will print all its
+	 * messages into the given stream.</p>
+	 * 
+	 * <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link LogLevel}).</i></p>
+	 * 
+	 * @param writer	A print writer.
+	 * 
+	 * @see DefaultUWSLog#DefaultUWSLog(PrintWriter)
+	 */
+	public DefaultTAPLog(final PrintWriter writer){
 		super(writer);
 	}
 
 	@Override
-	public void queryFinished(final TAPExecutionReport report){
-		StringBuffer buffer = new StringBuffer("QUERY END FOR " + report.jobID + "");
-		buffer.append(" - success ? ").append(report.success);
-		buffer.append(" - synchronous ? ").append(report.synchronous);
-		buffer.append(" - total duration = ").append(report.getTotalDuration()).append("ms");
-		buffer.append(" => upload=").append(report.getUploadDuration()).append("ms");
-		buffer.append(", parsing=").append(report.getParsingDuration()).append("ms");
-		buffer.append(", execution=").append(report.getExecutionDuration()).append("ms");
-		buffer.append(", formatting[").append(report.parameters.getFormat()).append("]=").append(report.getFormattingDuration()).append("ms");
-		info(buffer.toString());
-	}
-
-	public void dbActivity(final String message){
-		dbActivity(message, null);
-	}
-
-	public void dbActivity(final String message, final Throwable t){
-		String msgType = (t == null) ? "[INFO] " : "[ERROR] ";
-		log(DBConnection.LOG_TYPE_DB_ACTIVITY, ((message == null) ? null : (msgType + message)), t);
-	}
-
-	@Override
-	public void dbInfo(final String message){
-		dbActivity(message);
+	public void logDB(final LogLevel level, final DBConnection connection, final String event, final String message, final Throwable error){
+		log(level, "DB", event, (connection != null ? connection.getID() : null), message, error);
 	}
 
 	@Override
-	public void dbError(final String message, final Throwable t){
-		dbActivity(message, t);
-	}
-
-	@Override
-	public void tapMetadataFetched(TAPMetadata metadata){
-		dbActivity("TAP metadata fetched from the database !");
-	}
-
-	@Override
-	public void tapMetadataLoaded(TAPMetadata metadata){
-		dbActivity("TAP metadata loaded into the database !");
-	}
+	public void logTAP(final LogLevel level, final Object obj, final String event, final String message, final Throwable error){
+		// Get more information (when known event and available object):
+		String jobId = null, msgAppend = null;
+		try{
+			if (event != null && obj != null){
+				if (event.equals("SYNC_INIT"))
+					msgAppend = "QUERY=" + ((TAPParameters)obj).getQuery().replaceAll("(\t|\r?\n)+", " ");
+				else if (event.equals("SYNC_START"))
+					jobId = ((TAPSyncJob)obj).getID();
+				else if (obj instanceof TAPExecutionReport){
+					TAPExecutionReport report = (TAPExecutionReport)obj;
+					jobId = report.jobID;
+					msgAppend = (report.synchronous ? "SYNC" : "ASYNC") + ",duration=" + report.getTotalDuration() + "ms (upload=" + report.getUploadDuration() + ",parse=" + report.getParsingDuration() + ",exec=" + report.getExecutionDuration() + ",format[" + report.parameters.getFormat() + "]=" + report.getFormattingDuration() + ")";
+				}
+			}
+			if (msgAppend != null)
+				msgAppend = "\t" + msgAppend;
+		}catch(Throwable t){
+			error("Error while preparing a log message in logTAP(...)! The message will be logger but without additional information such as the job ID.", t);
+		}
 
-	@Override
-	public void connectionOpened(DBConnection connection, String dbName){
-		dbActivity("A connection has been opened to the database \"" + dbName + "\" !");
-	}
-
-	@Override
-	public void connectionClosed(DBConnection connection){
-		dbActivity("A database connection has been closed !");
-	}
-
-	@Override
-	public void transactionStarted(final DBConnection connection){
-		dbActivity("A transaction has been started !");
-	}
-
-	@Override
-	public void transactionCancelled(final DBConnection connection){
-		dbActivity("A transaction has been cancelled !");
-	}
-
-	@Override
-	public void transactionEnded(final DBConnection connection){
-		dbActivity("A transaction has been ended/commited !");
-	}
-
-	@Override
-	public void schemaCreated(final DBConnection connection, String schema){
-		dbActivity("CREATE SCHEMA \"" + schema + "\"\t" + connection.getID());
-	}
-
-	@Override
-	public void schemaDropped(final DBConnection connection, String schema){
-		dbActivity("DROP SCHEMA \"" + schema + "\"\t" + connection.getID());
-	}
-
-	protected final String getFullDBName(final TAPTable table){
-		return ((table.getSchema() != null) ? (table.getSchema().getDBName() + ".") : "") + table.getDBName();
-	}
-
-	@Override
-	public void tableCreated(final DBConnection connection, TAPTable table){
-		dbActivity("CREATE TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void tableDropped(final DBConnection connection, TAPTable table){
-		dbActivity("DROP TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void rowsInserted(final DBConnection connection, TAPTable table, int nbInsertedRows){
-		dbActivity("INSERT ROWS (" + ((nbInsertedRows > 0) ? nbInsertedRows : "???") + ") into \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void sqlQueryExecuting(final DBConnection connection, String sql){
-		dbActivity("EXECUTING SQL QUERY \t" + connection.getID() + "\n" + ((sql == null) ? "???" : sql.replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("\r", "")));
-	}
-
-	@Override
-	public void sqlQueryError(final DBConnection connection, String sql, Throwable t){
-		dbActivity("EXECUTION ERROR\t" + connection.getID(), t);
-	}
-
-	@Override
-	public void sqlQueryExecuted(final DBConnection connection, String sql){
-		dbActivity("SUCCESSFULL END OF EXECUTION\t" + connection.getID());
+		// Log the message:
+		log(level, "TAP", event, jobId, message + msgAppend, error);
 	}
 
 }
diff --git a/src/tap/log/TAPLog.java b/src/tap/log/TAPLog.java
index 84604a4b82bcabc17e3e2689c4c0830dc4aeda43..40a9c715a84ab544a5a849802528ced6aaf57680 100644
--- a/src/tap/log/TAPLog.java
+++ b/src/tap/log/TAPLog.java
@@ -21,53 +21,80 @@ package tap.log;
  */
 
 import tap.TAPExecutionReport;
+import tap.TAPSyncJob;
 import tap.db.DBConnection;
-import tap.metadata.TAPMetadata;
-import tap.metadata.TAPTable;
+import tap.parameters.TAPParameters;
 import uws.service.log.UWSLog;
 
 /**
- * Lets logging any kind of message about a TAP service.
+ * Let log any kind of message about a TAP service.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  */
 public interface TAPLog extends UWSLog {
 
-	public void queryFinished(final TAPExecutionReport report);
-
-	public void dbInfo(final String message);
-
-	public void dbError(final String message, final Throwable t);
-
-	public void tapMetadataFetched(final TAPMetadata metadata);
-
-	public void tapMetadataLoaded(final TAPMetadata metadata);
-
-	public void connectionOpened(final DBConnection connection, final String dbName);
-
-	public void connectionClosed(final DBConnection connection);
-
-	public void transactionStarted(final DBConnection connection);
-
-	public void transactionCancelled(final DBConnection connection);
-
-	public void transactionEnded(final DBConnection connection);
-
-	public void schemaCreated(final DBConnection connection, final String schema);
-
-	public void schemaDropped(final DBConnection connection, final String schema);
-
-	public void tableCreated(final DBConnection connection, final TAPTable table);
-
-	public void tableDropped(final DBConnection connection, final TAPTable table);
-
-	public void rowsInserted(final DBConnection connection, final TAPTable table, final int nbInsertedRows);
-
-	public void sqlQueryExecuting(final DBConnection connection, final String sql);
-
-	public void sqlQueryError(final DBConnection connection, final String sql, final Throwable t);
-
-	public void sqlQueryExecuted(final DBConnection connection, final String sql);
+	/**
+	 * <p>Log a message and/or an error in the DB (database) context.</p>
+	 * 
+	 * <p>List of all events sent by the library (case sensitive):</p>
+	 * <ul>
+	 * 	<li>TRANSLATE</li>
+	 * 	<li>EXECUTE</li>
+	 * 	<li>RESULT</li>
+	 * 	<li>LOAD_TAP_SCHEMA</li>
+	 * 	<li>CLEAN_TAP_SCHEMA</li>
+	 * 	<li>CREATE_TAP_SCHEMA</li>
+	 * 	<li>TABLE_EXIST</li>
+	 * 	<li>EXEC_UPDATE</li>
+	 * 	<li>ADD_UPLOAD_TABLE</li>
+	 * 	<li>DROP_UPLOAD_TABLE</li>
+	 * 	<li>START_TRANSACTION</li>
+	 * 	<li>COMMIT</li>
+	 * 	<li>ROLLBACK</li>
+	 * 	<li>END_TRANSACTION</li>
+	 * 	<li>CLOSE</li>
+	 * </ul>
+	 * 
+	 * @param level			Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param connection	DB connection from which this log comes. <i>MAY be NULL</i>
+	 * @param event			Event at the origin of this log or action executed by the given database connection while this log is sent. <i>MAY be NULL</i>
+	 * @param message		Message to log. <i>MAY be NULL</i>
+	 * @param error			Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 2.0
+	 */
+	public void logDB(final LogLevel level, final DBConnection connection, final String event, final String message, final Throwable error);
+
+	/**
+	 * <p>Log a message and/or an error in the general context of TAP.</p>
+	 * 
+	 * <p>
+	 * 	One of the parameter is of type {@link Object}. This object can be used to provide more information to the log function
+	 * 	in order to describe as much as possible the state and/or result event.
+	 * </p>
+	 * 
+	 * <p>List of all events sent by the library (case sensitive):</p>
+	 * <ul>
+	 * 	<li>SYNC_INIT (with "obj" as an instance of {@link TAPParameters})</li>
+	 * 	<li>ASYNC_INIT (with a NULL "obj")</li>
+	 * 	<li>SYNC_START (with "obj" as an instance of {@link TAPSyncJob})</li>
+	 * 	<li>UPLOAD (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>FORMAT (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>START_STEP (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>END_EXEC (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>END_QUERY (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>DROP_UPLOAD (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * </ul>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param obj		Object providing more information about the event/object at the origin of this log. <i>MAY be NULL</i>
+	 * @param event		Event at the origin of this log or action currently executed by TAP while this log is sent. <i>MAY be NULL</i>
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 2.0
+	 */
+	public void logTAP(final LogLevel level, final Object obj, final String event, final String message, final Throwable error);
 
 }
diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java
index 3aa3898faccfdce6a1612217ef3b9734a0b0909c..a000ea1e9fb2f056acecdfd23e8778d3d16a0026 100644
--- a/src/tap/metadata/TAPMetadata.java
+++ b/src/tap/metadata/TAPMetadata.java
@@ -60,7 +60,7 @@ import adql.db.DBTable;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  */
 public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource {
 
@@ -204,6 +204,64 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 			return schemas.get(schemaName);
 	}
 
+	/**
+	 * <p>Get the TAPSchema describing the TAP_UPLOAD schema.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function is equivalent to {@link #getSchema(String)} with {@link STDSchema#UPLOADSCHEMA} in parameter.
+	 * 	It should be reminded that the research done with {@link #getSchema(String)} is case sensitive.
+	 * </i></p>
+	 * 
+	 * @return	The description of the TAP_UPLOAD schema,
+	 *        	or NULL if none is defined here.
+	 * 
+	 * @since 2.0
+	 */
+	public final TAPSchema getUploadSchema(){
+		return getSchema(STDSchema.UPLOADSCHEMA.label);
+	}
+
+	/**
+	 * <p>Add/Update the description of the TAP_UPLOAD schema.</p>
+	 * 
+	 * <p><i><b>Important note:<b>
+	 * 	The addition/update won't be effective if the ADQL name of the given {@link TAPSchema} is not exactly equals to {@link STDSchema#UPLOADSCHEMA}.
+	 * </i></p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function is equivalent to {@link #addSchema(TAPSchema)} with the given {@link TAPSchema} object in parameter.
+	 * </i></p>
+	 * 
+	 * @param uploadSchema	{@link TAPSchema} object describing the TAP_UPLOAD schema.
+	 * 
+	 * @return	<i>true</i> if the TAP_UPLOAD schema has been successfully added/updated,
+	 *        	<i>false</i> otherwise (and particularly if the given {@link TAPSchema} is NULL).
+	 * 
+	 * @since 2.0
+	 */
+	public final boolean setUploadSchema(final TAPSchema uploadSchema){
+		if (uploadSchema != null && uploadSchema.getADQLName().equals(STDSchema.UPLOADSCHEMA.label)){
+			addSchema(uploadSchema);
+			return true;
+		}else
+			return false;
+	}
+
+	/**
+	 * <p>Add a default definition of the TAP_UPLOAD schema, and return it.</p>
+	 * 
+	 * <p>The ADQL name and the DB name will be the same, that's to say: {@link STDSchema#UPLOADSCHEMA} (TAP_UPLOAD).</p>
+	 * 
+	 * @return	The default upload schema added to this {@link TAPMetadata} object.
+	 * 
+	 * @since 2.0
+	 */
+	public TAPSchema setDefaultUploadSchema(){
+		TAPSchema uploadSchema = new TAPSchema(STDSchema.UPLOADSCHEMA.label, "Schema for tables uploaded by users.");
+		addSchema(uploadSchema);
+		return uploadSchema;
+	}
+
 	/**
 	 * Get the number of schemas contained in this TAP metadata set.
 	 * 
@@ -450,7 +508,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	public void destroy(){}
 
 	@Override
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
+	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws IOException{
 		response.setContentType("application/xml");
 
 		PrintWriter writer = response.getWriter();
diff --git a/src/tap/parameters/FormatController.java b/src/tap/parameters/FormatController.java
index 526536fece92570323fcc60e959e2450d8120a16..4e8d37a5c16c5dc8b8d78a84b698c70ad432b2c7 100644
--- a/src/tap/parameters/FormatController.java
+++ b/src/tap/parameters/FormatController.java
@@ -16,7 +16,8 @@ package tap.parameters;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Iterator;
@@ -25,14 +26,32 @@ import tap.ServiceConnection;
 import tap.TAPJob;
 import tap.formatter.OutputFormat;
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.parameters.InputParamController;
 
+/**
+ * <p>Let controlling the format of all job result in a TAP service.
+ * The default values are provided by the service connection.</p>
+ * 
+ * <p><i>Note:
+ * 	By default, the format can be modified by anyone without any limitation.
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class FormatController implements InputParamController {
 
+	/** Connection to the service which knows the maximum and default value of this parameter. */
 	protected final ServiceConnection service;
+
+	/** Indicates whether the output limit of jobs can be modified. */
 	protected boolean allowModification = true;
 
+	/**
+	 * Build a controller for the Format parameter.
+	 * 
+	 * @param service	Connection to the TAP service.
+	 */
 	public FormatController(final ServiceConnection service){
 		this.service = service;
 	}
@@ -42,6 +61,11 @@ public class FormatController implements InputParamController {
 		return allowModification;
 	}
 
+	/**
+	 * Lets indicating whether the format parameter can be modified.
+	 * 
+	 * @param allowModification	<i>true</i> if the format can be modified, <i>false</i> otherwise.
+	 */
 	public final void allowModification(final boolean allowModif){
 		this.allowModification = allowModif;
 	}
@@ -62,16 +86,22 @@ public class FormatController implements InputParamController {
 				return getDefault();
 
 			if (service.getOutputFormat(strFormat) == null)
-				throw new UWSException(UWSException.BAD_REQUEST, "Unknown output format (=" + strFormat + ") ! This TAP service can format query results ONLY in the following formats:" + getAllowedFormats() + ".");
+				throw new UWSException(UWSException.BAD_REQUEST, "Unknown value for the job parameter format: \"" + strFormat + "\". It should be " + getAllowedFormats());
 			else
 				return strFormat;
 		}else
-			throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_FORMAT, format.toString(), format.getClass().getName(), "A String equals to one of the following values: " + getAllowedFormats() + ".");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the format parameter: class \"" + format.getClass().getName() + "\"! It should be a String.");
 	}
 
-	public final String getAllowedFormats(){
+	/**
+	 * Get a list of all allowed output formats (for each, the main MIME type
+	 * but also the short type representation are given).
+	 * 
+	 * @return	List of all output formats.
+	 */
+	protected final String getAllowedFormats(){
 		Iterator<OutputFormat> itFormats = service.getOutputFormats();
-		StringBuffer allowedFormats = new StringBuffer();
+		StringBuffer allowedFormats = new StringBuffer("a String value among: ");
 		int i = 0;
 		OutputFormat formatter;
 		while(itFormats.hasNext()){
@@ -81,7 +111,10 @@ public class FormatController implements InputParamController {
 				allowedFormats.append(" (or ").append(formatter.getShortMimeType()).append(')');
 			i++;
 		}
-		return allowedFormats.toString();
+		if (i > 0)
+			return allowedFormats.toString();
+		else
+			return "a String value.";
 	}
 
 }
diff --git a/src/tap/parameters/MaxRecController.java b/src/tap/parameters/MaxRecController.java
index 3dabe0ee58c12b2c99cfe8e4beef7934708ff50f..0fc665587a3364894a7d284f7165f892e0c17390 100644
--- a/src/tap/parameters/MaxRecController.java
+++ b/src/tap/parameters/MaxRecController.java
@@ -24,29 +24,41 @@ import tap.ServiceConnection;
 import tap.ServiceConnection.LimitUnit;
 import tap.TAPJob;
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.parameters.InputParamController;
 
 /**
- * The logic of the output limit is set in this class. Here it is:
+ * <p>Let controlling the maximum number of rows that can be output by a TAP service.
+ * The maximum and default values are provided by the service connection.</p>
  * 
- *  - If no value is specified by the TAP client, none is returned.
- *  - If no default value is provided, no default limitation is set (={@link TAPJob#UNLIMITED_MAX_REC}).
- *  - If no maximum value is provided, there is no output limit (={@link TAPJob#UNLIMITED_MAX_REC}).
+ * <p><i>Note:
+ * 	By default, this parameter can be modified by anyone without any limitation.
+ * </i></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de
- * @version 1.1 (03/2014)
+ * <p>The logic of the output limit is set in this class. Here it is:</p>
+ * <ul>
+ * 	<li>If no value is specified by the TAP client, none is returned.</li>
+ *  <li>If no default value is provided, no default limitation is set (={@link TAPJob#UNLIMITED_MAX_REC}).</li>
+ *  <li>If no maximum value is provided, there is no output limit (={@link TAPJob#UNLIMITED_MAX_REC}).</li>
+ * </ul>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
  */
 public class MaxRecController implements InputParamController {
 
+	/** Connection to the service which knows the maximum and default value of this parameter. */
 	protected final ServiceConnection service;
 
 	/** Indicates whether the output limit of jobs can be modified. */
 	protected boolean allowModification = true;
 
+	/**
+	 * Build a controller for the MaxRec parameter.
+	 * 
+	 * @param service	Connection to the TAP service.
+	 */
 	public MaxRecController(final ServiceConnection service){
 		this.service = service;
-		allowModification(allowModification);
 	}
 
 	@Override
@@ -60,6 +72,11 @@ public class MaxRecController implements InputParamController {
 		return TAPJob.UNLIMITED_MAX_REC;
 	}
 
+	/**
+	 * Get the maximum number of rows that can be output.
+	 * 
+	 * @return	Maximum output limit.
+	 */
 	public final int getMaxOutputLimit(){
 		// If a maximum output limit is set by the TAP service connection, return it:
 		if (service.getOutputLimit() != null && service.getOutputLimit().length >= 2 && service.getOutputLimitType() != null && service.getOutputLimitType().length == service.getOutputLimit().length){
@@ -86,10 +103,10 @@ public class MaxRecController implements InputParamController {
 			try{
 				maxRec = Integer.parseInt(strValue);
 			}catch(NumberFormatException nfe){
-				throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_MAX_REC, strValue, null, "An integer value between " + TAPJob.UNLIMITED_MAX_REC + " and " + maxOutputLimit + " (Default value: " + defaultOutputLimit + ").");
+				throw new UWSException(UWSException.BAD_REQUEST, "Wrong format for the MaxRec parameter: \"" + strValue + "\"! It should be a integer value between " + TAPJob.UNLIMITED_MAX_REC + " and " + maxOutputLimit + " (Default value: " + defaultOutputLimit + ").");
 			}
 		}else
-			throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_MAX_REC, null, value.getClass().getName(), "An integer value between " + TAPJob.UNLIMITED_MAX_REC + " and " + maxOutputLimit + " (Default value: " + defaultOutputLimit + ").");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the MaxRec parameter: class \"" + value.getClass().getName() + "\"! It should be an integer or a string containing only an integer value.");
 
 		// A negative output limit is considered as an unlimited output limit:
 		if (maxRec < TAPJob.UNLIMITED_MAX_REC)
diff --git a/src/tap/parameters/TAPDestructionTimeController.java b/src/tap/parameters/TAPDestructionTimeController.java
index a3c0ca644fdd73c622c704525b9779adc12cabbd..1087aa84ad28f70eef8941569c69543021d57168 100644
--- a/src/tap/parameters/TAPDestructionTimeController.java
+++ b/src/tap/parameters/TAPDestructionTimeController.java
@@ -16,7 +16,8 @@ package tap.parameters;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.text.ParseException;
@@ -24,18 +25,36 @@ import java.util.Calendar;
 import java.util.Date;
 
 import tap.ServiceConnection;
-import tap.TAPJob;
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.UWSJob;
 import uws.job.parameters.DestructionTimeController.DateField;
 import uws.job.parameters.InputParamController;
 
+/**
+ * <p>Let controlling the destruction time of all jobs managed by a TAP service.
+ * The maximum and default values are provided by the service connection.</p>
+ * 
+ * <p><i>Note:
+ * 	By default, the destruction time can be modified by anyone without any limitation.
+ * 	There is no default value (that means jobs may stay forever).
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class TAPDestructionTimeController implements InputParamController {
 
+	/** Connection to the service which knows the maximum and default value of this parameter. */
 	protected final ServiceConnection service;
+
+	/** Indicates whether the execution duration of jobs can be modified. */
 	protected boolean allowModification = true;
 
+	/**
+	 * Build a controller for the Destruction parameter.
+	 * 
+	 * @param service	Connection to the TAP service.
+	 */
 	public TAPDestructionTimeController(final ServiceConnection service){
 		this.service = service;
 	}
@@ -45,10 +64,21 @@ public class TAPDestructionTimeController implements InputParamController {
 		return allowModification;
 	}
 
+	/**
+	 * Let indicate whether the destruction time of any managed job can be modified.
+	 * 
+	 * @param allowModification <i>true</i> if the destruction time can be modified, <i>false</i> otherwise.
+	 */
 	public final void allowModification(final boolean allowModif){
 		allowModification = allowModif;
 	}
 
+	/**
+	 * Get the default period during which a job is kept.
+	 * After this period, the job should be destroyed.
+	 * 
+	 * @return	The default retention period, -1 if none is provided.
+	 */
 	public final int getDefaultRetentionPeriod(){
 		if (service.getRetentionPeriod() != null && service.getRetentionPeriod().length >= 2){
 			if (service.getRetentionPeriod()[0] > 0)
@@ -72,6 +102,12 @@ public class TAPDestructionTimeController implements InputParamController {
 			return null;
 	}
 
+	/**
+	 * Get the maximum period during which a job is kept.
+	 * After this period, the job should be destroyed.
+	 * 
+	 * @return	The maximum retention period, -1 if none is provided.
+	 */
 	public final int getMaxRetentionPeriod(){
 		if (service.getRetentionPeriod() != null && service.getRetentionPeriod().length >= 2){
 			if (service.getRetentionPeriod()[1] > 0)
@@ -80,6 +116,11 @@ public class TAPDestructionTimeController implements InputParamController {
 		return -1;
 	}
 
+	/**
+	 * Gets the maximum destruction time: either computed with an interval of time or obtained directly by a maximum destruction time.
+	 * 
+	 * @return The maximum destruction time (<i>null</i> means that jobs may stay forever).
+	 */
 	public final Date getMaxDestructionTime(){
 		int maxPeriod = getMaxRetentionPeriod();
 		if (maxPeriod > 0){
@@ -107,10 +148,10 @@ public class TAPDestructionTimeController implements InputParamController {
 			try{
 				date = UWSJob.dateFormat.parse(strValue);
 			}catch(ParseException pe){
-				throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_DESTRUCTION_TIME, strValue, null, "A date not yet expired.");
+				throw new UWSException(UWSException.BAD_REQUEST, pe, "Wrong date format for the destruction time parameter: \"" + strValue + "\"! The format to respect is: " + UWSJob.DEFAULT_DATE_FORMAT);
 			}
 		}else
-			throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_DESTRUCTION_TIME, value.toString(), value.getClass().getName(), "A date not yet expired.");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the destruction time parameter: class \"" + value.getClass().getName() + "\"! It should be a Date or a string containing a date with the format \"" + UWSJob.DEFAULT_DATE_FORMAT + "\".");
 
 		Date maxDate = getMaxDestructionTime();
 		if (maxDate != null && date.after(maxDate))
diff --git a/src/tap/parameters/TAPExecutionDurationController.java b/src/tap/parameters/TAPExecutionDurationController.java
index 2187beda9b0a7a9fba3a13413d86a378a9d68b28..85922cd158814f7e89542e97401e2e608d1c6d97 100644
--- a/src/tap/parameters/TAPExecutionDurationController.java
+++ b/src/tap/parameters/TAPExecutionDurationController.java
@@ -16,20 +16,40 @@ package tap.parameters;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import tap.ServiceConnection;
 import tap.TAPJob;
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.parameters.InputParamController;
 
+/**
+ * <p>Let controlling the execution duration of all jobs managed by a TAP service.
+ * The maximum and default values are provided by the service connection.</p>
+ * 
+ * <p><i>Note:
+ * 	By default, the execution duration can be modified by anyone without any limitation.
+ * 	The default value is {@link TAPJob#UNLIMITED_DURATION}.
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class TAPExecutionDurationController implements InputParamController {
 
+	/** Connection to the service which knows the maximum and default value of this parameter. */
 	protected final ServiceConnection service;
+
+	/** Indicate whether the execution duration of jobs can be modified. */
 	protected boolean allowModification = true;
 
+	/**
+	 * Build a controller for the ExecutionDuration parameter.
+	 * 
+	 * @param service	Connection to the TAP service.
+	 */
 	public TAPExecutionDurationController(final ServiceConnection service){
 		this.service = service;
 	}
@@ -39,6 +59,11 @@ public class TAPExecutionDurationController implements InputParamController {
 		return allowModification;
 	}
 
+	/**
+	 * Let indicate whether the execution duration of any managed job can be modified.
+	 * 
+	 * @param allowModification <i>true</i> if the execution duration can be modified, <i>false</i> otherwise.
+	 */
 	public final void allowModification(final boolean allowModif){
 		allowModification = allowModif;
 	}
@@ -52,6 +77,11 @@ public class TAPExecutionDurationController implements InputParamController {
 		return TAPJob.UNLIMITED_DURATION;
 	}
 
+	/**
+	 * Gets the maximum execution duration.
+	 * 
+	 * @return The maximum execution duration <i>(0 or less mean an unlimited duration)</i>.
+	 */
 	public final long getMaxDuration(){
 		if (service.getExecutionDuration() != null && service.getExecutionDuration().length >= 2){
 			if (service.getExecutionDuration()[1] > 0)
@@ -74,10 +104,10 @@ public class TAPExecutionDurationController implements InputParamController {
 			try{
 				duration = Long.parseLong((String)value);
 			}catch(NumberFormatException nfe){
-				throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_EXECUTION_DURATION, value.toString(), null, "A long value between " + TAPJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
+				throw new UWSException(UWSException.BAD_REQUEST, "Wrong format for the maximum duration parameter: \"" + value.toString() + "\"! It should be a long numeric value between " + TAPJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
 			}
 		}else
-			throw UWSExceptionFactory.badFormat(null, TAPJob.PARAM_EXECUTION_DURATION, null, value.getClass().getName(), "A long value between " + TAPJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the maximum duration parameter: class \"" + value.getClass().getName() + "\"! It should be long or a string containing only a long value.");
 
 		if (duration < TAPJob.UNLIMITED_DURATION)
 			duration = TAPJob.UNLIMITED_DURATION;
diff --git a/src/tap/parameters/TAPParameters.java b/src/tap/parameters/TAPParameters.java
index 045e5af582f12212099e5dba283791b521646dc1..d24d2034ef2f01862abce503c187e63bd1e95041 100644
--- a/src/tap/parameters/TAPParameters.java
+++ b/src/tap/parameters/TAPParameters.java
@@ -60,9 +60,8 @@ public class TAPParameters extends UWSParameters {
 	/** All the tables to upload. If NULL, there is no tables to upload. */
 	protected TableLoader[] tablesToUpload = null;
 
-	@SuppressWarnings({"unchecked"})
 	public TAPParameters(final ServiceConnection service){
-		this(service, (Collection)null, null);
+		this(service, null, null);
 	}
 
 	public TAPParameters(final ServiceConnection service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers){
diff --git a/src/tap/resource/ASync.java b/src/tap/resource/ASync.java
index 6d8746ac0836439ecd0817dadd1c1ad6bb3c1b7b..5fe3c9cb3c4e9d2a4e6172135c70488a79c483a5 100644
--- a/src/tap/resource/ASync.java
+++ b/src/tap/resource/ASync.java
@@ -16,7 +16,8 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -37,16 +38,65 @@ import uws.job.manager.QueuedExecutionManager;
 import uws.service.UWSService;
 import uws.service.backup.UWSBackupManager;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
+/**
+ * <p>Asynchronous resource of a TAP service.</p>
+ * 
+ * <p>
+ * 	Requests sent to this resource are ADQL queries (plus some execution parameters) to execute asynchronously.
+ * 	Results and/or errors of the execution are stored on the server side and can be fetched by the user whenever he wants.
+ * </p>
+ * 
+ * <p>
+ * 	This resource is actually another VO service: a UWS (Universal Worker Service pattern).
+ * 	That's why all requests sent to this resource are actually forwarded to an instance of {@link UWSService}.
+ * 	All the behavior of UWS described by the IVOA is already fully implemented by this implementation.
+ * </p>
+ * 
+ * <p>This resource is also representing the only jobs' list of this UWS service.</p>
+ * 
+ * <p>The UWS service is created and configured at the creation of this resource. Here are the list of the most important configured elements:</p>
+ * <ul>
+ * 	<li><b>User identification:</b> the user identifier is the same as the one used by the TAP service. It is provided by the given {@link ServiceConnection}.</li>
+ * 	<li><b>Jobs' lists:</b> the /async resource of TAP contains only one jobs' list. Its name is "async" and is accessed directly when requesting the /async resource.</li>
+ * 	<li><b>Job execution management:</b> an execution manager is created at the creation of this resource. It is queuing jobs when a maximum number of asynchronous jobs
+ * 	                                     is already running. This maximum is provided by the TAP service description: {@link ServiceConnection#getNbMaxAsyncJobs()}. Jobs are also queued if no more DB
+ * 	                                     connection is available ; when connection(s) will be available, this resource will be notified by {@link #freeConnectionAvailable()} so that the execution manager
+ * 	                                     can be refreshed.</li>
+ * 	<li><b>Backup and Restoration:</b> UWS jobs can be saved at any defined moment. It is particularly useful when an grave error occurs and merely when the service must be restarted.
+ * 	                                   Then, at the creation of this resource, the jobs are restored. Thus, the restart has been transparent for the users: they did not lose any job
+ * 	                                   (except those at the origin of the grave error maybe).</li>
+ * 	<li><b>Error logging:</b> the created {@link UWSService} instance is using the same logger as the TAP service. It is also provided by the given {@link ServiceConnection} object at creation.</li>
+ * </ul>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ * 
+ * @see UWSService
+ */
 public class ASync implements TAPResource {
 
+	/** Name of this TAP resource. */
 	public static final String RESOURCE_NAME = "async";
 
+	/** Description of the TAP service owning this resource. */
 	protected final ServiceConnection service;
+	/** UWS service represented by this TAP resource. */
 	protected final UWSService uws;
+	/** The only jobs' list managed by the inner UWS service. This resource represent the UWS but also this jobs' list. */
 	protected final JobList jobList;
 
-	public ASync(ServiceConnection service) throws UWSException, TAPException{
+	/**
+	 * Build an Asynchronous Resource of a TAP service.
+	 * 
+	 * @param service	Description of the TAP service which will own this resource.
+	 * 
+	 * @throws TAPException	If any error occurs while creating a UWS service or its backup manager.
+	 * @throws UWSException	If any error occurs while setting a new execution manager to the recent inner UWS service,
+	 *                     	or while restoring a UWS backup.
+	 */
+	public ASync(final ServiceConnection service) throws UWSException, TAPException{
 		this.service = service;
 
 		uws = service.getFactory().createUWS();
@@ -57,8 +107,7 @@ public class ASync implements TAPResource {
 		if (uws.getJobList(getName()) == null){
 			jobList = new JobList(getName());
 			uws.addJobList(jobList);
-			if (service.getNbMaxAsyncJobs() > 0)
-				jobList.setExecutionManager(new AsyncExecutionManager(service.getFactory(), service.getLogger(), service.getNbMaxAsyncJobs()));
+			jobList.setExecutionManager(new AsyncExecutionManager(service.getFactory(), service.getLogger(), service.getNbMaxAsyncJobs()));
 		}else
 			jobList = uws.getJobList(getName());
 
@@ -71,30 +120,33 @@ public class ASync implements TAPResource {
 			int[] report = uws.getBackupManager().restoreAll();
 			String errorMsg = null;
 			if (report == null || report.length == 0)
-				errorMsg = "GRAVE error while the restoration of the asynchronous jobs !";
+				errorMsg = "GRAVE error while the restoration of the asynchronous jobs!";
 			else if (report.length < 4)
-				errorMsg = "Incorrect restoration report format ! => Impossible to know the restoration status !";
+				errorMsg = "Incorrect restoration report format! => Impossible to know the restoration status!";
 			else if (report[0] != report[1])
-				errorMsg = "FAILED restoration of the asynchronous jobs: " + report[0] + " on " + report[1] + " restored !";
+				errorMsg = "FAILED restoration of the asynchronous jobs: " + report[0] + " on " + report[1] + " restored!";
 			else
 				backupManager.setEnabled(true);
 
 			if (errorMsg != null){
 				errorMsg += " => Backup disabled.";
-				service.getLogger().error(errorMsg);
+				service.getLogger().logTAP(LogLevel.FATAL, null, "ASYNC_INIT", errorMsg, null);
 				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, errorMsg);
 			}
 		}
 	}
 
+	/**
+	 * <p>Notify this TAP resource that free DB connection(s) is(are) now available.
+	 * It means that the execution manager should be refreshed in order to execute one or more queued jobs.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function has no effect if there is no execution manager.
+	 * </i></p>
+	 */
 	public void freeConnectionAvailable(){
-		if (jobList.getExecutionManager() != null){
-			try{
-				jobList.getExecutionManager().refresh();
-			}catch(UWSException e){
-				service.getLogger().warning("Can not refresh the ASYNC queue! (CAUSE: " + e.getMessage() + ")");
-			}
-		}
+		if (jobList.getExecutionManager() != null)
+			jobList.getExecutionManager().refresh();
 	}
 
 	@Override
@@ -103,16 +155,21 @@ public class ASync implements TAPResource {
 	}
 
 	@Override
-	public void setTAPBaseURL(String baseURL){
+	public void setTAPBaseURL(final String baseURL){
 		;
 	}
 
+	/**
+	 * Get the UWS behind this TAP resource.
+	 * 
+	 * @return	The inner UWS used by this TAP resource.
+	 */
 	public final UWSService getUWS(){
 		return uws;
 	}
 
 	@Override
-	public void init(ServletConfig config) throws ServletException{
+	public void init(final ServletConfig config) throws ServletException{
 		;
 	}
 
@@ -122,17 +179,37 @@ public class ASync implements TAPResource {
 	}
 
 	@Override
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, TAPException, UWSException{
-		return uws.executeRequest(request, response);
+	public boolean executeResource(final HttpServletRequest request, final HttpServletResponse response) throws IOException, TAPException{
+		try{
+			return uws.executeRequest(request, response);
+		}catch(UWSException ue){
+			throw new TAPException(ue);
+		}
 	}
 
+	/**
+	 * An execution manager which queues jobs when too many asynchronous jobs are running or
+	 * when no more DB connection is available for the moment.
+	 * 
+	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
+	 * @version 2.0 (09/2014)
+	 * @since 2.0
+	 */
 	private class AsyncExecutionManager extends AbstractQueuedExecutionManager {
 
+		/** A factory of TAP objects. */
 		private final TAPFactory factory;
 
 		/** The maximum number of running jobs. */
 		protected int nbMaxRunningJobs = QueuedExecutionManager.NO_QUEUE;
 
+		/**
+		 * Build a queuing execution manager.
+		 * 
+		 * @param factory			Factory of TAP objects.
+		 * @param logger			Logger to use.
+		 * @param maxRunningJobs	Maximum number of asynchronous jobs that can run in the same time.
+		 */
 		public AsyncExecutionManager(final TAPFactory factory, UWSLog logger, int maxRunningJobs){
 			super(logger);
 			this.factory = factory;
@@ -140,7 +217,7 @@ public class ASync implements TAPResource {
 		}
 
 		@Override
-		public boolean isReadyForExecution(UWSJob jobToExecute){
+		public boolean isReadyForExecution(final UWSJob jobToExecute){
 			if (!hasQueue())
 				return factory.countFreeConnections() >= 1;
 			else
diff --git a/src/tap/resource/Availability.java b/src/tap/resource/Availability.java
index 1da467efa05d65b20ffc94cac44e33a7181d1f42..fc10f97252ef4a2a29e3fb626e3dfd33b4dc8f42 100644
--- a/src/tap/resource/Availability.java
+++ b/src/tap/resource/Availability.java
@@ -16,7 +16,8 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -28,24 +29,41 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import tap.ServiceConnection;
+import tap.TAPException;
+import uk.ac.starlink.votable.VOSerializer;
 
+/**
+ * <p>TAP resource describing the availability of a TAP service.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class Availability implements TAPResource, VOSIResource {
 
+	/** Name of this TAP resource. */
 	public static final String RESOURCE_NAME = "availability";
 
-	private final ServiceConnection service;
+	/** Description of the TAP service owning this resource. */
+	protected final ServiceConnection service;
+
+	/** <p>URL toward this TAP resource.
+	 * This URL is particularly important for its declaration in the capabilities of the TAP service.</p>
+	 * 
+	 * <p><i>Note: By default, it is just the name of this resource. It is updated after initialization of the service
+	 * when the TAP service base URL is communicated to its resources. Then, it is: baseTAPURL + "/" + RESOURCE_NAME.</i></p> */
 	protected String accessURL = getName();
 
-	protected Availability(ServiceConnection service){
+	/**
+	 * Build a "availability" resource.
+	 * 
+	 * @param service	Description of the TAP service which will own this resource.
+	 */
+	protected Availability(final ServiceConnection service){
 		this.service = service;
 	}
 
-	public ServiceConnection getService(){
-		return service;
-	}
-
 	@Override
-	public final void setTAPBaseURL(String baseURL){
+	public final void setTAPBaseURL(final String baseURL){
 		accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName();
 	}
 
@@ -70,7 +88,7 @@ public class Availability implements TAPResource, VOSIResource {
 	}
 
 	@Override
-	public void init(ServletConfig config) throws ServletException{
+	public void init(final ServletConfig config) throws ServletException{
 		;
 	}
 
@@ -80,19 +98,36 @@ public class Availability implements TAPResource, VOSIResource {
 	}
 
 	@Override
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
-		if (!request.getMethod().equalsIgnoreCase("GET"))	// ERREUR 405 selon VOSI (cf p.4)
-			response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "The AVAILABILITY resource is only accessible in HTTP-GET !");
-
+	public boolean executeResource(final HttpServletRequest request, final HttpServletResponse response) throws IOException, TAPException{
+		/* "In the REST binding, the support interfaces shall have distinct URLs in the HTTP scheme and shall be accessible by the GET operation in the HTTP protocol.
+		 * The response to an HTTP POST, PUT or DELETE to these resources is not defined by this specification. However, if an implementation has no special action
+		 * to perform for these requests, the normal response would be a 405 "Method not allowed" error."
+		 * (Extract of the VOSI definition: http://www.ivoa.net/documents/VOSI/20100311/PR-VOSI-1.0-20100311.html#sec2) */
+		if (!request.getMethod().equalsIgnoreCase("GET"))
+			throw new TAPException("The AVAILABILITY resource is only accessible in HTTP-GET! No special action can be perfomed with another HTTP method.", HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+
+		// Set the response MIME type (XML):
 		response.setContentType("text/xml");
 
-		String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
-		xml += "<availability xmlns=\"http://www.ivoa.net/xml/VOSIAvailability/v1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.ivoa.net/xml/VOSIAvailability/v1.0 http://www.ivoa.net/xml/VOSIAvailability/v1.0\">\n";
-		xml += "\t<available>" + service.isAvailable() + "</available>\n\t<note>" + service.getAvailability() + "</note>\n";
-		xml += "</availability>";
-
+		// Get the output stream:
 		PrintWriter pw = response.getWriter();
-		pw.print(xml);
+
+		// ...And write the XML document describing the availability of the TAP service:
+		pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+		pw.println("<availability xmlns=\"http://www.ivoa.net/xml/VOSIAvailability/v1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.ivoa.net/xml/VOSIAvailability/v1.0 http://www.ivoa.net/xml/VOSIAvailability/v1.0\">");
+
+		// available ? (true or false)
+		pw.print("\t<available>");
+		pw.print(service.isAvailable());
+		pw.println("</available>");
+
+		// reason/description of the (non-)availability:
+		pw.print("\t<note>");
+		pw.print(VOSerializer.formatText(service.getAvailability()));
+		pw.println("</note>");
+
+		pw.println("</availability>");
+
 		pw.flush();
 
 		return true;
diff --git a/src/tap/resource/Capabilities.java b/src/tap/resource/Capabilities.java
index 4cbdba0d562cbe9f7b7dc282c2edf1dd0967c4bf..8e548080f1e44b39636be399e7f284e4cd6e1515 100644
--- a/src/tap/resource/Capabilities.java
+++ b/src/tap/resource/Capabilities.java
@@ -16,7 +16,8 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -28,19 +29,42 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import tap.TAPException;
+import uk.ac.starlink.votable.VOSerializer;
+
+/**
+ * <p>TAP resource describing the capabilities of a TAP service.</p>
+ * 
+ * <p>This resource just return an XML document giving a description of the TAP service and list all its VOSI resources.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class Capabilities implements TAPResource, VOSIResource {
 
+	/** Name of this TAP resource. */
 	public static final String RESOURCE_NAME = "capabilities";
 
+	/** Representation of the whole TAP service. This object list all available resources ;
+	 * resources that correspond to the capabilities this resource must list. */
 	private final TAP tap;
+
+	/** <p>URL toward this TAP resource.
+	 * This URL is particularly important for its declaration in the capabilities of the TAP service.</p>
+	 * 
+	 * <p><i>Note: By default, it is just the name of this resource. It is updated after initialization of the service
+	 * when the TAP service base URL is communicated to its resources. Then, it is: baseTAPURL + "/" + RESOURCE_NAME.</i></p> */
 	protected String accessURL = getName();
 
-	public Capabilities(TAP tap){
+	/**
+	 * Build a "/capabilities" resource.
+	 * 
+	 * @param tap	Object representation of the whole TAP service.
+	 */
+	public Capabilities(final TAP tap){
 		this.tap = tap;
 	}
 
-	/**
-	 */
 	@Override
 	public final void setTAPBaseURL(String baseURL){
 		accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName();
@@ -68,52 +92,71 @@ public class Capabilities implements TAPResource, VOSIResource {
 
 	@Override
 	public void init(ServletConfig config) throws ServletException{
-
+		;
 	}
 
 	@Override
 	public void destroy(){
-		// TODO Auto-generated method stub
-
+		;
 	}
 
 	@Override
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
+	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws IOException, TAPException{
+		/* "In the REST binding, the support interfaces shall have distinct URLs in the HTTP scheme and shall be accessible by the GET operation in the HTTP protocol.
+		 * The response to an HTTP POST, PUT or DELETE to these resources is not defined by this specification. However, if an implementation has no special action
+		 * to perform for these requests, the normal response would be a 405 "Method not allowed" error."
+		 * (Extract of the VOSI definition: http://www.ivoa.net/documents/VOSI/20100311/PR-VOSI-1.0-20100311.html#sec2) */
+		if (!request.getMethod().equalsIgnoreCase("GET"))
+			throw new TAPException("The CAPABILITIES resource is only accessible in HTTP-GET! No special action can be perfomed with another HTTP method.", HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+
+		// Set the response MIME type (XML):
 		response.setContentType("application/xml");
 
-		StringBuffer xml = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-		xml.append("<vosi:capabilities xmlns:vosi=\"http://www.ivoa.net/xml/VOSICapabilities/v1.0\"");
-		xml.append(" xmlns:tr=\"http://www.ivoa.net/xml/TAP/v0.1\"");
-		xml.append(" xmlns:vr=\"http://www.ivoa.net/xml/VOResource/v1.0\"");
-		xml.append(" xmlns:vs=\"http://www.ivoa.net/xml/VODataService/v1.0\"");
-		xml.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
-		xml.append(" xsi:schemaLocation=\"http://www.ivoa.net/xml/TAP/v0.1 http://www.ivoa.net/xml/TAP/v0.1\">\n");
+		// Get the response stream:
+		PrintWriter out = response.getWriter();
+
+		// Write the XML document header:
+		out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+		out.print("<vosi:capabilities xmlns:vosi=\"http://www.ivoa.net/xml/VOSICapabilities/v1.0\"");
+		out.print(" xmlns:tr=\"http://www.ivoa.net/xml/TAP/v0.1\"");
+		out.print(" xmlns:vr=\"http://www.ivoa.net/xml/VOResource/v1.0\"");
+		out.print(" xmlns:vs=\"http://www.ivoa.net/xml/VODataService/v1.0\"");
+		out.print(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
+		out.println(" xsi:schemaLocation=\"http://www.ivoa.net/xml/TAP/v0.1 http://www.ivoa.net/xml/TAP/v0.1\">");
 
-		xml.append(tap.getCapability());
+		// Write the full list of this TAP capabilities:
+		out.print(tap.getCapability());
 
-		// Build the xml document:
+		// Write the capabilities of all VOSI resources:
 		Iterator<TAPResource> it = tap.getTAPResources();
 		while(it.hasNext()){
 			TAPResource res = it.next();
 			if (res instanceof VOSIResource){
 				String cap = ((VOSIResource)res).getCapability();
-				if (cap != null)
-					xml.append('\n').append(cap);
+				if (cap != null){
+					out.println();
+					out.print(cap);
+				}
 			}
 		}
 
-		xml.append("\n</vosi:capabilities>");
+		// Write the end of the XML document:
+		out.println("\n</vosi:capabilities>");
 
-		// Write the Capabilities resource into the ServletResponse:
-		PrintWriter out = response.getWriter();
-		out.print(xml.toString());
 		out.flush();
 
 		return true;
 	}
 
-	public static final String getDefaultCapability(VOSIResource res){
-		return "\t<capability standardID=\"" + res.getStandardID() + "\">\n" + "\t\t<interface xsi:type=\"vs:ParamHTTP\" role=\"std\">\n" + "\t\t\t<accessURL use=\"full\"> " + ((res.getAccessURL() == null) ? "" : res.getAccessURL()) + " </accessURL>\n" + "\t\t</interface>\n" + "\t</capability>";
+	/**
+	 * Write the XML description of the given VOSI resource.
+	 * 
+	 * @param res	Resource to describe in XML.
+	 * 
+	 * @return	XML description of the given VOSI resource.
+	 */
+	public static final String getDefaultCapability(final VOSIResource res){
+		return "\t<capability " + VOSerializer.formatAttribute("standardID", res.getStandardID()) + ">\n" + "\t\t<interface xsi:type=\"vs:ParamHTTP\" role=\"std\">\n" + "\t\t\t<accessURL use=\"full\"> " + ((res.getAccessURL() == null) ? "" : VOSerializer.formatText(res.getAccessURL())) + " </accessURL>\n" + "\t\t</interface>\n" + "\t</capability>";
 	}
 
 }
diff --git a/src/tap/resource/Sync.java b/src/tap/resource/Sync.java
index c3cf02232a25714a73a7163510df53351c9328db..460b0095caba1dd1e574dc4350beba7079fcc047 100644
--- a/src/tap/resource/Sync.java
+++ b/src/tap/resource/Sync.java
@@ -16,7 +16,8 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -32,18 +33,39 @@ import tap.TAPJob;
 import tap.TAPSyncJob;
 import tap.parameters.TAPParameters;
 import uws.UWSException;
+import uws.service.log.UWSLog.LogLevel;
 
+/**
+ * <p>Synchronous resource of a TAP service.</p>
+ * 
+ * <p>
+ * 	Requests sent to this resource can be either to get the capabilities of the TAP service (which should actually be accessed with the resource /capabilities)
+ * 	or to execute synchronously an ADQL query. For the second case, "synchronously" means that result or error is returned immediately when the execution ends.
+ * 	Besides, generally, the execution time is much more limited than an asynchronous query. 
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class Sync implements TAPResource {
 
+	/** Name of this TAP resource. */
 	public static final String RESOURCE_NAME = "sync";
 
-	protected String accessURL = null;
-
+	/** Description of the TAP service owning this resource. */
 	protected final ServiceConnection service;
 
+	/** List of all capabilities of the TAP service. */
 	protected final Capabilities capabilities;
 
-	public Sync(ServiceConnection service, Capabilities capabilities){
+	/**
+	 * Build a synchronous resource for the TAP service whose the description and
+	 * the capabilities are provided in parameters.
+	 * 
+	 * @param service		Description of the TAP service which will own this resource.
+	 * @param capabilities	Capabilities of the TAP service.
+	 */
+	public Sync(final ServiceConnection service, final Capabilities capabilities){
 		this.service = service;
 		this.capabilities = capabilities;
 	}
@@ -54,12 +76,12 @@ public class Sync implements TAPResource {
 	}
 
 	@Override
-	public void setTAPBaseURL(String baseURL){
-		accessURL = ((baseURL != null) ? (baseURL + "/") : "") + getName();
+	public void setTAPBaseURL(final String baseURL){
+		;
 	}
 
 	@Override
-	public void init(ServletConfig config) throws ServletException{
+	public void init(final ServletConfig config) throws ServletException{
 		;
 	}
 
@@ -69,32 +91,38 @@ public class Sync implements TAPResource {
 	}
 
 	@Override
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, TAPException, UWSException{
-		TAPParameters params = (TAPParameters)service.getFactory().createUWSParameters(request);
+	public boolean executeResource(final HttpServletRequest request, final HttpServletResponse response) throws IOException, TAPException{
+		// Retrieve the execution parameters:
+		TAPParameters params = service.getFactory().createTAPParameters(request);
 		params.check();
 
+		// CASE 1: GET CAPABILITIES
+		/* If the user asks for the capabilities through the TAP parameters, execute the corresponding resource. */
 		if (params.getRequest().equalsIgnoreCase(TAPJob.REQUEST_GET_CAPABILITIES))
 			return capabilities.executeResource(request, response);
 
+		// CASE 2: EXECUTE SYNCHRONOUSLY AN ADQL QUERY
 		// Ensure the service is currently available:
-		if (!service.isAvailable()){
-			response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, service.getAvailability());
-			return false;
-		}
+		if (!service.isAvailable())
+			throw new TAPException("Can not execute a query: this TAP service is not available! " + service.getAvailability(), UWSException.SERVICE_UNAVAILABLE);
 
 		/* Ensure that at least 1 DB connection is available for asynchronous queries.
-		 * If yes, just execute synchronously the given job:
-		 */
+		 * If yes, just execute synchronously the given job: */
 		if (service.getFactory().countFreeConnections() > 1){
-			TAPSyncJob syncJob = new TAPSyncJob(service, params);
-			syncJob.start(response);
-			return true;
+			try{
+				TAPSyncJob syncJob = new TAPSyncJob(service, params);
+				syncJob.start(response);
+				return true;
+			}catch(TAPException te){
+				throw te;
+			}catch(Throwable t){
+				service.getLogger().logTAP(LogLevel.FATAL, params, "SYNC_INIT", "Unexpected error while executing the given ADQL query!", t);
+				throw new TAPException("Unexpected error while executing the given ADQL query!");
+			}
 		}
 		// Otherwise, send an error:
-		else{
-			response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "TAP service too busy! No connection available for the moment. You should try later or create an asynchronous query (which will be executed when enough resources will be available again).");
-			return false;
-		}
+		else
+			throw new TAPException("TAP service too busy! No connection available for the moment. You should try later or create an asynchronous query (which will be executed when enough resources will be available again).", UWSException.SERVICE_UNAVAILABLE);
 
 	}
 
diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java
index acb2d98ef4a0cab141aa913971a4fac03c4da551..696d80adf939bf97c81b36f96571aa6720aebf32 100644
--- a/src/tap/resource/TAP.java
+++ b/src/tap/resource/TAP.java
@@ -27,9 +27,6 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -42,33 +39,61 @@ import javax.servlet.http.HttpServletResponse;
 import tap.ServiceConnection;
 import tap.ServiceConnection.LimitUnit;
 import tap.TAPException;
-import tap.db.DBConnection;
 import tap.error.DefaultTAPErrorWriter;
 import tap.formatter.OutputFormat;
 import tap.log.TAPLog;
 import tap.metadata.TAPMetadata;
+import uk.ac.starlink.votable.VOSerializer;
 import uws.UWSException;
-import uws.job.ErrorType;
-import uws.job.UWSJob;
 import uws.job.user.JobOwner;
 import uws.service.UWSService;
 import uws.service.UWSUrl;
 import uws.service.error.ServiceErrorWriter;
+import uws.service.log.UWSLog.LogLevel;
 
+/**
+ * <p>Root/Home of the TAP service. It is also the resource (HOME) which gathers all the others of the same TAP service.</p>
+ * 
+ * <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public class TAP implements VOSIResource {
 
-	private static final long serialVersionUID = 1L;
-
+	/** Description of the TAP service owning this resource. */
 	protected final ServiceConnection service;
 
+	/** List of all the other TAP resources of the service. */
 	protected final Map<String,TAPResource> resources;
 
+	/** Base URL of the TAP service. It is also the URL of this resource (HOME). */
 	protected String tapBaseURL = null;
 
+	/** URI of the page or path of the file to display when this resource is requested. */
 	protected String homePageURI = null;
 
+	/** MIME type of the custom home page. By default, it is "text/html". */
+	protected String homePageMimeType = "text/html";
+
+	/** Object to use when an error occurs or comes until this resource from the others.
+	 * This object fills the HTTP response in the most appropriate way in function of the error. */
 	protected ServiceErrorWriter errorWriter;
 
+	/** Last generated request ID. If the next generated request ID is equivalent to this one,
+	 * a new one will generate in order to ensure the uniqueness.
+	 * @since 2.0 */
+	protected static String lastRequestID = null;
+
+	/**
+	 * Build a HOME resource of a TAP service whose the description is given in parameter.
+	 * All the other TAP resources will be created and configured here thanks to the given {@link ServiceConnection}. 
+	 * 
+	 * @param serviceConnection	Description of the TAP service.
+	 * 
+	 * @throws UWSException	If an error occurs while creating the /async resource.
+	 * @throws TAPException	If any other error occurs.
+	 */
 	public TAP(ServiceConnection serviceConnection) throws UWSException, TAPException{
 		service = serviceConnection;
 		resources = new HashMap<String,TAPResource>();
@@ -88,67 +113,283 @@ public class TAP implements VOSIResource {
 		resources.put(res.getName(), res);
 		getUWS().setErrorWriter(errorWriter);
 
-		if (service.uploadEnabled()){
-			DBConnection dbConn = null;
-			try{
-				dbConn = service.getFactory().getConnection("TAP(ServiceConnection)");
-				// TODO CLEAN ACTION: DROP SCHEMA!
-				/*dbConn.dropSchema("TAP_UPLOAD");
-				dbConn.createSchema("TAP_UPLOAD");*/
-			}catch(TAPException e){
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Error while creating the schema TAP_UPLOAD !");
-			}finally{
-				if (dbConn != null)
-					service.getFactory().freeConnection(dbConn);
-			}
-		}
-
-		updateTAPMetadata();
+		TAPMetadata metadata = service.getTAPMetadata();
+		if (metadata != null)
+			resources.put(metadata.getName(), metadata);
 	}
 
+	/**
+	 * Get the logger used by this resource and all the other resources managed by it.
+	 * 
+	 * @return	The used logger.
+	 */
 	public final TAPLog getLogger(){
 		return service.getLogger();
 	}
 
-	public void setTAPBaseURL(String baseURL){
+	/**
+	 * <p>Let initialize this resource and all the other managed resources.</p>
+	 * 
+	 * <p>This function is called by the library just once: when the servlet is initialized.</p>
+	 * 
+	 * @param config	Configuration of the servlet.
+	 * 
+	 * @throws ServletException	If any error occurs while reading the given configuration.
+	 * 
+	 * @see TAPResource#init(ServletConfig)
+	 */
+	public void init(final ServletConfig config) throws ServletException{
+		for(TAPResource res : resources.values())
+			res.init(config);
+	}
+
+	/**
+	 * <p>Free all the resources used by this resource and the other managed resources.</p>
+	 * 
+	 * <p>This function is called by the library just once: when the servlet is destroyed.</p>
+	 * 
+	 * @see TAPResource#destroy()
+	 */
+	public void destroy(){
+		for(TAPResource res : resources.values())
+			res.destroy();
+	}
+
+	/**
+	 * <p>Set the base URL of this TAP service.</p>
+	 * 
+	 * <p>
+	 * 	This URL must be the same as the one of this resource ; it corresponds to the
+	 * 	URL of the root (or home) of the TAP service.
+	 * </p>
+	 * 
+	 * <p>The given URL will be propagated to the other TAP resources automatically.</p>
+	 * 
+	 * @param baseURL	URL of this resource.
+	 * 
+	 * @see TAPResource#setTAPBaseURL(String)
+	 */
+	public void setTAPBaseURL(final String baseURL){
 		tapBaseURL = baseURL;
 		for(TAPResource res : resources.values())
 			res.setTAPBaseURL(tapBaseURL);
 	}
 
-	public void setTAPBaseURL(HttpServletRequest request){
+	/**
+	 * <p>Build the base URL from the given HTTP request, and use it to set the base URL of this TAP service.</p>
+	 * 
+	 * <p>The given URL will be propagated to the other TAP resources automatically.</p>
+	 * 
+	 * @param request	HTTP request from which a TAP service's base URL will be extracted.
+	 * 
+	 * @see #setTAPBaseURL(String)
+	 */
+	public void setTAPBaseURL(final HttpServletRequest request){
 		setTAPBaseURL(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + request.getServletPath());
 	}
 
+	/* ******************** */
+	/* RESOURCES MANAGEMENT */
+	/* ******************** */
+
+	/**
+	 * Get the /availability resource of this TAP service.
+	 * 
+	 * @return	The /availability resource.
+	 */
 	public final Availability getAvailability(){
 		return (Availability)resources.get(Availability.RESOURCE_NAME);
 	}
 
+	/**
+	 * Get the /capabilities resource of this TAP service.
+	 * 
+	 * @return	The /capabilities resource.
+	 */
 	public final Capabilities getCapabilities(){
 		return (Capabilities)resources.get(Capabilities.RESOURCE_NAME);
 	}
 
+	/**
+	 * Get the /sync resource of this TAP service.
+	 * 
+	 * @return	The /sync resource.
+	 */
 	public final Sync getSync(){
 		return (Sync)resources.get(Sync.RESOURCE_NAME);
 	}
 
+	/**
+	 * Get the /async resource of this TAP service.
+	 * 
+	 * @return	The /async resource.
+	 */
 	public final ASync getASync(){
 		return (ASync)resources.get(ASync.RESOURCE_NAME);
 	}
 
+	/**
+	 * Get the UWS service used for the /async service.
+	 * 
+	 * @return	The used UWS service.
+	 */
+	public final UWSService getUWS(){
+		TAPResource res = getASync();
+		if (res != null)
+			return ((ASync)res).getUWS();
+		else
+			return null;
+	}
+
+	/**
+	 * <p>Get the object managing all the metadata (information about the published columns and tables)
+	 * of this TAP service.</p>
+	 * 
+	 * <p>This object is also to the /tables resource.</p>
+	 * 
+	 * @return	List of all metadata of this TAP service.
+	 */
 	public final TAPMetadata getTAPMetadata(){
 		return (TAPMetadata)resources.get(TAPMetadata.RESOURCE_NAME);
 	}
 
+	/**
+	 * <p>Add the given resource in this TAP service.</p>
+	 * 
+	 * <p>The ID of this resource (which is also its URI) will be its name (given by {@link TAPResource#getName()}).</p>
+	 * 
+	 * <p><b>WARNING:
+	 * 	If another resource with an ID strictly identical (case sensitively) to the name of the given resource, it will be overwritten!
+	 * 	You should check (thanks to {@link #hasResource(String)}) before calling this function that no resource is associated with the same URI.
+	 * 	If it is the case, you should then use the function {@link #addResource(String, TAPResource)} with a different ID/URI.
+	 * </b></p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function is equivalent to {@link #addResource(String, TAPResource)} with {@link TAPResource#getName()} in first parameter.
+	 * </i></p>
+	 * 
+	 * @param newResource	Resource to add in the service.
+	 * 
+	 * @return	<i>true</i> if the given resource has been successfully added,
+	 *        	<i>false</i> otherwise (and particularly if the given resource is NULL).
+	 * 
+	 * @see #addResource(String, TAPResource)
+	 */
+	public final boolean addResource(final TAPResource newResource){
+		return addResource(newResource.getName(), newResource);
+	}
+
+	/**
+	 * <p>Add the given resource in this TAP service with the given ID (which will be also the URI to access this resource).</p>
+	 * 
+	 * <p><b>WARNING:
+	 * 	If another resource with an ID strictly identical (case sensitively) to the name of the given resource, it will be overwritten!
+	 * 	You should check (thanks to {@link #hasResource(String)}) before calling this function that no resource is associated with the same URI.
+	 * 	If it is the case, you should then use the function {@link #addResource(String, TAPResource)} with a different ID/URI.
+	 * </b></p>
+	 * 
+	 * <p><i>Note:
+	 * 	If the given ID is NULL, the name of the resource will be used.
+	 * </i></p>
+	 * 
+	 * @param resourceId	ID/URI of the resource to add.
+	 * @param newResource	Resource to add.
+	 * 
+	 * @return	<i>true</i> if the given resource has been successfully added to this service with the given ID/URI,
+	 *        	<i>false</I> otherwise (and particularly if the given resource is NULL).
+	 */
+	public final boolean addResource(final String resourceId, final TAPResource newResource){
+		if (newResource == null)
+			return false;
+		resources.put((resourceId == null) ? newResource.getName() : resourceId, newResource);
+		return true;
+	}
+
+	/**
+	 * Get the number of all resources managed by this TAP service (this resource - HOME - excluded).
+	 * 
+	 * @return	Number of managed resources.
+	 */
+	public final int getNbResources(){
+		return resources.size();
+	}
+
+	/**
+	 * <p>Get the specified resource.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	The research is case sensitive.
+	 * </i></p>
+	 * 
+	 * @param resourceId	Exact ID/URI of the resource to get.
+	 * 
+	 * @return	The corresponding resource,
+	 *        	or NULL if no match can be found.
+	 */
+	public final TAPResource getResource(final String resourceId){
+		return resources.get(resourceId);
+	}
+
+	/**
+	 * Let iterate over the full list of the TAP resources managed by this TAP service.
+	 * 
+	 * @return	Iterator over the available TAP resources.
+	 */
 	public final Iterator<TAPResource> getTAPResources(){
 		return resources.values().iterator();
 	}
 
+	/**
+	 * <p>Tell whether a resource is already associated with the given ID/URI.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	The research is case sensitive.
+	 * </i></p>
+	 * 
+	 * @param resourceId	Exact ID/URI of the resource to find.
+	 * 
+	 * @return	<i>true</i> if a resource is already associated with the given ID/URI,
+	 *        	<i>false</i> otherwise.
+	 */
+	public final boolean hasResource(final String resourceId){
+		return resources.containsKey(resourceId);
+	}
+
+	/**
+	 * <p>Remove the resource associated with the given ID/URI.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	The research is case sensitive.
+	 * </i></p>
+	 * 
+	 * @param resourceId	Exact ID/URI of the resource to remove.
+	 * 
+	 * @return	The removed resource, if associated with the given ID/URI,
+	 *        	otherwise, NULL is returned.
+	 */
+	public final TAPResource removeResource(final String resourceId){
+		return resources.remove(resourceId);
+	}
+
+	/* **************** */
+	/* ERROR MANAGEMENT */
+	/* **************** */
+
+	/**
+	 * Get the object to use in order to report errors to the user in replacement of the expected result.
+	 * 
+	 * @return	Used error writer.
+	 */
 	public final ServiceErrorWriter getErrorWriter(){
 		return errorWriter;
 	}
 
-	public final void setErrorWriter(ServiceErrorWriter errorWriter){
+	/**
+	 * Set the object to use in order to report errors to the user in replacement of the expected result.
+	 * 
+	 * @param errorWriter	Error writer to use. (if NULL, nothing will be done)
+	 */
+	public final void setErrorWriter(final ServiceErrorWriter errorWriter){
 		if (errorWriter != null){
 			this.errorWriter = errorWriter;
 			getUWS().setErrorWriter(errorWriter);
@@ -169,29 +410,36 @@ public class TAP implements VOSIResource {
 	public String getCapability(){
 		StringBuffer xml = new StringBuffer();
 
-		xml.append("<capability standardID=\"").append(getStandardID()).append("\" xsi:type=\"tr:TableAccess\">\n");
+		// Header:
+		xml.append("<capability ").append(VOSerializer.formatAttribute("standardID", getStandardID())).append(" xsi:type=\"tr:TableAccess\">\n");
+
+		// TAP access:
 		xml.append("\t<interface role=\"std\" xsi:type=\"vs:ParamHTTP\">\n");
-		xml.append("\t\t<accessURL use=\"base\">").append(getAccessURL()).append("</accessURL>\n");
+		xml.append("\t\t<accessURL use=\"base\">").append(VOSerializer.formatText(getAccessURL())).append("</accessURL>\n");
 		xml.append("\t</interface>\n");
+
+		// Language description:
 		xml.append("\t<language>\n");
 		xml.append("\t\t<name>ADQL</name>\n");
 		xml.append("\t\t<version>2.0</version>\n");
 		xml.append("\t\t<description>ADQL 2.0</description>\n");
 		xml.append("\t</language>\n");
 
+		// Available output formats:
 		Iterator<OutputFormat> itFormats = service.getOutputFormats();
 		OutputFormat formatter;
 		while(itFormats.hasNext()){
 			formatter = itFormats.next();
 			xml.append("\t<outputFormat>\n");
-			xml.append("\t\t<mime>").append(formatter.getMimeType()).append("</mime>\n");
+			xml.append("\t\t<mime>").append(VOSerializer.formatText(formatter.getMimeType())).append("</mime>\n");
 			if (formatter.getShortMimeType() != null)
-				xml.append("\t\t<alias>").append(formatter.getShortMimeType()).append("</alias>\n");
+				xml.append("\t\t<alias>").append(VOSerializer.formatText(formatter.getShortMimeType())).append("</alias>\n");
 			if (formatter.getDescription() != null)
-				xml.append("\t\t<description>").append(formatter.getDescription()).append("</description>\n");
+				xml.append("\t\t<description>").append(VOSerializer.formatText(formatter.getDescription())).append("</description>\n");
 			xml.append("\t</outputFormat>\n");
 		}
 
+		// Retention period (for asynchronous jobs):
 		int[] retentionPeriod = service.getRetentionPeriod();
 		if (retentionPeriod != null && retentionPeriod.length >= 2){
 			if (retentionPeriod[0] > -1 || retentionPeriod[1] > -1){
@@ -204,6 +452,7 @@ public class TAP implements VOSIResource {
 			}
 		}
 
+		// Execution duration (still for asynchronous jobs):
 		int[] executionDuration = service.getExecutionDuration();
 		if (executionDuration != null && executionDuration.length >= 2){
 			if (executionDuration[0] > -1 || executionDuration[1] > -1){
@@ -216,19 +465,21 @@ public class TAP implements VOSIResource {
 			}
 		}
 
+		// Output/Result limit:
 		int[] outputLimit = service.getOutputLimit();
 		LimitUnit[] outputLimitType = service.getOutputLimitType();
 		if (outputLimit != null && outputLimit.length >= 2 && outputLimitType != null && outputLimitType.length >= 2){
 			if (outputLimit[0] > -1 || outputLimit[1] > -1){
 				xml.append("\t<outputLimit>\n");
 				if (outputLimit[0] > -1)
-					xml.append("\t\t<default unit=\"").append(outputLimitType[0]).append("\">").append(outputLimit[0]).append("</default>\n");
+					xml.append("\t\t<default ").append(VOSerializer.formatAttribute("unit", outputLimitType[0].toString())).append(">").append(outputLimit[0]).append("</default>\n");
 				if (outputLimit[1] > -1)
-					xml.append("\t\t<hard unit=\"").append(outputLimitType[1]).append("\">").append(outputLimit[1]).append("</hard>\n");
+					xml.append("\t\t<hard ").append(VOSerializer.formatAttribute("unit", outputLimitType[1].toString())).append(">").append(outputLimit[1]).append("</hard>\n");
 				xml.append("\t</outputLimit>\n");
 			}
 		}
 
+		// Upload capabilities and limits:
 		if (service.uploadEnabled()){
 			// Write upload methods: INLINE, HTTP, FTP:
 			xml.append("<uploadMethod ivo-id=\"ivo://ivoa.org/tap/uploadmethods#inline\" />");
@@ -245,92 +496,189 @@ public class TAP implements VOSIResource {
 				if (uploadLimit[0] > -1 || uploadLimit[1] > -1){
 					xml.append("\t<uploadLimit>\n");
 					if (uploadLimit[0] > -1)
-						xml.append("\t\t<default unit=\"").append(uploadLimitType[0]).append("\">").append(uploadLimit[0]).append("</default>\n");
+						xml.append("\t\t<default ").append(VOSerializer.formatAttribute("unit", uploadLimitType[0].toString())).append(">").append(uploadLimit[0]).append("</default>\n");
 					if (uploadLimit[1] > -1)
-						xml.append("\t\t<hard unit=\"").append(uploadLimitType[1]).append("\">").append(uploadLimit[1]).append("</hard>\n");
+						xml.append("\t\t<hard ").append(VOSerializer.formatAttribute("unit", uploadLimitType[1].toString())).append(">").append(uploadLimit[1]).append("</hard>\n");
 					xml.append("\t</uploadLimit>\n");
 				}
 			}
 		}
 
+		// Footer:
 		xml.append("\t</capability>");
 
 		return xml.toString();
 	}
 
-	public final UWSService getUWS(){
-		TAPResource res = getASync();
-		if (res != null)
-			return ((ASync)res).getUWS();
-		else
-			return null;
-	}
+	/* ************************************* */
+	/* MANAGEMENT OF THIS RESOURCE'S CONTENT */
+	/* ************************************* */
 
 	/**
-	 * @return The homePageURI.
+	 * <p>Get the URL or the file path of a custom home page.</p>
+	 * 
+	 * <p>The home page will be displayed when this resource is directly requested.</p>
+	 * 
+	 * @return	URL or file path of the file to display as home page,
+	 *        	or NULL if no custom home page has been specified.
 	 */
 	public final String getHomePageURI(){
 		return homePageURI;
 	}
 
-	public final void setHomePageURI(String uri){
+	/**
+	 * <p>Set the URL or the file path of a custom home page.</p>
+	 * 
+	 * <p>The home page will be displayed when this resource is directly requested.</p>
+	 * 
+	 * @param uri	URL or file path of the file to display as home page, or NULL to display the default home page.
+	 */
+	public final void setHomePageURI(final String uri){
 		homePageURI = (uri != null) ? uri.trim() : uri;
 		if (homePageURI != null && homePageURI.length() == 0)
 			homePageURI = null;
 	}
 
-	public void init(ServletConfig config) throws ServletException{
-		for(TAPResource res : resources.values())
-			res.init(config);
+	/**
+	 * <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>
+	 * 
+	 * @return	MIME type of the custom home page.
+	 */
+	public final String getHomePageMimeType(){
+		return homePageMimeType;
 	}
 
-	public void destroy(){
-		for(TAPResource res : resources.values())
-			res.destroy();
+	/**
+	 * <p>Set the MIME type of the custom home page.</p>
+	 * 
+	 * <p>A NULL value will be considered as "text/html".</p>
+	 * 
+	 * @param mime	MIME type of the custom home page.
+	 */
+	public final void setHomePageMimeType(final String mime){
+		homePageMimeType = (mime == null || mime.trim().length() == 0) ? "text/html" : mime.trim();
 	}
 
-	public void executeRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
-		response.setContentType("text/plain");
+	/**
+	 * <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 2.0
+	 */
+	protected synchronized String generateRequestID(final HttpServletRequest request){
+		String id;
+		do{
+			id = System.currentTimeMillis() + "";
+		}while(lastRequestID != null && lastRequestID.startsWith(id));
+		lastRequestID = id;
+		return id;
+	}
+
+	/**
+	 * <p>Execute the given request in the TAP service by forwarding it to the appropriate resource.</p>
+	 * 
+	 * <h3>Home page</h3>
+	 * <p>
+	 * 	If the appropriate resource is the home page, the request is not propagated and
+	 * 	this class/resource displays directly the home page in the given response by calling {@link #writeHomePage(HttpServletResponse, JobOwner)}.
+	 * 	The default implementation of this function takes 2 cases into account:
+	 * </p>
+	 * <ol>
+	 * 	<li><b>A custom home page has been specified</b> using {@link #setHomePageURI(String)}. In this case, the content of the URL or file path will
+	 * 	                                                 be directly copied into the HTTP response. The content type of the response must be specified by
+	 * 	                                                 {@link #setHomePageMimeType(String)} ; by default, it is "text/html".</li>
+	 * 	<li><b>Default home page.</b> When no custom home page has been specified, a default content is displayed. It is an HTML document which merely
+	 * 	                              lists all resources available in this TAP service.</li>
+	 * </ol>
+	 * 
+	 * <h3>Error/Exception management</h3>
+	 * <p>
+	 * 	Only this resource (the root) should write any errors in the response. For that, it catches any {@link Throwable} and
+	 * 	write an appropriate message in the HTTP response. The format and the content of this message is designed by the {@link ServiceErrorWriter}
+	 * 	set in this class. By changing it, it is then possible to change, for instance, the format of the error responses.
+	 * </p>
+	 * 
+	 * <h3>Request ID &amp; Log</h3>
+	 * <p>
+	 * 	Each request is identified by a unique identifier (see {@link #generateRequestID(HttpServletRequest)}).
+	 * 	This ID is used only for logging purpose. Request and jobs/threads can then be associated more easily in the logs.
+	 * 	Besides, every requests and their response are logged as INFO with this ID.
+	 * </p>
+	 * 
+	 * @param request	Request of the user to execute in this TAP service.
+	 * @param response	Object in which the result of the request must be written.
+	 * 
+	 * @throws ServletException	If any grave/fatal error occurs.
+	 * @throws IOException		If any error occurs while reading or writing from or into a stream (and particularly the given request or response).
+	 */
+	public void executeRequest(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException{
+		if (request == null || response == null)
+			return;
+
+		// Generate a unique ID for this request execution (for log purpose only):
+		final String reqID = generateRequestID(request);
+
+		// Retrieve the resource path parts:
+		String[] resourcePath = (request.getPathInfo() == null) ? null : request.getPathInfo().split("/");
+		final String resourceName = (resourcePath == null || resourcePath.length < 1) ? "homePage" : resourcePath[1].trim().toLowerCase();
+
+		// Log the reception of the request, only if the asked resource is not UWS (because UWS is already logging the received request):
+		if (!resourceName.equalsIgnoreCase(ASync.RESOURCE_NAME))
+			getLogger().logHttp(LogLevel.INFO, request, reqID, null, null);
 
-		if (tapBaseURL == null)
+		// Initialize the base URL of this TAP service by guessing it from the received request:
+		if (tapBaseURL == null){
+			// initialize the base URL:
 			setTAPBaseURL(request);
+			// log the successful initialization:
+			getLogger().logUWS(LogLevel.INFO, this, "INIT", "TAP successfully initialized.", null);
+		}
 
 		JobOwner owner = null;
-		String resourceName = null;
-
 		try{
 			// Identify the user:
-			if (service.getUserIdentifier() != null)
-				owner = service.getUserIdentifier().extractUserId(new UWSUrl(request), request);
+			try{
+				if (service.getUserIdentifier() != null)
+					owner = service.getUserIdentifier().extractUserId(new UWSUrl(request), request);
+			}catch(UWSException ue){
+				throw new TAPException(ue);
+			}
 
-			String[] resourcePath = (request.getPathInfo() == null) ? null : request.getPathInfo().split("/");
 			// Display the TAP Main Page:
-			if (resourcePath == null || resourcePath.length < 1){
-				resourceName = "homePage";
-				response.setContentType("text/html");
-				writeHomePage(response.getWriter(), owner);
-			}
+			if (resourceName.equals("homePage"))
+				writeHomePage(response, owner);
 			// or Display/Execute the selected TAP Resource:
 			else{
-				resourceName = resourcePath[1].trim().toLowerCase();
+				// search for the corresponding resource:
 				TAPResource res = resources.get(resourceName);
+				// if one is found, execute it:
 				if (res != null)
 					res.executeResource(request, response);
+				// otherwise, throw an error:
 				else
-					errorWriter.writeError("This TAP service does not have a resource named \"" + resourceName + "\" !", ErrorType.TRANSIENT, HttpServletResponse.SC_NOT_FOUND, response, request, null, "Get a TAP resource");
+					throw new TAPException("Unknown TAP resource: \"" + resourceName + "\"!", UWSException.NOT_IMPLEMENTED);
 			}
 
-			service.getLogger().httpRequest(request, owner, resourceName, HttpServletResponse.SC_OK, "[OK]", null);
-
 			response.flushBuffer();
-		}catch(IOException ioe){
-			errorWriter.writeError(ioe, response, request, owner, (resourceName == null) ? "Writing the TAP home page" : ("Executing the TAP resource " + resourceName));
-		}catch(UWSException ue){
-			errorWriter.writeError(ue, response, request, owner, (resourceName == null) ? "Writing the TAP home page" : ("Executing the TAP resource " + resourceName));
-		}catch(TAPException te){
-			writeError(te, response);
+
+			// Log the successful execution of the action, only if the asked resource is not UWS (because UWS is already logging the received request):
+			if (!resourceName.equalsIgnoreCase(ASync.RESOURCE_NAME))
+				getLogger().logHttp(LogLevel.INFO, response, reqID, owner, "HTTP " + UWSException.OK + " - Action \"" + resourceName + "\" successfully executed.", null);
+
 		}catch(Throwable t){
-			errorWriter.writeError(t, response, request, owner, (resourceName == null) ? "Writing the TAP home page" : ("Executing the TAP resource " + resourceName));
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(t, response, request, reqID, owner, resourceName);
+			// Log the error:
+			getLogger().logHttp(LogLevel.ERROR, response, reqID, owner, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\", because: " + t.getMessage(), t);
 		}finally{
 			// Notify the queue of the asynchronous jobs that a new connection is available:
 			if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME) && service.getFactory().countFreeConnections() >= 1)
@@ -338,9 +686,35 @@ public class TAP implements VOSIResource {
 		}
 	}
 
-	public void writeHomePage(final PrintWriter writer, final JobOwner owner) throws IOException{
+	/**
+	 * <p>Write the content of the home page in the given writer.</p>
+	 * 
+	 * <p>This content can be:</p>
+	 * <ul>
+	 * 	<li><b>a distance document</b> if a URL has been provided to this class using {@link #setHomePageURI(String)}.
+	 * 	                               In this case, the content of the distant document is copied in the given writer.
+	 * 	                               No redirection is done.</li>
+	 * 	<li><b>a local file</b> if a file path has been provided to this class using {@link #setHomePageURI(String)}.
+	 * 	                        In this case, the content of the local file is copied in the given writer.</li>
+	 * 	<li><b>a default content</b> if no custom home page has been specified using {@link #setHomePageURI(String)}.
+	 * 	                             This default home page is hard-coded in this function and displays just an HTML list of
+	 * 	                             links. There is one link for each resources of this TAP service.</li>
+	 * </ul>
+	 * 
+	 * @param response	{@link HttpServletResponse} in which the home page must be written.
+	 * @param owner		The identified user who asked this home page.
+	 * 
+	 * @throws IOException	If any error occurs while writing the home page in the given HTTP response.
+	 */
+	public void writeHomePage(final HttpServletResponse response, final JobOwner owner) throws IOException{
+		PrintWriter writer = response.getWriter();
+
 		// By default, list all available resources:
 		if (homePageURI == null){
+			// Set the content type: HTML document
+			response.setContentType("text/html");
+
+			// Write the home page:
 			writer.println("<html><head><title>TAP HOME PAGE</title></head><body><h1 style=\"text-align: center\">TAP HOME PAGE</h1><h2>Available resources:</h2><ul>");
 			for(TAPResource res : resources.values())
 				writer.println("<li><a href=\"" + tapBaseURL + "/" + res.getName() + "\">" + res.getName() + "</a></li>");
@@ -348,14 +722,19 @@ public class TAP implements VOSIResource {
 		}
 		// or Display the specified home page:
 		else{
+			response.setContentType(homePageMimeType);
+
+			// Get an input toward the custom home page:
 			BufferedInputStream input = null;
 			try{
+				// CASE: URL => distant document
 				input = new BufferedInputStream((new URL(homePageURI)).openStream());
 			}catch(MalformedURLException mue){
+				// CASE: file path => local file
 				input = new BufferedInputStream(new FileInputStream(new File(homePageURI)));
 			}
-			if (input == null)
-				throw new IOException("Incorrect TAP home page URI !");
+
+			// Copy the content of the input into the given writer:
 			byte[] buffer = new byte[255];
 			int nbReads = 0;
 			while((nbReads = input.read(buffer)) > 0)
@@ -363,95 +742,4 @@ public class TAP implements VOSIResource {
 		}
 	}
 
-	public void writeError(TAPException ex, HttpServletResponse response) throws ServletException, IOException{
-		service.getLogger().error(ex);
-		response.reset();
-		response.setStatus(ex.getHttpErrorCode());
-		response.setContentType("text/xml");
-		writeError(ex, response.getWriter());
-	}
-
-	protected void writeError(TAPException ex, PrintWriter output) throws ServletException, IOException{
-		output.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-		output.println("<VOTABLE xmlns=\"http://www.ivoa.net/xml/VOTable/v1.2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.ivoa.net/xml/VOTable/v1.2\" version=\"1.2\">");
-		output.println("\t<RESOURCE type=\"results\">");
-
-		// Print the error:
-		output.println("\t\t<INFO name=\"QUERY_STATUS\" value=\"ERROR\">");
-		output.print("\t\t\t<![CDATA[ ");
-		if (ex.getExecutionStatus() != null)
-			output.print("[WHILE " + ex.getExecutionStatus() + "] ");
-		output.print(ex.getMessage().replace('«', '\"').replace('»', '\"'));
-		output.println("]]>\t\t</INFO>");
-
-		// Print the current date:
-		DateFormat dateFormat = new SimpleDateFormat(UWSJob.DEFAULT_DATE_FORMAT);
-		output.print("\t\t<INFO name=\"DATE\" value=\"");
-		output.print(dateFormat.format(new Date()));
-		output.println("\" />");
-
-		// Print the provider (if any):
-		if (service.getProviderName() != null){
-			output.print("\t\t<INFO name=\"PROVIDER\" value=\"");
-			output.print(service.getProviderName());
-			if (service.getProviderDescription() != null){
-				output.print("\">\n\t\t\t<![CDATA[");
-				output.print(service.getProviderDescription());
-				output.println("]]>\n\t\t</INFO>");
-			}else
-				output.println("\" />");
-		}
-
-		// Print the query (if any):
-		if (ex.getQuery() != null){
-			output.print("\t\t<INFO name=\"QUERY\">\n\t\t\t<![CDATA[");
-			output.println(ex.getQuery());
-			output.println("]]>\t\t</INFO>");
-		}
-
-		output.println("\t</RESOURCE>");
-		output.println("</VOTABLE>");
-
-		output.flush();
-	}
-
-	public final boolean addResource(TAPResource newResource){
-		if (newResource == null)
-			return false;
-		resources.put(newResource.getName(), newResource);
-		return true;
-	}
-
-	public final boolean addResource(String resourceId, TAPResource newResource){
-		if (newResource == null)
-			return false;
-		resources.put((resourceId == null) ? newResource.getName() : resourceId, newResource);
-		return true;
-	}
-
-	public final int getNbResources(){
-		return resources.size();
-	}
-
-	public final TAPResource getResource(String resourceId){
-		return resources.get(resourceId);
-	}
-
-	public final boolean hasResource(String resourceId){
-		return resources.containsKey(resourceId);
-	}
-
-	public final TAPResource removeResource(String resourceId){
-		return resources.remove(resourceId);
-	}
-
-	public boolean updateTAPMetadata(){
-		TAPMetadata metadata = service.getTAPMetadata();
-		if (metadata != null){
-			resources.put(metadata.getName(), metadata);
-			return true;
-		}
-		return false;
-	}
-
 }
diff --git a/src/tap/resource/TAPResource.java b/src/tap/resource/TAPResource.java
index 4a830c91d5ba0c6098f04d2b71733e52dc3b69d1..37938da4dc51c314c05de3247cba5a294e066673 100644
--- a/src/tap/resource/TAPResource.java
+++ b/src/tap/resource/TAPResource.java
@@ -16,7 +16,8 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -27,18 +28,77 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import tap.TAPException;
-import uws.UWSException;
 
+/**
+ * <p>List the common functions that a TAP resource must have.
+ * Basically, any TAP resource may be initialized, may be destroyed, must have a name and must execute a request provided by its TAP service.</p>
+ * 
+ * <p><i><b>Important note:</b>
+ * 	It is strongly recommended that the name of the TAP resource is also provided through a public static attribute named "RESOURCE_NAME".
+ * 	If this attribute exists, its value must be the same as the one returned by {@link #getName()}.
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public interface TAPResource {
 
+	/**
+	 * Let initialize this TAP resource.
+	 * 
+	 * @param config	Servlet configuration. (may be useful for the configuration of this resource)
+	 * 
+	 * @throws ServletException	If any error prevent the initialization of this TAP resource. In case a such exception is thrown, the service should stop immediately.
+	 */
 	public void init(ServletConfig config) throws ServletException;
 
+	/**
+	 * Let free properly all system/file/DB resources kept opened by this TAP resource.
+	 */
 	public void destroy();
 
+	/**
+	 * <p>Let diffuse the base URL of the TAP service to all its TAP resources.</p>
+	 * 
+	 * <p><i><b>Important note:</b>
+	 * 	This function should be called just once: either at the creation of the service or when the first request is sent to the TAP service
+	 * 	(in this case, the request is also used to finish the initialization of the TAP service, and of all its resources).
+	 * </i></p>
+	 * 
+	 * @param baseURL	Common URL/URI used in all requests sent by any user to the TAP service.  
+	 */
 	public void setTAPBaseURL(String baseURL);
 
+	/**
+	 * <p>Get the name of this TAP resource.</p>
+	 * 
+	 * <p><i><b>Important note:</b>
+	 * 	This name MUST NOT be NULL and SHOULD NEVER change.
+	 * </i></p>
+	 * 
+	 * @return	Name of this TAP resource.
+	 */
 	public String getName();
 
-	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, TAPException, UWSException;
+	/**
+	 * <p>Interpret the given request, execute the appropriate action and finally return a result or display information to the user.</p>
+	 * 
+	 * <p><b>IMPORTANT:
+	 * 	"TAP resources can not take the law in their own hands!"</b> :-)
+	 * 	Errors that could occur inside this function should not be written directly in the given {@link HttpServletResponse}.
+	 * 	They should be thrown to the resources executor: an instance of {@link TAP}, which
+	 * 	will fill the {@link HttpServletResponse} with the error in the format described by the IVOA standard - VOTable. Besides, {@link TAP} may also
+	 * 	add more information and may log the error (in function of this type).
+	 * </p>
+	 * 
+	 * @param request	Request sent by the user and which should be interpreted/executed here.
+	 * @param response	Response in which the result of the request must be written.
+	 * 
+	 * @return	<i>true</i> if the request has been successfully executed, <i>false</i> otherwise (but generally an exception will be sent if the request can't be executed).
+	 * 
+	 * @throws IOException		If any error occurs while writing the result of the given request.
+	 * @throws TAPException		If any other error occurs while interpreting and executing the request or by formating and writing its result.
+	 */
+	public boolean executeResource(HttpServletRequest request, HttpServletResponse response) throws IOException, TAPException;
 
 }
diff --git a/src/tap/resource/VOSIResource.java b/src/tap/resource/VOSIResource.java
index 758c8ad43a323133f86bc9ad1a278c91bd029c4e..216b55b1a5e87a0216ef9df4510e869059509ae3 100644
--- a/src/tap/resource/VOSIResource.java
+++ b/src/tap/resource/VOSIResource.java
@@ -16,15 +16,70 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
+/**
+ * <p>VOSI - VO Support Interfaces - lets describe a minimal interface that VO web services should provide.</p>
+ * 
+ * <p>
+ * 	This interface aims to give information about the capabilities, the availability and the reliability of the service.
+ * 	To reach this goal the 3 following endpoints (resources) should be provided:
+ * </p>
+ * <ol>
+ * 	<li><b>Capability metadata:</b> list all available resources, give their access URL and a standard ID (helping to identify the type of resource).
+ * 	                                More information related to the service itself (or about the VO standard it is implementing) may be provided.</li>
+ *                                  
+ * 	<li><b>Availability metadata:</b> indicate whether the service is available or not. It may also provide a note and some other information about
+ * 	                                  its reliability, such as the date at which it is up, or since when it is down and when it will be back.</li>
+ * 
+ * 	<li><b>Tables metadata:</b> since some VO services deal with tabular data (in output, in input or queriable by a language like ADQL),
+ * 	                            a VOSI-compliant service shall provide a list and a description of them.</li>
+ * </ol>
+ * 
+ * <p>
+ * 	Implementing the VOSI interface means that each service endpoint/resource must be described in the capability endpoint with an access URL and a standard VO ID.
+ * </p>
+ * 
+ * <p>The standard IDs of the VOSI endpoints are the following:</p>
+ * <ul>
+ * 	<li><b>Capabilities:</b> ivo://ivoa.net/std/VOSI#capabilities</li>
+ * 	<li><b>Availability:</b> ivo://ivoa.net/std/VOSI#availability</li>
+ * 	<li><b>Tables:</b> ivo://ivoa.net/std/VOSI#tables</li>
+ * </ul>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2014)
+ */
 public interface VOSIResource {
 
+	/**
+	 * Get the capabilities of this resource.
+	 * 
+	 * @return	Resource capabilities.
+	 */
 	public String getCapability();
 
+	/**
+	 * Get the URL which lets access this resource.
+	 * 
+	 * @return	Access URL.
+	 */
 	public String getAccessURL();
 
+	/**
+	 * <p>Get the standardID of this endpoint of the VOSI interface.</p>
+	 * 
+	 * <p>The standard IDs of the VOSI endpoints are the following:</p>
+	 * <ul>
+	 * 	<li><b>Capabilities:</b> ivo://ivoa.net/std/VOSI#capabilities</li>
+	 * 	<li><b>Availability:</b> ivo://ivoa.net/std/VOSI#availability</li>
+	 * 	<li><b>Tables:</b> ivo://ivoa.net/std/VOSI#tables</li>
+	 * </ul>
+	 * 
+	 * @return	Standard ID of this VOSI endpoint.
+	 */
 	public String getStandardID();
 
 }
\ No newline at end of file
diff --git a/src/tap/upload/Uploader.java b/src/tap/upload/Uploader.java
index 8286b960254c9b137859b286d0e992b271f1b428..4e610df8de0555daee0b122fd3741a5013896d10 100644
--- a/src/tap/upload/Uploader.java
+++ b/src/tap/upload/Uploader.java
@@ -35,6 +35,7 @@ import tap.metadata.TAPColumn;
 import tap.metadata.TAPMetadata.STDSchema;
 import tap.metadata.TAPSchema;
 import tap.metadata.TAPTable;
+import uws.UWSException;
 
 import com.oreilly.servlet.multipart.ExceededSizeException;
 
@@ -47,7 +48,7 @@ import com.oreilly.servlet.multipart.ExceededSizeException;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2014)
+ * @version 2.0 (09/2014)
  * 
  * @see LimitedTableIterator
  * @see VOTableIterator
@@ -99,7 +100,13 @@ public class Uploader {
 	}
 
 	/**
-	 * Upload all the given VOTable inputs.
+	 * <p>Upload all the given VOTable inputs.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	The {@link TAPTable} objects representing the uploaded tables will be associated with the TAP_UPLOAD schema defined in the TAP metadata.
+	 * 	If no such schema is defined, a default one (whose DB name will be equals to the ADQL name, that's to say {@link STDSchema#UPLOADSCHEMA})
+	 * 	is created and added into the TAP metadata. The corresponding schema should be then created in the database automatically by the {@link DBConnection}.
+	 * </i></p>
 	 * 
 	 * @param loaders	Array of tables to upload.
 	 * 
@@ -110,7 +117,15 @@ public class Uploader {
 	 * @see DBConnection#addUploadedTable(TAPTable, tap.data.TableIterator)
 	 */
 	public TAPSchema upload(final TableLoader[] loaders) throws TAPException{
-		TAPSchema uploadSchema = new TAPSchema(STDSchema.UPLOADSCHEMA.label);
+		// Get the TAP_UPLOAD schema as defined in the TAP metadata:
+		TAPSchema uploadSchema = service.getTAPMetadata().getUploadSchema();
+
+		// If no TAP_UPLOAD schema is defined, create a default one and update the TAP metadata with it:
+		if (uploadSchema == null){
+			uploadSchema = new TAPSchema(STDSchema.UPLOADSCHEMA.label);
+			service.getTAPMetadata().setUploadSchema(uploadSchema);
+		}
+
 		InputStream votable = null;
 		String tableName = null;
 		try{
@@ -145,9 +160,9 @@ public class Uploader {
 			if (dre.getCause() instanceof ExceededSizeException)
 				throw dre;
 			else
-				throw new TAPException("Error while reading the VOTable \"" + tableName + "\": " + dre.getMessage(), dre);
+				throw new TAPException("Error while reading the VOTable \"" + tableName + "\": " + dre.getMessage(), dre, UWSException.BAD_REQUEST);
 		}catch(IOException ioe){
-			throw new TAPException("Error while reading the VOTable of \"" + tableName + "\"!", ioe);
+			throw new TAPException("IO error while reading the VOTable of \"" + tableName + "\"!", ioe);
 		}finally{
 			try{
 				if (votable != null)
diff --git a/src/uws/UWSException.java b/src/uws/UWSException.java
index 32aed8cc495cdca8a3994b4784153f69bce98eae..00359606ef63e0d8aa6d7a4b68172702df6c64a6 100644
--- a/src/uws/UWSException.java
+++ b/src/uws/UWSException.java
@@ -16,23 +16,30 @@ package uws;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.job.ErrorType;
 
 /**
- * Any exception returned by a class of the UWS pattern may be associated with
- * an HTTP error code (like: 404, 303, 500) and a UWS error type.
+ * <p>Any exception returned by a class of the UWS pattern may be associated with
+ * an HTTP error code (like: 404, 303, 500) and a UWS error type.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 12/2010
+ * <p>
+ * 	Any error reported with this kind of exception will (in the most of cases) interrupt a UWS action,
+ * 	by reporting an error related with the UWS usage.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class UWSException extends Exception {
 	private static final long serialVersionUID = 1L;
 
 	// SUCCESS codes:
 	public final static int OK = 200;
+	public final static int ACCEPTED_BUT_NOT_COMPLETE = 202;
 	public final static int NO_CONTENT = 204;
 
 	// REDIRECTION codes:
@@ -61,69 +68,136 @@ public class UWSException extends Exception {
 	/* ************ */
 	/* CONSTRUCTORS */
 	/* ************ */
+	/**
+	 * Exception in the general context of UWS.
+	 * 
+	 * @param msg	Error message to display.
+	 */
 	public UWSException(String msg){
-		this(msg, ErrorType.FATAL);
+		this(msg, null);
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS, and with the specified error type (FATAL or TRANSIENT). 
+	 * 
+	 * @param msg		Error message to display.
+	 * @param type		Type of the error (FATAL or TRANSIENT). <i>Note: If NULL, it will be considered as FATAL.</i>
+	 */
 	public UWSException(String msg, ErrorType type){
 		super(msg);
-		if (type != null)
-			errorType = type;
+		this.errorType = (type == null) ? ErrorType.FATAL : type;
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS because the given exception has been thrown.
+	 * 
+	 * @param t	The thrown (and so caught) exception.
+	 */
 	public UWSException(Throwable t){
-		this(t, ErrorType.FATAL);
+		this(t, null);
 	}
 
+	/**
+	 * Exception with the given type that occurs in the general context of UWS
+	 * because the given exception has been thrown.
+	 * 
+	 * @param t			The thrown (and so caught) exception.
+	 * @param type		Type of the error (FATAL or TRANSIENT). <i>Note: If NULL, it will be considered as FATAL.</i>
+	 */
 	public UWSException(Throwable t, ErrorType type){
 		super(t);
-		if (type != null)
-			errorType = type;
+		this.errorType = (type == null) ? ErrorType.FATAL : type;
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS and which should return the given HTTP error code.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param msg		Error message to display.
+	 */
 	public UWSException(int httpError, String msg){
-		this(msg);
-		if (httpError >= 0)
-			httpErrorCode = httpError;
+		this(httpError, msg, null);
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS, with the given type and which should return the given HTTP error code.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param msg		Error message to display.
+	 * @param type		Type of the error (FATAL or TRANSIENT). <i>Note: If NULL, it will be considered as FATAL.</i>
+	 */
 	public UWSException(int httpError, String msg, ErrorType type){
 		this(msg, type);
-		if (httpError >= 0)
-			httpErrorCode = httpError;
+		this.httpErrorCode = (httpError < 0) ? NOT_FOUND : httpError;
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS,
+	 * because the given exception has been thrown and that which should return the given HTTP error status.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param t			The thrown (and so caught) exception.
+	 */
 	public UWSException(int httpError, Throwable t){
-		this(t);
-		if (httpError >= 0)
-			httpErrorCode = httpError;
+		this(httpError, t, null, null);
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS with the given error type,
+	 * because the given exception has been thrown and that which should return the given HTTP error status.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param t			The thrown (and so caught) exception.
+	 * @param type		Type of the error (FATAL or TRANSIENT). <i>Note: If NULL, it will be considered as FATAL.</i>
+	 */
 	public UWSException(int httpError, Throwable t, ErrorType type){
-		this(t, type);
-		if (httpError >= 0)
-			httpErrorCode = httpError;
+		this(httpError, t, null, type);
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS,
+	 * because the given exception has been thrown and that which should return the given HTTP error status.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param t			The thrown (and so caught) exception.
+	 * @param msg		Error message to display.
+	 */
 	public UWSException(int httpError, Throwable t, String msg){
-		this(httpError, t, msg, ErrorType.FATAL);
+		this(httpError, t, msg, null);
 	}
 
+	/**
+	 * Exception that occurs in the general context of UWS,
+	 * because the given exception has been thrown and that which should return the given HTTP error status.
+	 * 
+	 * @param httpError	HTTP error code to return.
+	 * @param t			The thrown (and so caught) exception.
+	 * @param msg		Error message to display.
+	 * @param type		Type of the error (FATAL or TRANSIENT). <i>Note: If NULL, it will be considered as FATAL.</i>
+	 */
 	public UWSException(int httpError, Throwable t, String msg, ErrorType type){
 		super(msg, t);
-		if (httpError >= 0)
-			httpErrorCode = httpError;
-		if (type != null)
-			errorType = type;
+		this.httpErrorCode = (httpError < 0) ? NOT_FOUND : httpError;
+		this.errorType = (type == null) ? ErrorType.FATAL : type;
 	}
 
 	/* ******* */
 	/* GETTERS */
 	/* ******* */
+	/**
+	 * Get the HTTP error code that should be returned.
+	 * 
+	 * @return	The corresponding HTTP error code.
+	 */
 	public int getHttpErrorCode(){
 		return httpErrorCode;
 	}
 
+	/**
+	 * Get the type of this error (from the UWS point of view ; FATAL or TRANSIENT).
+	 * 
+	 * @return	Type of this error.
+	 */
 	public ErrorType getUWSErrorType(){
 		return errorType;
 	}
diff --git a/src/uws/UWSExceptionFactory.java b/src/uws/UWSExceptionFactory.java
index c2812736eccedb340ef7cfbc5d252a79aed5f8b5..c88fff2d2239b65b025ebc48e455387cf3519106 100644
--- a/src/uws/UWSExceptionFactory.java
+++ b/src/uws/UWSExceptionFactory.java
@@ -16,18 +16,18 @@ package uws;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.job.ExecutionPhase;
-
 import uws.job.user.JobOwner;
 
 /**
  * Let's creating the common exceptions of a UWS service.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  * 
  * @see UWSException
  */
@@ -49,164 +49,47 @@ public final class UWSExceptionFactory {
 		return ((consequence == null || consequence.trim().length() > 0) ? "" : " => " + consequence);
 	}
 
-	public final static UWSException missingJobListName(){
-		return missingJobListName(null);
-	}
-
-	public final static UWSException missingJobListName(final String consequence){
-		return new UWSException(UWSException.BAD_REQUEST, "Missing job list name !" + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectJobListName(final String jlName){
-		return incorrectJobListName(jlName, null);
-	}
-
-	public final static UWSException incorrectJobListName(final String jlName, final String consequence){
-		return new UWSException(UWSException.NOT_FOUND, "Incorrect job list name ! The jobs list " + jlName + " does not exist." + appendMessage(consequence));
-	}
-
-	public final static UWSException missingJobID(){
-		return missingJobID(null);
-	}
-
-	public final static UWSException missingJobID(final String consequence){
-		return new UWSException(UWSException.BAD_REQUEST, "Missing job ID !" + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectJobID(String jobListName, String jobID){
-		return incorrectJobID(jobListName, jobID, null);
-	}
-
-	public final static UWSException incorrectJobID(final String jobListName, final String jobID, final String consequence){
-		return new UWSException(UWSException.NOT_FOUND, "Incorrect job ID ! The job " + jobID + " does not exist in the jobs list " + jobListName + appendMessage(consequence));
-	}
-
-	public final static UWSException missingSerializer(final String mimeTypes){
-		return missingSerializer(null);
-	}
-
-	public final static UWSException missingSerializer(final String mimeTypes, final String consequence){
-		return new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeTypes + " !" + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectJobParameter(final String jobID, final String paramName){
-		return incorrectJobParameter(jobID, paramName, null);
-	}
-
-	public final static UWSException incorrectJobParameter(final String jobID, final String paramName, final String consequence){
-		return new UWSException(UWSException.NOT_FOUND, "Incorrect job parameter ! The parameter " + paramName + " does not exist in the job " + jobID + "." + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectJobResult(final String jobID, final String resultID){
-		return incorrectJobResult(jobID, resultID, null);
-	}
-
-	public final static UWSException incorrectJobResult(final String jobID, final String resultID, final String consequence){
-		return new UWSException(UWSException.NOT_FOUND, "Incorrect result ID ! There is no result " + resultID + " in the job " + jobID + "." + appendMessage(consequence));
-	}
-
-	public final static UWSException noErrorSummary(final String jobID){
-		return noErrorSummary(jobID, null);
-	}
-
-	public final static UWSException noErrorSummary(final String jobID, final String consequence){
-		return new UWSException(UWSException.NOT_FOUND, "There is no error summary in the job " + jobID + " !" + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectPhaseTransition(final String jobID, final ExecutionPhase fromPhase, final ExecutionPhase toPhase){
+	public final static String incorrectPhaseTransition(final String jobID, final ExecutionPhase fromPhase, final ExecutionPhase toPhase){
 		return incorrectPhaseTransition(jobID, fromPhase, toPhase, null);
 	}
 
-	public final static UWSException incorrectPhaseTransition(final String jobID, final ExecutionPhase fromPhase, final ExecutionPhase toPhase, final String consequence){
-		return new UWSException(UWSException.BAD_REQUEST, "Incorrect phase transition ! => the job " + jobID + " is in the phase " + fromPhase + ". It can not go to " + toPhase + "." + appendMessage(consequence));
-	}
-
-	public final static UWSException missingOutputStream(){
-		return missingOutputStream(null);
+	public final static String incorrectPhaseTransition(final String jobID, final ExecutionPhase fromPhase, final ExecutionPhase toPhase, final String consequence){
+		return "Incorrect phase transition ! => the job " + jobID + " is in the phase " + fromPhase + ". It can not go to " + toPhase + "." + appendMessage(consequence);
 	}
 
-	public final static UWSException missingOutputStream(final String consequence){
-		return new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing output stream !" + appendMessage(consequence));
-	}
-
-	public final static UWSException incorrectSerialization(final String serializationValue, final String serializationTarget){
-		return incorrectSerialization(serializationValue, serializationTarget, null);
-	}
-
-	public final static UWSException incorrectSerialization(final String serializationValue, final String serializationTarget, final String consequence){
-		return new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect serialization value (=" + serializationValue + ") ! => impossible to serialize " + serializationTarget + "." + appendMessage(consequence));
-	}
-
-	public final static UWSException readPermissionDenied(final JobOwner user, final boolean jobList, final String containerName){
+	public final static String readPermissionDenied(final JobOwner user, final boolean jobList, final String containerName){
 		return readPermissionDenied(user, jobList, containerName, null);
 	}
 
-	public final static UWSException readPermissionDenied(final JobOwner user, final boolean jobList, final String containerName, final String consequence){
-		return new UWSException(UWSException.PERMISSION_DENIED, user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to read the content of the " + (jobList ? "jobs list" : "job") + " \"" + containerName + "\" !" + appendMessage(consequence));
+	public final static String readPermissionDenied(final JobOwner user, final boolean jobList, final String containerName, final String consequence){
+		return user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to read the content of the " + (jobList ? "jobs list" : "job") + " \"" + containerName + "\" !" + appendMessage(consequence);
 	}
 
-	public final static UWSException writePermissionDenied(final JobOwner user, final boolean jobList, final String containerName){
+	public final static String writePermissionDenied(final JobOwner user, final boolean jobList, final String containerName){
 		return writePermissionDenied(user, jobList, containerName, null);
 	}
 
-	public final static UWSException writePermissionDenied(final JobOwner user, final boolean jobList, final String containerName, final String consequence){
-		return new UWSException(UWSException.PERMISSION_DENIED, user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to update the content of the " + (jobList ? "jobs list" : "job") + " \"" + containerName + "\" !" + appendMessage(consequence));
+	public final static String writePermissionDenied(final JobOwner user, final boolean jobList, final String containerName, final String consequence){
+		return user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to update the content of the " + (jobList ? "jobs list" : "job") + " \"" + containerName + "\" !" + appendMessage(consequence);
 	}
 
-	public final static UWSException executePermissionDenied(final JobOwner user, final String jobID){
+	public final static String executePermissionDenied(final JobOwner user, final String jobID){
 		return executePermissionDenied(user, jobID, null);
 	}
 
-	public final static UWSException executePermissionDenied(final JobOwner user, final String jobID, final String consequence){
-		return new UWSException(UWSException.PERMISSION_DENIED, user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to execute/abort the job \"" + jobID + "\" !" + appendMessage(consequence));
+	public final static String executePermissionDenied(final JobOwner user, final String jobID, final String consequence){
+		return user.getID() + ((user.getPseudo() == null) ? "" : (" (alias " + user.getPseudo() + ")")) + " is not allowed to execute/abort the job \"" + jobID + "\" !" + appendMessage(consequence);
 	}
 
-	public final static UWSException restoreJobImpossible(final Throwable t, final String cause){
-		return restoreJobImpossible(t, cause, null);
-	}
-
-	public final static UWSException restoreJobImpossible(final Throwable t, final String cause, final String consequence){
-		return new UWSException(UWSException.INTERNAL_SERVER_ERROR, t, ((cause == null) ? "" : cause) + " Impossible to restore a job from the backup file(s)." + appendMessage(consequence));
-	}
-
-	public final static UWSException restoreUserImpossible(final String cause){
-		return restoreUserImpossible(null, cause, null);
-	}
-
-	public final static UWSException restoreUserImpossible(final Throwable t, final String cause){
-		return restoreUserImpossible(t, cause, null);
-	}
-
-	public final static UWSException restoreUserImpossible(final Throwable t, final String cause, final String consequence){
-		return new UWSException(UWSException.INTERNAL_SERVER_ERROR, t, ((cause == null) ? "" : cause) + " Impossible to restore a user from the backup file(s)." + appendMessage(consequence));
-	}
-
-	public final static UWSException jobModificationForbidden(final String jobId, final ExecutionPhase phase, final String parameter){
+	public final static String jobModificationForbidden(final String jobId, final ExecutionPhase phase, final String parameter){
 		return jobModificationForbidden(jobId, phase, parameter, null);
 	}
 
-	public final static UWSException jobModificationForbidden(final String jobId, final ExecutionPhase phase, final String parameter, final String consequence){
+	public final static String jobModificationForbidden(final String jobId, final ExecutionPhase phase, final String parameter, final String consequence){
 		if (parameter != null && !parameter.trim().isEmpty())
-			return new UWSException(UWSException.NOT_ALLOWED, "Impossible to change the parameter \"" + parameter + "\" of the job " + jobId + ((phase != null) ? (" (phase: " + phase + ")") : "") + " !" + appendMessage(consequence));
+			return "Impossible to change the parameter \"" + parameter + "\" of the job " + jobId + ((phase != null) ? (" (phase: " + phase + ")") : "") + " !" + appendMessage(consequence);
 		else
-			return new UWSException(UWSException.NOT_ALLOWED, "Impossible to change the parameters of the job " + jobId + ((phase != null) ? (" (phase: " + phase + ")") : "") + " !" + appendMessage(consequence));
-	}
-
-	public final static UWSException badFormat(final String jobId, final String paramName, final String paramValue, final String valueClass, final String expectedFormat){
-		return badFormat(jobId, paramName, paramValue, valueClass, expectedFormat, null);
-	}
-
-	public final static UWSException badFormat(final String jobId, final String paramName, final String paramValue, final String valueClass, final String expectedFormat, final String consequence){
-		String strExpected = ((expectedFormat != null && !expectedFormat.trim().isEmpty()) ? (" Expected: " + expectedFormat) : "");
-		String strClass = ((valueClass != null && !valueClass.trim().isEmpty()) ? (" {an instance of " + valueClass + "}") : "");
-
-		if (paramName != null && !paramName.trim().isEmpty()){
-			if (jobId != null && !jobId.trim().isEmpty())
-				return new UWSException(UWSException.BAD_REQUEST, "Bad format for the parameter " + paramName.toUpperCase() + " of the job " + jobId + ": \"" + paramValue + "\"" + strClass + "." + strExpected + appendMessage(consequence));
-			else
-				return new UWSException(UWSException.BAD_REQUEST, "Bad format for " + paramName + ": \"" + paramValue + "\"" + strClass + "." + strExpected + appendMessage(consequence));
-		}else
-			return new UWSException(UWSException.BAD_REQUEST, "Bad format: \"" + paramValue + "\"" + strClass + "." + strExpected + appendMessage(consequence));
+			return "Impossible to change the parameters of the job " + jobId + ((phase != null) ? (" (phase: " + phase + ")") : "") + " !" + appendMessage(consequence);
 	}
 
 }
diff --git a/src/uws/job/ErrorSummary.java b/src/uws/job/ErrorSummary.java
index cc2c983434dea58939f652afdedf746f10634961..607dbff03f9b60d2b4b804677c258104d97937c6 100644
--- a/src/uws/job/ErrorSummary.java
+++ b/src/uws/job/ErrorSummary.java
@@ -16,21 +16,20 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.UWSException;
-
 import uws.job.serializer.UWSSerializer;
-
 import uws.job.user.JobOwner;
 
 /**
  * This class gives a short description of the occurred error (if any) during a job execution.
  * A fuller representation of the error may be retrieved from <i>{jobs}/(job-id)/error</i>.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 02/2011
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class ErrorSummary extends SerializableUWSObject {
 	private static final long serialVersionUID = 1L;
@@ -132,7 +131,7 @@ public class ErrorSummary extends SerializableUWSObject {
 	/* INHERITED METHODS */
 	/* ***************** */
 	@Override
-	public String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException{
+	public String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException, Exception{
 		return serializer.getErrorSummary(this, true);
 	}
 
diff --git a/src/uws/job/JobList.java b/src/uws/job/JobList.java
index 9c10ee54fcea4eaa28a5e6b9ad1f3b2ae1c2f3a3..dfd4802596414bdb568a20f248213d065caade89 100644
--- a/src/uws/job/JobList.java
+++ b/src/uws/job/JobList.java
@@ -16,7 +16,8 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.ArrayList;
@@ -24,25 +25,22 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import java.lang.IllegalStateException;
-
 import uws.UWSException;
 import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
-
 import uws.job.manager.DefaultDestructionManager;
 import uws.job.manager.DefaultExecutionManager;
 import uws.job.manager.DestructionManager;
 import uws.job.manager.ExecutionManager;
 import uws.job.serializer.UWSSerializer;
 import uws.job.user.JobOwner;
-
-import uws.service.UWSService;
 import uws.service.UWS;
+import uws.service.UWSService;
 import uws.service.UWSUrl;
 import uws.service.UserIdentifier;
 import uws.service.backup.UWSBackupManager;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <h3>General description</h3>
@@ -98,8 +96,8 @@ import uws.service.log.UWSLog;
  * 	if the jobs list is not managed by a UWS or {@link UWSService#setDestructionManager(DestructionManager)} otherwise.
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  * 
  * @see UWSJob
  */
@@ -132,11 +130,11 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * 
 	 * @param jobListName				The jobs list name.
 	 * 
-	 * @throws UWSException				If the given name is <i>null</i> or empty.
+	 * @throws NullPointerException	If the given job list name is NULL.
 	 * 
 	 * @see #JobList(String, ExecutionManager)
 	 */
-	public JobList(String jobListName) throws UWSException{
+	public JobList(String jobListName) throws NullPointerException{
 		this(jobListName, new DefaultExecutionManager(), new DefaultDestructionManager());
 	}
 
@@ -145,10 +143,10 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * 
 	 * @param jobListName		The jobs list name.
 	 * @param executionManager	The object which will manage the execution of all jobs of this list.
-	 * 
-	 * @throws UWSException		If the given name is <i>null</i> or empty, or if the given execution manager is <i>null</i>.
+	 *
+	 * @throws NullPointerException	If the given job list name is NULL or empty or if no execution manager is provided.
 	 */
-	public JobList(String jobListName, ExecutionManager executionManager) throws UWSException{
+	public JobList(String jobListName, ExecutionManager executionManager) throws NullPointerException{
 		this(jobListName, executionManager, new DefaultDestructionManager());
 	}
 
@@ -157,10 +155,10 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * 
 	 * @param jobListName			The jobs list name.
 	 * @param destructionManager	The object which manages the automatic destruction of jobs when they have reached their destruction date.
-	 * 
-	 * @throws UWSException			If the given name is <i>null</i> or empty, or if the given destruction manager is <i>null</i>.
+	 *
+	 * @throws NullPointerException	If the given job list name is NULL or empty or if no destruction manager is provided.
 	 */
-	public JobList(String jobListName, DestructionManager destructionManager) throws UWSException{
+	public JobList(String jobListName, DestructionManager destructionManager) throws NullPointerException{
 		this(jobListName, new DefaultExecutionManager(), destructionManager);
 	}
 
@@ -170,29 +168,26 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * @param jobListName			The jobs list name.
 	 * @param executionManager		The object which will manage the execution of all jobs of this list.
 	 * @param destructionManager	The object which manages the automatic destruction of jobs when they have reached their destruction date.
-	 * 
-	 * @throws UWSException		If the given name is <i>null</i> or empty, or if the given execution or destruction manager is <i>null</i>.
+	 *
+	 * @throws NullPointerException	If the given job list name is NULL or empty or if no execution manager and destruction manager are provided.
 	 */
-	public JobList(String jobListName, ExecutionManager executionManager, DestructionManager destructionManager) throws UWSException{
+	public JobList(String jobListName, ExecutionManager executionManager, DestructionManager destructionManager) throws NullPointerException{
 		if (jobListName == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job list name ! => Impossible to build the job list.");
+			throw new NullPointerException("Missing job list name ! => Impossible to build the job list.");
 		else{
 			jobListName = jobListName.trim();
 			if (jobListName.length() == 0)
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job list name ! => Impossible to build the job list.");
+				throw new NullPointerException("Missing job list name ! => Impossible to build the job list.");
 		}
 
 		name = jobListName;
 		jobsList = new LinkedHashMap<String,UWSJob>();
 		ownerJobs = new LinkedHashMap<JobOwner,Map<String,UWSJob>>();
 
-		if (executionManager == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing execution manager ! => Impossible to build the job list.");
-		else
-			this.executionManager = executionManager;
+		this.executionManager = executionManager;
 
 		if (destructionManager == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing destruction manager ! => Impossible to build the job list.");
+			throw new NullPointerException("Missing destruction manager ! => Impossible to build the job list.");
 		else
 			this.destructionManager = destructionManager;
 	}
@@ -289,6 +284,12 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * @return	The used execution manager.
 	 */
 	public final ExecutionManager getExecutionManager(){
+		if (executionManager == null){
+			if (uws == null)
+				executionManager = new DefaultExecutionManager();
+			else
+				executionManager = new DefaultExecutionManager(uws.getLogger());
+		}
 		return executionManager;
 	}
 
@@ -302,17 +303,19 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * @see ExecutionManager#remove(UWSJob)
 	 * @see ExecutionManager#execute(UWSJob)
 	 */
-	public synchronized final void setExecutionManager(final ExecutionManager manager) throws UWSException{
+	public synchronized final void setExecutionManager(final ExecutionManager manager){
 		if (manager == null)
 			return;
 
 		ExecutionManager oldManager = executionManager;
 		executionManager = manager;
 
-		for(UWSJob job : this){
-			if (job.getPhase() != ExecutionPhase.PENDING && !job.isFinished()){
-				oldManager.remove(job);
-				executionManager.execute(job);
+		if (oldManager != null){
+			for(UWSJob job : this){
+				if (job.getPhase() != ExecutionPhase.PENDING && !job.isFinished()){
+					oldManager.remove(job);
+					executionManager.execute(job);
+				}
 			}
 		}
 	}
@@ -364,7 +367,7 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 */
 	public UWSJob getJob(String jobID, JobOwner user) throws UWSException{
 		if (user != null && !user.hasReadPermission(this))
-			throw UWSExceptionFactory.readPermissionDenied(user, true, getName());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.readPermissionDenied(user, true, getName()));
 
 		// Get the specified job:
 		UWSJob job = jobsList.get(jobID);
@@ -373,7 +376,7 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 		if (user != null && job != null && job.getOwner() != null){
 			JobOwner owner = job.getOwner();
 			if (!owner.equals(user) && !user.hasReadPermission(job))
-				throw UWSExceptionFactory.readPermissionDenied(user, false, job.getJobId());
+				throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.readPermissionDenied(user, false, job.getJobId()));
 		}
 
 		return job;
@@ -407,14 +410,17 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 				return ownerJobs.get(user).values().iterator();
 			else
 				return new Iterator<UWSJob>(){
+					@Override
 					public boolean hasNext(){
 						return false;
 					}
 
+					@Override
 					public UWSJob next(){
 						return null;
 					}
 
+					@Override
 					public void remove(){
 						;
 					}
@@ -427,6 +433,7 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	 * 
 	 * @see java.lang.Iterable#iterator()
 	 */
+	@Override
 	public final Iterator<UWSJob> iterator(){
 		return jobsList.values().iterator();
 	}
@@ -525,39 +532,35 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 
 			// Check the WRITE permission of the owner of this job:
 			if (owner != null && !owner.hasWritePermission(this))
-				throw UWSExceptionFactory.writePermissionDenied(owner, true, getName());
-
-			try{
-				// Set its job list:
-				j.setJobList(this);
-
-				// Add the job to the jobs list:
-				jobsList.put(j.getJobId(), j);
-				if (owner != null){
-					// Index also this job in function of its owner:
-					if (!ownerJobs.containsKey(owner))
-						ownerJobs.put(owner, new LinkedHashMap<String,UWSJob>());
-					ownerJobs.get(owner).put(j.getJobId(), j);
-				}
+				throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(owner, true, getName()));
+
+			// Set its job list:
+			j.setJobList(this);
+
+			// Add the job to the jobs list:
+			jobsList.put(j.getJobId(), j);
+			if (owner != null){
+				// Index also this job in function of its owner:
+				if (!ownerJobs.containsKey(owner))
+					ownerJobs.put(owner, new LinkedHashMap<String,UWSJob>());
+				ownerJobs.get(owner).put(j.getJobId(), j);
+			}
 
-				// Save the owner jobs list:
-				if (owner != null && uws.getBackupManager() != null && j.getRestorationDate() == null)
-					uws.getBackupManager().saveOwner(j.getOwner());
+			// Save the owner jobs list:
+			if (owner != null && uws.getBackupManager() != null && j.getRestorationDate() == null)
+				uws.getBackupManager().saveOwner(j.getOwner());
 
-				// Add it to the destruction manager:
-				destructionManager.update(j);
+			// Add it to the destruction manager:
+			destructionManager.update(j);
 
-				// Execute the job if asked in the additional parameters:
-				j.applyPhaseParam(null);
+			// Execute the job if asked in the additional parameters:
+			j.applyPhaseParam(null);	// Note: can not throw an exception since no user is specified (so, no permission check is done).
 
-				// Log the "creation" of the job:
-				if (j.getRestorationDate() == null)
-					getLogger().jobCreated(j);
+			// Log the "creation" of the job:
+			if (j.getRestorationDate() == null)
+				getLogger().logJob(LogLevel.INFO, j, "CREATED", "Job \"" + j.getJobId() + "\" successfully created and added in the job list \"" + getName() + "\".", null);
 
-				return j.getJobId();
-			}catch(IllegalStateException e){
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Impossible to add the job " + j.getJobId() + " into the jobs list " + getName() + " !");
-			}
+			return j.getJobId();
 		}
 	}
 
@@ -638,7 +641,7 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 				uws.getBackupManager().saveOwner(destroyedJob.getOwner());
 
 			// Log this job destruction:
-			getLogger().jobDestroyed(destroyedJob, this);
+			getLogger().logJob(LogLevel.INFO, destroyedJob, "DESTROY", "The job \"" + destroyedJob.getJobId() + "\" has been removed from the job list \"" + name + "\".", null);
 
 			return true;
 		}
@@ -660,10 +663,10 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	public boolean destroyJob(final String jobId, final JobOwner user) throws UWSException{
 		if (user != null){
 			if (!user.hasWritePermission(this))
-				throw UWSExceptionFactory.writePermissionDenied(user, true, getName());
+				throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, getName()));
 			UWSJob job = getJob(jobId);
 			if (job != null && job.getOwner() != null && !user.equals(job.getOwner()) && !user.hasWritePermission(job))
-				throw UWSExceptionFactory.writePermissionDenied(user, false, job.getJobId());
+				throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, false, jobId));
 		}
 		return destroyJob(jobId);
 	}
@@ -704,7 +707,7 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 		if (owner == null)
 			clear();
 		else if (!owner.hasWritePermission(this))
-			throw UWSExceptionFactory.writePermissionDenied(owner, true, getName());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(owner, true, getName()));
 		else{
 			if (ownerJobs.containsKey(owner)){
 				ArrayList<String> jobIDs = new ArrayList<String>(ownerJobs.get(owner).keySet());
@@ -719,9 +722,9 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 	/* INHERITED METHODS */
 	/* ***************** */
 	@Override
-	public String serialize(UWSSerializer serializer, JobOwner user) throws UWSException{
+	public String serialize(UWSSerializer serializer, JobOwner user) throws UWSException, Exception{
 		if (user != null && !user.hasReadPermission(this))
-			throw UWSExceptionFactory.readPermissionDenied(user, true, getName());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, getName()));
 
 		return serializer.getJobList(this, user, true);
 	}
diff --git a/src/uws/job/JobPhase.java b/src/uws/job/JobPhase.java
index cf0a270fb25dc19c1cd85013e6fbeea434fafc36..1de05bbbd0b4428b265fdcc59ba75db0b54c2882 100644
--- a/src/uws/job/JobPhase.java
+++ b/src/uws/job/JobPhase.java
@@ -16,7 +16,8 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.Serializable;
@@ -28,8 +29,8 @@ import uws.UWSExceptionFactory;
  * An instance of this class represents the current execution phase of a given job,
  * and it describes the transitions between the different phases.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see ExecutionPhase
  * @see UWSJob
@@ -48,11 +49,11 @@ public class JobPhase implements Serializable {
 	 * 
 	 * @param j				The job whose the execution phase must be represented by the built JobPhase instance.
 	 * 
-	 * @throws UWSException	If the given job is <i>null</i>.
+	 * @throws NullPointerException	If the given job is <i>null</i>.
 	 */
-	public JobPhase(UWSJob j) throws UWSException{
+	public JobPhase(UWSJob j) throws NullPointerException{
 		if (j == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job instance ! => impossible to build a JobPhase instance.");
+			throw new NullPointerException("Missing job instance ! => impossible to build a JobPhase instance.");
 		job = j;
 	}
 
@@ -88,12 +89,16 @@ public class JobPhase implements Serializable {
 	}
 
 	/**
-	 * Lets changing the current phase of the associated job considering or not the order of execution phases.
+	 * <p>Lets changing the current phase of the associated job considering or not the order of execution phases.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	If the given phase is <i>null</i>, nothing is done.
+	 * </i></p>
 	 * 
 	 * @param p				The new phase.
 	 * @param force			<i>true</i> to ignore the phases order, <i>false</i> otherwise.
 	 * 
-	 * @throws UWSException	If the given phase is <i>null</i> or if the phase transition is forbidden.
+	 * @throws UWSException	If the phase transition is forbidden.
 	 * 
 	 * @see #setPendingPhase(boolean)
 	 * @see #setQueuedPhase(boolean)
@@ -107,7 +112,7 @@ public class JobPhase implements Serializable {
 	 */
 	public void setPhase(ExecutionPhase p, boolean force) throws UWSException{
 		if (p == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect phase ! => The phase of a job can not be set to NULL !");
+			return;
 
 		// Check that the given phase follows the imposed phases order:
 		switch(p){
@@ -151,7 +156,7 @@ public class JobPhase implements Serializable {
 	 */
 	protected void setPendingPhase(boolean force) throws UWSException{
 		if (!force && phase != ExecutionPhase.PENDING && phase != ExecutionPhase.UNKNOWN)
-			throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.PENDING);
+			throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.PENDING));
 
 		phase = ExecutionPhase.PENDING;
 	}
@@ -168,7 +173,7 @@ public class JobPhase implements Serializable {
 			phase = ExecutionPhase.QUEUED;
 		else{
 			if (phase != ExecutionPhase.QUEUED && phase != ExecutionPhase.HELD && phase != ExecutionPhase.PENDING && phase != ExecutionPhase.UNKNOWN)
-				throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.QUEUED);
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.QUEUED));
 
 			phase = ExecutionPhase.QUEUED;
 		}
@@ -186,7 +191,7 @@ public class JobPhase implements Serializable {
 			phase = ExecutionPhase.EXECUTING;
 		else{
 			if (phase != ExecutionPhase.EXECUTING && phase != ExecutionPhase.SUSPENDED && phase != ExecutionPhase.PENDING && phase != ExecutionPhase.QUEUED && phase != ExecutionPhase.UNKNOWN)
-				throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.EXECUTING);
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.EXECUTING));
 
 			phase = ExecutionPhase.EXECUTING;
 		}
@@ -204,7 +209,7 @@ public class JobPhase implements Serializable {
 			phase = ExecutionPhase.COMPLETED;
 		else{
 			if (phase != ExecutionPhase.COMPLETED && phase != ExecutionPhase.EXECUTING && phase != ExecutionPhase.UNKNOWN)
-				throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.COMPLETED);
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.COMPLETED));
 
 			phase = ExecutionPhase.COMPLETED;
 		}
@@ -222,7 +227,7 @@ public class JobPhase implements Serializable {
 			phase = ExecutionPhase.ABORTED;
 		else{
 			if (phase == ExecutionPhase.COMPLETED || phase == ExecutionPhase.ERROR)
-				throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.ABORTED);
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.ABORTED));
 
 			phase = ExecutionPhase.ABORTED;
 		}
@@ -240,7 +245,7 @@ public class JobPhase implements Serializable {
 			phase = ExecutionPhase.ERROR;
 		else{
 			if (phase == ExecutionPhase.COMPLETED || phase == ExecutionPhase.ABORTED)
-				throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.ERROR);
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.ERROR));
 
 			phase = ExecutionPhase.ERROR;
 		}
@@ -255,7 +260,7 @@ public class JobPhase implements Serializable {
 	 */
 	protected void setHeldPhase(boolean force) throws UWSException{
 		if (!force && phase != ExecutionPhase.HELD && phase != ExecutionPhase.PENDING && phase != ExecutionPhase.UNKNOWN)
-			throw UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.HELD);
+			throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(job.getJobId(), phase, ExecutionPhase.HELD));
 		phase = ExecutionPhase.HELD;
 	}
 
diff --git a/src/uws/job/JobThread.java b/src/uws/job/JobThread.java
index 0b80bd60c1be6fc663e1dbcd5d394ab5c842a1e0..1e979dbd481eb5a3738f7c51f99644975aad2046 100644
--- a/src/uws/job/JobThread.java
+++ b/src/uws/job/JobThread.java
@@ -16,7 +16,8 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -26,6 +27,8 @@ import java.util.Date;
 import uws.UWSException;
 import uws.UWSToolBox;
 import uws.service.file.UWSFileManager;
+import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <P>An instance of this class is a thread dedicated to a job execution.</P>
@@ -55,8 +58,8 @@ import uws.service.file.UWSFileManager;
  * 	<li>an {@link InterruptedException}: the method {@link UWSJob#abort()} is called.</li>
  * </ul>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see UWSJob#start()
  * @see UWSJob#abort()
@@ -104,8 +107,6 @@ public abstract class JobThread extends Thread {
 	 */
 	public JobThread(UWSJob j, String task) throws UWSException{
 		super(tg, j.getJobId());
-		if (j == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job instance ! => impossible to build a JobThread instance.");
 
 		job = j;
 		taskDescription = task;
@@ -225,7 +226,7 @@ public abstract class JobThread extends Thread {
 			setError(error);
 
 		}catch(IOException ioe){
-			job.getLogger().error("The stack trace of a UWSException (job ID: " + job.getJobId() + " ; error message: \"" + ue.getMessage() + "\") had not been written !", ioe);
+			job.getLogger().logThread(LogLevel.ERROR, this, "SET_ERROR", "The stack trace of a UWSException had not been written !", ioe);
 			setError(new ErrorSummary(ue.getMessage(), ue.getUWSErrorType()));
 		}
 	}
@@ -361,8 +362,10 @@ public abstract class JobThread extends Thread {
 			finished = false;
 		}
 
+		UWSLog logger = job.getLogger();
+
 		// Log the start of this thread:
-		job.getLogger().threadStarted(this, taskDescription);
+		logger.logThread(LogLevel.INFO, this, "START", "Thread \"" + getId() + "\" started.", null);
 
 		try{
 			try{
@@ -378,7 +381,7 @@ public abstract class JobThread extends Thread {
 				if (!job.stopping)
 					job.abort();
 				// Log the abortion:
-				job.getLogger().threadInterrupted(this, taskDescription, ex);
+				logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getId() + "\" cancelled.", null);
 			}
 			return;
 
@@ -399,25 +402,22 @@ public abstract class JobThread extends Thread {
 			// Publish the error if any has occurred:
 			if (lastError != null){
 				// Log the error:
-				job.getLogger().threadInterrupted(this, taskDescription, lastError);
+				logger.logThread(LogLevel.ERROR, this, "END", "Thread \"" + getId() + "\" ended with an error.", lastError);
 				// Set the error into the job:
 				try{
 					setError(lastError);
 				}catch(UWSException ue){
 					try{
-						job.getLogger().error("[JobThread] LEVEL 1 -> Problem in JobThread.setError(UWSException), while setting the execution error of the job " + job.getJobId(), ue);
+						logger.logThread(LogLevel.ERROR, this, "SET_ERROR", "[1st Attempt] Problem in JobThread.setError(UWSException), while setting the execution error of the job " + job.getJobId() + ". A last attempt will be done.", ue);
 						setError(new ErrorSummary((lastError.getCause() != null) ? lastError.getCause().getMessage() : lastError.getMessage(), lastError.getUWSErrorType()));
 					}catch(UWSException ue2){
-						job.getLogger().error("[JobThread] LEVEL 2 -> Problem in JobThread.setError(ErrorSummary), while setting the execution error of the job " + job.getJobId(), ue2);
-						try{
-							setError(new ErrorSummary(lastError.getMessage(), ErrorType.FATAL));
-						}catch(UWSException ue3){
-							job.getLogger().error("[JobThread] LEVEL 3 -> Problem in JobThread.setError(ErrorSummary), while setting the execution error of the job " + job.getJobId(), ue3);
-						}
+						logger.logThread(LogLevel.ERROR, this, "SET_ERROR", "[2nd and last Attempt] Problem in JobThread.setError(ErrorSummary), while setting the execution error of the job " + job.getJobId() + ". This error can not be reported to the user, but it will be reported in the log in the JOB context.", ue2);
+						// Note: no need of a level 3: if the second attempt fails, it means the job is in a wrong phase and no error summary can never be set ; further attempt won't change anything!
+						logger.logJob(LogLevel.ERROR, job, "EXECUTING", "An error has interrupted the execution of the job \"" + job.getJobId() + "\". Here is its summary: " + lastError.getMessage(), lastError);
 					}
 				}
 			}else
-				job.getLogger().threadFinished(this, taskDescription);
+				logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getId() + "\" successfully ended.", null);
 		}
 	}
 }
diff --git a/src/uws/job/Result.java b/src/uws/job/Result.java
index 1361bac8fb86e5b5d91f8d0c9d899a9c14bb0dae..aa4386cedbd3748a60e24b8e1768e0c8afde59ea 100644
--- a/src/uws/job/Result.java
+++ b/src/uws/job/Result.java
@@ -16,21 +16,20 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.UWSException;
-
 import uws.job.serializer.UWSSerializer;
 import uws.job.user.JobOwner;
-
 import uws.service.UWSUrl;
 
 /**
  * This class gives a short description (mainly an ID and a URL) of a job result.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class Result extends SerializableUWSObject {
 	private static final long serialVersionUID = 1L;
@@ -257,7 +256,7 @@ public class Result extends SerializableUWSObject {
 	/* INHERITED METHODS */
 	/* ***************** */
 	@Override
-	public String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException{
+	public String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException, Exception{
 		return serializer.getResult(this, true);
 	}
 
diff --git a/src/uws/job/SerializableUWSObject.java b/src/uws/job/SerializableUWSObject.java
index 3fc7c493be3a0ad5783089f27232821b16401a29..f0c45d826bfb8f56f2e458f35c24887f20419adf 100644
--- a/src/uws/job/SerializableUWSObject.java
+++ b/src/uws/job/SerializableUWSObject.java
@@ -16,7 +16,8 @@ package uws.job;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -25,7 +26,6 @@ import java.io.Serializable;
 import javax.servlet.ServletOutputStream;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.serializer.UWSSerializer;
 import uws.job.serializer.XMLSerializer;
 import uws.job.user.JobOwner;
@@ -36,8 +36,8 @@ import uws.job.user.JobOwner;
  * <P>The {@link SerializableUWSObject#serialize(UWSSerializer, JobOwner)} method must be implemented. It is the most important method of this class
  * because it returns a serialized representation of this UWS object.</P>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 01/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public abstract class SerializableUWSObject implements Serializable {
 	private static final long serialVersionUID = 1L;
@@ -49,11 +49,11 @@ public abstract class SerializableUWSObject implements Serializable {
 	 * 
 	 * @return				The serialized representation of this object.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an unexpected error during the serialization.
 	 * 
 	 * @see #serialize(UWSSerializer, String)
 	 */
-	public String serialize(UWSSerializer serializer) throws UWSException{
+	public String serialize(UWSSerializer serializer) throws Exception{
 		return serialize(serializer, null);
 	}
 
@@ -66,9 +66,10 @@ public abstract class SerializableUWSObject implements Serializable {
 	 * 
 	 * @return					The serialized representation of this object.
 	 * 
-	 * @throws UWSException		If there is an error during the serialization.
+	 * @throws UWSException		If the owner is not allowed to see the content of the serializable object. 
+	 * @throws Exception		If there is any other error during the serialization.
 	 */
-	public abstract String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException;
+	public abstract String serialize(UWSSerializer serializer, JobOwner owner) throws UWSException, Exception;
 
 	/**
 	 * Serializes the whole object in the given output stream and thanks to the given serializer.
@@ -80,7 +81,7 @@ public abstract class SerializableUWSObject implements Serializable {
 	 * 
 	 * @see #serialize(ServletOutputStream, UWSSerializer, String)
 	 */
-	public void serialize(ServletOutputStream output, UWSSerializer serializer) throws UWSException{
+	public void serialize(ServletOutputStream output, UWSSerializer serializer) throws Exception{
 		serialize(output, serializer, null);
 	}
 
@@ -92,25 +93,21 @@ public abstract class SerializableUWSObject implements Serializable {
 	 * @param serializer	The serializer to use.
 	 * @param ownerId		The ID of the current ID.
 	 * 
-	 * @throws UWSException	If the given ouput stream is <i>null</i>,
-	 * 						or if there is an error during the serialization,
-	 * 						or if there is an error while writing in the given stream.
+	 * @throws UWSException		If the owner is not allowed to see the content of the serializable object.
+	 * @throws IOException		If there is an error while writing in the given stream. 
+	 * @throws Exception		If there is any other error during the serialization.
 	 * 
 	 * @see #serialize(UWSSerializer, String)
 	 */
-	public void serialize(ServletOutputStream output, UWSSerializer serializer, JobOwner owner) throws UWSException{
+	public void serialize(ServletOutputStream output, UWSSerializer serializer, JobOwner owner) throws UWSException, IOException, Exception{
 		if (output == null)
-			throw UWSExceptionFactory.missingOutputStream("impossible to serialize {" + toString() + "}.");
+			throw new NullPointerException("Missing serialization output stream!");
 
-		try{
-			String serialization = serialize(serializer, owner);
-			if (serialization != null){
-				output.print(serialization);
-				output.flush();
-			}else
-				throw UWSExceptionFactory.incorrectSerialization("NULL", "{" + toString() + "}");
-		}catch(IOException ex){
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ex, "IOException => impossible to serialize {" + toString() + "} !");
-		}
+		String serialization = serialize(serializer, owner);
+		if (serialization != null){
+			output.print(serialization);
+			output.flush();
+		}else
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect serialization value (=NULL) ! => impossible to serialize " + toString() + ".");
 	}
 }
diff --git a/src/uws/job/UWSJob.java b/src/uws/job/UWSJob.java
index 24b24e8bf235b2fe0c8536baed60d1969724281f..088fbfdf98420f315a1e07037148e3907f8f1357 100644
--- a/src/uws/job/UWSJob.java
+++ b/src/uws/job/UWSJob.java
@@ -45,6 +45,7 @@ import uws.service.UWSFactory;
 import uws.service.UWSUrl;
 import uws.service.file.UWSFileManager;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <h3>Brief description</h3>
@@ -117,7 +118,7 @@ import uws.service.log.UWSLog;
  * </ul>
  * 
  * @author	Gr&eacute;gory Mantelet (CDS;ARI)
- * @version	4.1 (04/2014)
+ * @version	4.1 (08/2014)
  */
 public class UWSJob extends SerializableUWSObject {
 	private static final long serialVersionUID = 1L;
@@ -205,11 +206,6 @@ public class UWSJob extends SerializableUWSObject {
 	/** The jobs list which is supposed to managed this job. */
 	private JobList myJobList = null;
 
-	/* The name/label that the job creator uses to identify this job.<BR />
-	 * <i><u>Note:</u> this is distinct from the Job Identifier that the UWS system itself
-	 * assigns to each job ({@link #jobId}). <u>It may not be unique !</u></i> *
-	protected String runId = null;*/
-
 	/**
 	 * <p>The current phase of the job.</p>
 	 * <i><u>Remember:</u> A job is treated as a state machine thanks to this attribute.
@@ -242,37 +238,11 @@ public class UWSJob extends SerializableUWSObject {
 	/** The time at which the job execution ended. */
 	private Date endTime = null;
 
-	/*
-	 * <p>This is the duration (in seconds) for which the job shall run.</p>
-	 * <i><u>Notes:</u>
-	 * <ul>
-	 * 	<li>An execution duration of 0 ({@link #UNLIMITED_DURATION}) implies unlimited execution duration.</li>
-	 * 	<li>When a job is created, the service sets the initial execution duration.</li>
-	 * 	<li>When the execution duration has been exceeded the service should automatically abort the job,
-	 * 	which has the same effect as when a manual "Abort" is requested.</li>
-	 * </ul></i> *
-	private long executionDuration = UNLIMITED_DURATION;
-
-	/* <p>This represents the instant when the job shall be destroyed.</p>
-	 * <i><u>Notes:</u> Destroying a job implies:
-	 * <ul>
-	 * 	<li>if the job is still executing, the execution is aborted</li>
-	 * 	<li>any results from the job are destroyed and storage reclaimed</li>
-	 * 	<li>the service forgets that the job existed.</li>
-	 * </ul>
-	 * <p>The Destruction time should be viewed as a measure of the amount of time
-	 * that a service is prepared to allocated storage for a job - typically this will be a longer duration
-	 * that the amount of CPU time that a service would allocate.</p></i> *
-	private Date destructionTime = null;*/
-
 	/** <p>This error summary gives a human-readable error message for the underlying job.</p>
 	 * <i><u>Note:</u> This object is intended to be a detailed error message, and consequently,
 	 * might be a large piece of text such as a stack trace.</i> */
 	protected ErrorSummary errorSummary = null;
 
-	/* This is an enumeration of the other Job parameters (given in POST queries). *
-	protected Map<String, Object> additionalParameters;*/
-
 	/** This is a list of all results of this job. */
 	protected Map<String,Result> results;
 
@@ -300,13 +270,11 @@ public class UWSJob extends SerializableUWSObject {
 	 * <p><i><u>Note:</u> if the parameter {@link UWSJob#PARAM_PHASE} (</i>phase<i>) is given with the value {@link UWSJob#PHASE_RUN}
 	 * the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p>
 	 * 
-	 * @param params		UWS standard and non-standard parameters.
-	 * 
-	 * @throws UWSException	If a parameter is incorrect.
+	 * @param params	UWS standard and non-standard parameters.
 	 * 
 	 * @see UWSJob#AbstractJob(String, Map)
 	 */
-	public UWSJob(final UWSParameters params) throws UWSException{
+	public UWSJob(final UWSParameters params){
 		this(null, params);
 	}
 
@@ -316,27 +284,19 @@ public class UWSJob extends SerializableUWSObject {
 	 * <p><i><u>Note:</u> if the parameter {@link #PARAM_PHASE} (</i>phase<i>) is given with the value {@link #PHASE_RUN}
 	 * the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p>
 	 * 
-	 * @param owner			Job.owner ({@link #PARAM_OWNER}).
-	 * @param params		UWS standard and non-standard parameters.
-	 * 
-	 * @throws UWSException	If a parameter is incorrect.
+	 * @param owner		Job.owner ({@link #PARAM_OWNER}).
+	 * @param params	UWS standard and non-standard parameters.
 	 * 
 	 * @see #loadDefaultParams(Map)
 	 * @see #loadAdditionalParams()
 	 */
-	public UWSJob(JobOwner owner, final UWSParameters params) throws UWSException{
+	public UWSJob(JobOwner owner, final UWSParameters params){
 		this.owner = owner;
 
 		phase = new JobPhase(this);
 
-		//additionalParameters = new HashMap<String,Object>();
 		results = new HashMap<String,Result>();
 
-		/*Map<String,Object> others = loadDefaultParams(lstParam);
-		if (others != null){
-			additionalParameters.putAll(others);
-			loadAdditionalParams();
-		}*/
 		inputParams = params;
 		inputParams.init();
 
@@ -363,18 +323,16 @@ public class UWSJob extends SerializableUWSObject {
 	 * @param results		Its results (if phase=COMPLETED).
 	 * @param error			Its error (if phase=ERROR).
 	 * 
-	 * @throws UWSException	If the given ID is <i>null</i> or if another error occurs while building this job.
+	 * @throws NullPointerException	If the given ID is NULL.
 	 */
-	public UWSJob(final String jobID, final JobOwner owner, final UWSParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws UWSException{
+	public UWSJob(final String jobID, final JobOwner owner, final UWSParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws NullPointerException{
 		if (jobID == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job ID => impossible to build a Job without a valid ID !");
+			throw new NullPointerException("Missing job ID => impossible to build a Job without a valid ID!");
 
 		this.jobId = jobID;
 		this.owner = owner;
 
 		this.quote = quote;
-		/*this.destructionTime = destruction;
-		this.executionDuration = (maxDuration<0)?UNLIMITED_DURATION:maxDuration;*/
 
 		if (startTime > 0)
 			this.startTime = new Date(startTime);
@@ -396,13 +354,6 @@ public class UWSJob extends SerializableUWSObject {
 		inputParams = params;
 		params.init();
 
-		/*this.additionalParameters = new HashMap<String,Object>();
-		Map<String,Object> others = loadDefaultParams(lstParams);
-		if (others != null){
-			additionalParameters.putAll(others);
-			loadAdditionalParams();
-		}*/
-
 		ExecutionPhase p = ExecutionPhase.PENDING;
 		if (startTime > 0 && endTime > 0){
 			if (this.results.isEmpty() && this.errorSummary == null)
@@ -412,8 +363,13 @@ public class UWSJob extends SerializableUWSObject {
 			else if (this.errorSummary != null)
 				p = ExecutionPhase.ERROR;
 		}
-		if (phase != null)
-			setPhase(p, true);
+		if (phase != null){
+			try{
+				setPhase(p, true);
+			}catch(UWSException ue){
+				// Can never append because the "force" parameter is true! 
+			}
+		}
 
 		restorationDate = new Date();
 	}
@@ -429,7 +385,7 @@ public class UWSJob extends SerializableUWSObject {
 	 * 
 	 * @return	A unique job identifier.
 	 */
-	protected String generateJobId() throws UWSException{
+	protected String generateJobId(){
 		String generatedId = System.currentTimeMillis() + "A";
 		if (lastId != null){
 			while(lastId.equals(generatedId))
@@ -439,133 +395,6 @@ public class UWSJob extends SerializableUWSObject {
 		return generatedId;
 	}
 
-	/*
-	 * <p>Loads the given parameters: all known parameters (with write access) are updated
-	 * whereas others are returned in a new map in which all keys are in lower case.</p>
-	 * 
-	 * <p><b><u>Important:</u> The phase parameter is NEVER managed here and is ALWAYS added immediately in the additional parameters attribute !</b></p>
-	 * 
-	 * <p><i><u>Note:</u> UWS parameters with write access are:
-	 * <ul>
-	 * 	<li>{@link UWSJob#PARAM_RUN_ID RUN_ID}</li>
-	 * 	<li>{@link UWSJob#PARAM_EXECUTION_DURATION EXECUTION_DURATION}</li>
-	 * 	<li>{@link UWSJob#PARAM_DESTRUCTION_TIME DESTRUCTION_TIME}</li>
-	 * 	<li>{@link UWSJob#PARAM_PHASE PHASE} if equals to {@link UWSJob#PHASE_RUN} or {@link UWSJob#PHASE_ABORT}</li>
-	 * </ul></i></p>
-	 * 
-	 * <p><i><u>Note:</u> To check more DEFAULT parameters you just have to:
-	 * <ol>
-	 * 	<li>override the function {@link UWSJob#loadDefaultParams(Map)}</li>
-	 * 	<li>call super.loadParams(Map)</li>
-	 * 	<li>add your own checking (do not forget to update the returned map and to return it).</li>
-	 * </ol></i></p>
-	 * 
-	 * @param lstParam	The list of parameters to load <i>(UWS - included PHASE - and additional parameters)</i>.
-	 * 
-	 * @return			<ul>
-	 * 						<li>a new map with all the parameters that have not been loaded <i>(additional parameters and/or not known UWS parameter and/or the PHASE parameter)</i></li>
-	 * 						<li>or an empty map</li>
-	 * 						<li>or <i>null</i> if the job is executing or is ended <i>(actually: all phase except PENDING)</i></li>
-	 * 					</ul>
-	 * 
-	 * @throws UWSException	If a given UWS parameter is not correct.
-	 *
-	@SuppressWarnings("unchecked")
-	protected Map<String, Object> loadDefaultParams(final Map<String, Object> lstParam) throws UWSException {
-		if (lstParam == null)
-			return new HashMap<String,Object>();
-
-		// Forbids the parameter modification if the job is already finished:
-		if (isFinished())
-			throw UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), null);
-
-		// Build a new map for all the ignored parameters (that's to say all non UWS parameters):
-		HashMap<String,Object> otherParams = new HashMap<String,Object>();
-
-		Set<Map.Entry<String, Object>> paramSet = lstParam.entrySet();
-		String paramName = null;
-		Object paramValue = null;
-		for(Map.Entry<String, Object> param : paramSet){
-			paramName = param.getKey();
-			paramValue = param.getValue();
-
-			if (paramName == null || paramValue == null)
-				continue;
-
-			// PHASE:
-			if (paramName.equalsIgnoreCase(PARAM_PHASE)){
-				if (!phase.isFinished())
-					otherParams.put(PARAM_PHASE, paramValue);
-
-			}// PARAMETERS:
-			else if (paramName.equalsIgnoreCase(PARAM_PARAMETERS)){
-				if (paramValue instanceof Map){
-					Map m = (Map)paramValue;
-					for(Map.Entry entry : (Set<Map.Entry>)m.entrySet()){
-						if (entry.getKey() instanceof String)
-							otherParams.put((String)entry.getKey(), entry.getValue());
-					}
-				}
-
-			}// RUN ID:
-			else if (paramName.equalsIgnoreCase(PARAM_RUN_ID)){
-				if (paramValue instanceof String)
-					setRunId((String)paramValue);
-				else
-					throw UWSExceptionFactory.badFormat(getJobId(), "RUN ID", paramValue.toString(), paramValue.getClass().getName(), "A String instance");
-
-			}// EXECUTION DURATION:
-			else if (paramName.equalsIgnoreCase(PARAM_EXECUTION_DURATION)){
-				if (isRunning())
-					throw UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "EXECUTION DURATION");
-
-				if (!(paramValue instanceof String) && !(paramValue instanceof Long))
-					throw UWSExceptionFactory.badFormat(getJobId(), "EXECUTION DURATION", paramValue.toString(), paramValue.getClass().getName(), "A Long or a String instance.");
-
-				try{
-					setExecutionDuration((paramValue instanceof String) ? Long.parseLong((String)paramValue) : (Long)paramValue);
-				}catch(NumberFormatException ex){
-					setExecutionDuration(0);
-					throw UWSExceptionFactory.badFormat(getJobId(), "EXECUTION DURATION", paramValue.toString(), paramValue.getClass().getName(), "A long integer value");
-				}
-
-			}// DESTRUCTION TIME:
-			else if (paramName.equalsIgnoreCase(PARAM_DESTRUCTION_TIME)){
-				if (isRunning()){
-					try{
-						throw UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "DESTRUCTION TIME");
-					}catch(UWSException ue){
-						ue.printStackTrace();
-						System.out.println("    => PARAM NAME = \""+paramName+"\" ; PARAM VALUE = "+paramValue);
-						throw ue;
-					}
-				}
-
-				if (!(paramValue instanceof String) && !(paramValue instanceof Date))
-					throw UWSExceptionFactory.badFormat(getJobId(), paramName, paramValue.toString(), paramValue.getClass().getName(), "A Date or a String instance.");
-
-				try {
-					if (paramValue instanceof String){
-						String time = (String)paramValue;
-						if (time != null && !time.trim().isEmpty())
-							setDestructionTime(dateFormat.parse(time));
-					}else
-						setDestructionTime((Date)paramValue);
-				} catch (ParseException e) {
-					throw UWSExceptionFactory.badFormat(getJobId(), paramName, paramValue.toString(), null, ((dateFormat instanceof SimpleDateFormat)?(((SimpleDateFormat)dateFormat).toPattern()):"A valid date (format: ???)."));
-				}
-
-			}// READ-ONLY PARAMETERS:
-			else if (paramName.equalsIgnoreCase(PARAM_JOB_ID) && paramName.equalsIgnoreCase(PARAM_QUOTE) && paramName.equalsIgnoreCase(PARAM_START_TIME) && paramName.equalsIgnoreCase(PARAM_END_TIME) && paramName.equalsIgnoreCase(PARAM_RESULTS) && paramName.equalsIgnoreCase(PARAM_ERROR_SUMMARY)){
-				continue;
-
-			}// ADDITIONAL PARAMETERS
-			else
-				otherParams.put(paramName, paramValue);
-		}
-		return otherParams;
-	}*/
-
 	/**
 	 * <p>Gets the value of the specified parameter.</p>
 	 * 
@@ -598,25 +427,6 @@ public class UWSJob extends SerializableUWSObject {
 			return inputParams.get(name);
 	}
 
-	/*
-	 * <p>Method called when updating one or several parameters using the functions {@link #addOrUpdateParameter(String, String)} and
-	 * {@link #addOrUpdateParameters(Map)} or at the job creation.</p>
-	 * 
-	 * <p><b>It is useful if you need to check or to process all or a part of the additional parameters stored in {@link #additionalParameters}.</b></p>
-	 * 
-	 * <p><i><b>By default</b> this function does nothing and always return </i>true<i>.</i></p>
-	 * 
-	 * @return					<i>true</i> if all required additional parameters have been successfully updated, <i>false</i> otherwise.
-	 * 
-	 * @throws UWSException		If an error occurred during the updating of one parameter.
-	 * 
-	 * @see #addOrUpdateParameter(String, String)
-	 * @see #addOrUpdateParameters(Map)
-	 *
-	protected boolean loadAdditionalParams() throws UWSException {
-		return true;
-	}*/
-
 	/**
 	 * <p>Looks for an additional parameters which corresponds to the Execution Phase. If it exists and:</p>
 	 * <ul>
@@ -627,7 +437,7 @@ public class UWSJob extends SerializableUWSObject {
 	 * 
 	 * @param user			The user who asks to apply the phase parameter (start/abort). (may be NULL)
 	 * 
-	 * @throws UWSException	If it is impossible to change the Execution Phase
+	 * @throws UWSException	If it is impossible the state of this job (into EXECUTING or ABORTED)
 	 * 						or if the given user is not allowed to execute this job.
 	 * 
 	 * @see UWSParameters#hasInputPhase()
@@ -642,12 +452,12 @@ public class UWSJob extends SerializableUWSObject {
 				if (inputPhase.equalsIgnoreCase(PHASE_RUN)){
 					// Forbids the execution if the user has not the required permission:
 					if (user != null && !user.equals(owner) && !user.hasExecutePermission(this))
-						throw UWSExceptionFactory.executePermissionDenied(user, getJobId());
+						throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.executePermissionDenied(user, jobId));
 					start();
 				}else if (inputPhase.equalsIgnoreCase(PHASE_ABORT)){
 					// Forbids the execution if the user has not the required permission:
 					if (user != null && !user.equals(owner) && !user.hasExecutePermission(this))
-						throw UWSExceptionFactory.executePermissionDenied(user, getJobId());
+						throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.executePermissionDenied(user, jobId));
 					abort();
 				}
 			}
@@ -765,6 +575,8 @@ public class UWSJob extends SerializableUWSObject {
 			ExecutionPhase oldPhase = phase.getPhase();
 			phase.setPhase(p, force);
 
+			getLogger().logJob(LogLevel.INFO, this, "CHANGE_PHASE", "The job \"\" goes from " + oldPhase + " to " + p, null);
+
 			// Notify the execution manager:
 			if (phase.isFinished() && getJobList() != null)
 				getJobList().getExecutionManager().remove(this);
@@ -840,7 +652,7 @@ public class UWSJob extends SerializableUWSObject {
 			getJobList().getUWS().getBackupManager().saveOwner(owner);
 
 		// Log the end of this job:
-		getLogger().jobFinished(this);
+		getLogger().logJob(LogLevel.INFO, this, "END", "Job \"" + jobId + "\" ended with the status " + phase, null);
 	}
 
 	/**
@@ -902,7 +714,7 @@ public class UWSJob extends SerializableUWSObject {
 				if (myJobList != null)
 					myJobList.updateDestruction(this);
 			}catch(UWSException ue){
-				;
+				getLogger().logJob(LogLevel.WARNING, this, "SET_DESTRUCTION", "Can not set the destruction time of the job \"" + getJobId() + "\" to \"" + destructionTime + "\"!", ue);
 			}
 		}
 	}
@@ -931,8 +743,11 @@ public class UWSJob extends SerializableUWSObject {
 	public final void setErrorSummary(ErrorSummary errorSummary) throws UWSException{
 		if (!isFinished())
 			this.errorSummary = errorSummary;
-		else
-			throw UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "ERROR SUMMARY");
+		else{
+			UWSException ue = new UWSException(UWSException.NOT_ALLOWED, UWSExceptionFactory.jobModificationForbidden(jobId, getPhase(), "ERROR SUMMARY"));
+			getLogger().logJob(LogLevel.ERROR, this, "SET_ERROR", "Can not set an error summary when the job is finished (or not yet started)! The current phase is: " + getPhase() + " ; the summary of the error to set is: \"" + errorSummary.message + "\".", ue);
+			throw ue;
+		}
 	}
 
 	/**
@@ -1049,7 +864,6 @@ public class UWSJob extends SerializableUWSObject {
 	 * @throws UWSException	If a parameter value is incorrect.
 	 * 
 	 * @see JobPhase#isJobUpdatable()
-	 * @see UWSJob#addOrUpdateParameters(Map)
 	 */
 	public final boolean addOrUpdateParameter(String paramName, Object paramValue) throws UWSException{
 		if (!phase.isFinished()){
@@ -1075,7 +889,7 @@ public class UWSJob extends SerializableUWSObject {
 	 * 
 	 * @throws UWSException	If a parameter value is incorrect.
 	 * 
-	 * @see #addOrUpdateParameters(Map)
+	 * @see #addOrUpdateParameters(UWSParameters, JobOwner)
 	 */
 	public boolean addOrUpdateParameters(UWSParameters params) throws UWSException{
 		return addOrUpdateParameters(params, null);
@@ -1105,7 +919,7 @@ public class UWSJob extends SerializableUWSObject {
 	public boolean addOrUpdateParameters(UWSParameters params, final JobOwner user) throws UWSException{
 		// Forbids the update if the user has not the required permission:
 		if (user != null && !user.equals(owner) && !user.hasWritePermission(this))
-			throw UWSExceptionFactory.writePermissionDenied(user, false, getJobId());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, false, getJobId()));
 
 		// Load all parameters:
 		String[] updated = inputParams.update(params);
@@ -1189,9 +1003,11 @@ public class UWSJob extends SerializableUWSObject {
 	public boolean addResult(Result res) throws UWSException{
 		if (res == null)
 			return false;
-		else if (isFinished())
-			throw UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "RESULT");
-		else{
+		else if (isFinished()){
+			UWSException ue = new UWSException(UWSException.NOT_ALLOWED, UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "RESULT"));
+			getLogger().logJob(LogLevel.ERROR, this, "ADD_RESULT", "Can not add the result \"" + res.getId() + "\" to the job \"" + getJobId() + "\": this job is already finished (or not yet started). Current phase: " + getPhase(), ue);
+			throw ue;
+		}else{
 			synchronized(results){
 				if (results.containsKey(res.getId()))
 					return false;
@@ -1228,7 +1044,7 @@ public class UWSJob extends SerializableUWSObject {
 	 * <p><i><u>note 2:</u> this job is removed from its previous job list, if there is one.</i></p>
 	 * <p><i><u>note 3:</u> this job is NOT automatically added into the new jobs list. Indeed, this function should be called by {@link JobList#addNewJob(UWSJob)}.</i></p>
 	 * 
-	 * @param jobList					Its new jobs list. <i><u>note:</u> if NULL, nothing is done !</i>
+	 * @param jobList		Its new jobs list. <i><u>note:</u> if NULL, nothing is done !</i>
 	 * 
 	 * @throws IllegalStateException	If this job is not PENDING.
 	 * 
@@ -1302,7 +1118,8 @@ public class UWSJob extends SerializableUWSObject {
 	 * 
 	 * @param useManager	<i>true</i> to let the execution manager deciding whether the job starts immediately or whether it must be put in a queue until enough resources are available, <i>false</i> to start the execution immediately.
 	 * 
-	 * @throws UWSException	If there is an error while changing the execution phase or when starting the corresponding thread.
+	 * @throws NullPointerException	If this job is not associated with a job list or the associated job list is not part of a UWS service or if no thread is created.
+	 * @throws UWSException			If there is an error while changing the execution phase or when starting the corresponding thread.
 	 * 
 	 * @see #isRunning()
 	 * @see UWSFactory#createJobThread(UWSJob)
@@ -1314,7 +1131,7 @@ public class UWSJob extends SerializableUWSObject {
 	public void start(boolean useManager) throws UWSException{
 		// This job must know its jobs list and this jobs list must know its UWS:
 		if (myJobList == null || myJobList.getUWS() == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "A UWSJob can not start if it is not part of a job list or if its job list is not part of a UWS.");
+			throw new NullPointerException("A UWSJob can not start if it is not part of a job list or if its job list is not part of a UWS.");
 
 		// If already running do nothing:
 		else if (isRunning())
@@ -1326,24 +1143,32 @@ public class UWSJob extends SerializableUWSObject {
 
 		}// Otherwise start directly the execution:
 		else{
-			// Try to change the phase:
-			setPhase(ExecutionPhase.EXECUTING);
-
-			// Create and run its corresponding thread:
+			// Create its corresponding thread:
 			thread = getFactory().createJobThread(this);
 			if (thread == null)
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing job work ! The thread created by the factory is NULL => The job can't be executed !");
-			thread.start();
-			(new JobTimeOut()).start();
+				throw new NullPointerException("Missing job work ! The thread created by the factory is NULL => The job can't be executed !");
+
+			// Change the job phase:
+			setPhase(ExecutionPhase.EXECUTING);
 
 			// Set the start time:
 			setStartTime(new Date());
 
+			// Run the job:
+			thread.start();
+			(new JobTimeOut()).start();
+
 			// Log the start of this job:
-			getLogger().jobStarted(this);
+			getLogger().logJob(LogLevel.INFO, this, "START", "Job \"" + jobId + "\" started.", null);
 		}
 	}
 
+	/**
+	 * Stop/Cancel this job when its maximum execution duration has been reached. 
+	 * 
+	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
+	 * @version 4.1 (09/2014)
+	 */
 	protected final class JobTimeOut extends Thread {
 		public JobTimeOut(){
 			super(JobThread.tg, "TimeOut_" + jobId);
@@ -1358,9 +1183,9 @@ public class UWSJob extends SerializableUWSObject {
 					if (!isFinished())
 						UWSJob.this.abort();
 				}catch(InterruptedException ie){
-					getLogger().error("Unexpected InterruptedException while waiting the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ") !", ie);
+					/* Not needed to report any interruption while waiting. */
 				}catch(UWSException ue){
-					getLogger().error("Unexpected UWSException while waiting the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ") !", ue);
+					getLogger().logJob(LogLevel.WARNING, UWSJob.this, "EXECUTING", "Unexpected error while waiting the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ue);
 				}
 			}
 		}
@@ -1419,8 +1244,9 @@ public class UWSJob extends SerializableUWSObject {
 				// Set the end time:
 				setEndTime(new Date());
 			}else if (thread == null || (thread != null && !thread.isAlive()))
-				throw UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED);
-		}
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED));
+		}else
+			getLogger().logJob(LogLevel.WARNING, this, "ABORT", "Abortion of the job \"" + getJobId() + "\" asked but not yet effective (after having waited " + waitForStop + "ms)!", null);
 	}
 
 	/**
@@ -1456,8 +1282,9 @@ public class UWSJob extends SerializableUWSObject {
 				// Set the end time:
 				setEndTime(new Date());
 			}else if (thread != null && !thread.isAlive())
-				throw UWSExceptionFactory.incorrectPhaseTransition(jobId, phase.getPhase(), ExecutionPhase.ERROR);
-		}
+				throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(jobId, phase.getPhase(), ExecutionPhase.ERROR));
+		}else
+			getLogger().logJob(LogLevel.WARNING, this, "ERROR", "Stopping of the job \"" + getJobId() + "\" with error asked but not yet effective (after having waited " + waitForStop + "ms)!", null);
 	}
 
 	/** Used by the thread to known whether the {@link #stop()} method has already been called, and so, that the job is stopping. */
@@ -1479,7 +1306,7 @@ public class UWSJob extends SerializableUWSObject {
 					try{
 						thread.join(waitForStop);
 					}catch(InterruptedException ie){
-						getLogger().error("Unexpected InterruptedException while waiting the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ") !", ie);
+						getLogger().logJob(LogLevel.WARNING, this, "END", "Unexpected InterruptedException while waiting for the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ie);
 					}
 				}
 			}
@@ -1507,18 +1334,14 @@ public class UWSJob extends SerializableUWSObject {
 			try{
 				abort();
 			}catch(UWSException e){
-				getLogger().error("Impossible to abort the job" + jobId + " => trying to stop it...", e);
+				getLogger().logJob(LogLevel.WARNING, this, "CLEAR_RESOURCES", "Impossible to abort the job \"" + jobId + "\" => trying to stop it...", e);
 				stop();
 			}
 		}
 
 		// Remove this job from its execution manager:
-		try{
-			if (getJobList() != null)
-				getJobList().getExecutionManager().remove(this);
-		}catch(UWSException ue){
-			getLogger().error("Impossible to remove the job " + jobId + " from its execution manager !", ue);
-		}
+		if (getJobList() != null)
+			getJobList().getExecutionManager().remove(this);
 
 		thread = null;
 
@@ -1527,7 +1350,7 @@ public class UWSJob extends SerializableUWSObject {
 			try{
 				getFileManager().deleteResult(r, this);
 			}catch(IOException ioe){
-				getLogger().error("Impossible to delete the file associated with the result '" + r.getId() + "' of the job " + jobId + " !", ioe);
+				getLogger().logJob(LogLevel.ERROR, this, "CLEAR_RESOURCES", "Impossible to delete the file associated with the result '" + r.getId() + "' of the job \"" + jobId + "\"!", ioe);
 			}
 		}
 
@@ -1536,9 +1359,11 @@ public class UWSJob extends SerializableUWSObject {
 			try{
 				getFileManager().deleteError(errorSummary, this);
 			}catch(IOException ioe){
-				getLogger().error("Impossible to delete the file associated with the error '" + errorSummary.message + "' of the job " + jobId + " !", ioe);
+				getLogger().logJob(LogLevel.ERROR, this, "CLEAR_RESOURCES", "Impossible to delete the file associated with the error '" + errorSummary.message + "' of the job \"" + jobId + "\"!", ioe);
 			}
 		}
+
+		getLogger().logJob(LogLevel.INFO, this, "CLEAR_RESOURCES", "Resources associated with the job \"" + getJobId() + "\" have been successfully freed.", null);
 	}
 
 	/* ******************* */
@@ -1629,7 +1454,7 @@ public class UWSJob extends SerializableUWSObject {
 		}
 
 		if (errors != null)
-			getLogger().error("Some observers of \"" + jobId + "\" can not have been updated:\n" + errors);
+			getLogger().logJob(LogLevel.WARNING, this, "NOTIFY", "Some observers of the job \"" + jobId + "\" can not have been updated:\n" + errors, null);
 	}
 
 	/* **************** */
@@ -1652,9 +1477,9 @@ public class UWSJob extends SerializableUWSObject {
 	/* SERIALIZATION */
 	/* ************* */
 	@Override
-	public String serialize(UWSSerializer serializer, JobOwner user) throws UWSException{
+	public String serialize(UWSSerializer serializer, JobOwner user) throws UWSException, Exception{
 		if (user != null && !user.equals(getOwner()) && !user.hasReadPermission(this))
-			throw UWSExceptionFactory.readPermissionDenied(user, false, getJobId());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.readPermissionDenied(user, false, getJobId()));
 
 		return serializer.getJob(this, true);
 	}
@@ -1667,11 +1492,11 @@ public class UWSJob extends SerializableUWSObject {
 	 * 
 	 * @return					The serialized job attribute (or the whole job if <i>attributes</i> is an empty array or is <i>null</i>).
 	 * 
-	 * @throws UWSException		If there is an error during the serialization.
+	 * @throws Exception		If there is an unexpected error during the serialization.
 	 * 
 	 * @see UWSSerializer#getJob(UWSJob, String[], boolean)
 	 */
-	public String serialize(String[] attributes, UWSSerializer serializer) throws UWSException{
+	public String serialize(String[] attributes, UWSSerializer serializer) throws Exception{
 		return serializer.getJob(this, attributes, true);
 	}
 
@@ -1682,30 +1507,27 @@ public class UWSJob extends SerializableUWSObject {
 	 * @param attributes		The name of the attribute to serialize (if <i>null</i>, the whole job will be serialized).
 	 * @param serializer		The serializer to use.
 	 * 
-	 * @throws UWSException		If there is an error during the serialization.
+	 * @throws Exception		If there is an unexpected error during the serialization.
 	 * 
 	 * @see #serialize(String[], UWSSerializer)
 	 */
-	public void serialize(ServletOutputStream output, String[] attributes, UWSSerializer serializer) throws UWSException{
+	public void serialize(ServletOutputStream output, String[] attributes, UWSSerializer serializer) throws UWSException, IOException, Exception{
 		String errorMsgPart = null;
 		if (attributes == null || attributes.length <= 0)
-			errorMsgPart = "the job " + toString();
+			errorMsgPart = "the job \"" + getJobId() + "\"";
 		else
-			errorMsgPart = "the given attribute \"" + errorMsgPart + "\" of {" + toString() + "}";
+			errorMsgPart = "the given attribute \"" + attributes[0] + "\" of the job \"" + getJobId() + "\"";
 
 		if (output == null)
-			throw UWSExceptionFactory.missingOutputStream("impossible to serialize " + errorMsgPart + ".");
-
-		try{
-			String serialization = serialize(attributes, serializer);
-			if (serialization == null)
-				throw UWSExceptionFactory.incorrectSerialization("NULL", errorMsgPart);
-			else{
-				output.print(serialization);
-				output.flush();
-			}
-		}catch(IOException ex){
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ex, "IOException => impossible to serialize " + errorMsgPart + ".");
+			throw new NullPointerException("Missing serialization output stream when serializing " + errorMsgPart + "!");
+
+		String serialization = serialize(attributes, serializer);
+		if (serialization == null){
+			getLogger().logJob(LogLevel.ERROR, this, "SERIALIZE", "Error while serializing " + errorMsgPart + ": NULL was returned.", null);
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect serialization value (=NULL) ! => impossible to serialize " + errorMsgPart + ".");
+		}else{
+			output.print(serialization);
+			output.flush();
 		}
 	}
 
diff --git a/src/uws/job/manager/AbstractQueuedExecutionManager.java b/src/uws/job/manager/AbstractQueuedExecutionManager.java
index 6a14c8febb20bf5c32a918c3ef59776c08a90994..49a1d0be3b1f67a92a0987f0dff3f3857f6a046d 100644
--- a/src/uws/job/manager/AbstractQueuedExecutionManager.java
+++ b/src/uws/job/manager/AbstractQueuedExecutionManager.java
@@ -16,7 +16,8 @@ package uws.job.manager;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Iterator;
@@ -26,10 +27,10 @@ import java.util.Vector;
 
 import uws.UWSException;
 import uws.UWSToolBox;
-import uws.job.ErrorType;
 import uws.job.ExecutionPhase;
 import uws.job.UWSJob;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Abstract implementation of the interface {@link ExecutionManager} which lets managing an execution queue.</p>
@@ -38,15 +39,14 @@ import uws.service.log.UWSLog;
  * 	to {@link ExecutionPhase#QUEUED}). A call to {@link #refresh()}, reads this list and tries to execute the first job of the list.
  * 	The function {@link #isReadyForExecution(UWSJob)} decides whether the first job of the queue can be executed NOW or not.
  * </p>
- * </p>
- * 	NOTE: The order of queued jobs is preserved: it is implemented by a FIFO queue.
- * </p>
+ * <p><i>Note:
+ * 	The order of queued jobs is preserved: it is implemented by a FIFO queue.
+ * </i></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public abstract class AbstractQueuedExecutionManager implements ExecutionManager {
-	private static final long serialVersionUID = 1L;
 
 	/** List of running jobs. */
 	protected Map<String,UWSJob> runningJobs;
@@ -105,6 +105,7 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	 * of the result of this function, the given job will be put in the queue or it will be executed.
 	 * 
 	 * @param 	jobToExecute
+	 * 
 	 * @return	<i>true</i> if the given job can be executed NOW (=&gt; it will be executed), <i>false</i> otherwise (=&gt; it will be put in the queue).
 	 */
 	public abstract boolean isReadyForExecution(UWSJob jobToExecute);
@@ -116,9 +117,13 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	 * <p>Removes the first queued job(s) from the queue and executes it (them)
 	 * <b>ONLY IF</b> it (they) can be executed (see {@link #isReadyForExecution(AbstractJob)}).</p>
 	 * 
-	 * <p><i><u>Note:</u> Nothing is done if there is no queue.</i></p>
+	 * <p><i>Note:
+	 * 	Nothing is done if there is no queue.
+	 * </i></p>
 	 * 
-	 * @throws UWSException	If there is an error during the phase transition of one or more jobs.
+	 * <p><i>Note:
+	 * 	If any error occurs while refreshing this manager, it SHOULD be logged using the service logger.
+	 * </i></p>
 	 * 
 	 * @see #hasQueue()
 	 * @see #isReadyForExecution(UWSJob)
@@ -127,25 +132,21 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	 * @see uws.job.manager.ExecutionManager#refresh()
 	 */
 	@Override
-	public synchronized final void refresh() throws UWSException{
+	public synchronized final void refresh(){
 		// Return immediately if no queue:
 		if (!hasQueue())
 			return;
 
-		String allMsg = null;	// the concatenation of all errors which may occur
-
 		// Start the first job of the queue while it can be executed:
+		UWSJob jobToStart;
 		while(!queuedJobs.isEmpty() && isReadyForExecution(queuedJobs.firstElement())){
+			jobToStart = queuedJobs.remove(0);
 			try{
-				startJob(queuedJobs.remove(0));
+				startJob(jobToStart);
 			}catch(UWSException ue){
-				allMsg = ((allMsg == null) ? "ERRORS THAT OCCURED WHILE REFRESHING THE EXECUTION MANAGER:" : allMsg) + "\n\t- " + ue.getMessage();
+				logger.logJob(LogLevel.ERROR, jobToStart, "START", "Can not start the job \"" + jobToStart.getJobId() + "\"! This job is not any more part of its execution manager.", ue);
 			}
 		}
-
-		// Throw one error for all jobs that can not have been executed:
-		if (allMsg != null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, allMsg, ErrorType.TRANSIENT);
 	}
 
 	/**
@@ -169,10 +170,13 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	/**
 	 * <p>Refreshes this manager and then put the given job into the queue (if it is not already into it).</p>
 	 * 
+	 * <p><i>Note:
+	 * 	If any error occurs while executing the given job, it SHOULD be logged using the service logger.
+	 * </i></p>
+	 * 
 	 * @param jobToExecute	The job to execute.
-	 * @return				The resulting execution phase of the given job ({@link ExecutionPhase#EXECUTING EXECUTING} or {@link ExecutionPhase#QUEUED QUEUED} or <i>null</i> if the given job is <i>null</i>).
 	 * 
-	 * @throws UWSException	If there is an error while changing the execution phase of the given job or if the job is already finished.
+	 * @return				The resulting execution phase of the given job ({@link ExecutionPhase#EXECUTING EXECUTING} or {@link ExecutionPhase#QUEUED QUEUED} or <i>null</i> if the given job is <i>null</i>).
 	 * 
 	 * @see #refresh()
 	 * @see AbstractJob#isRunning()
@@ -182,16 +186,12 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	 * @see uws.job.manager.ExecutionManager#execute(AbstractJob)
 	 */
 	@Override
-	public synchronized final ExecutionPhase execute(final UWSJob jobToExecute) throws UWSException{
+	public synchronized final ExecutionPhase execute(final UWSJob jobToExecute){
 		if (jobToExecute == null)
 			return null;
 
 		// Refresh the list of running jobs before all:
-		try{
-			refresh();
-		}catch(UWSException ue){
-			logger.error("Impossible to refresh the execution manager !", ue);
-		}
+		refresh();
 
 		// If the job is already running, ensure it is in the list of running jobs:
 		if (jobToExecute.isRunning())
@@ -204,12 +204,16 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 
 		}// Otherwise, change the phase to QUEUED, put it into the queue and then refresh the queue:
 		else{
-			if (jobToExecute.getPhase() != ExecutionPhase.QUEUED)
-				jobToExecute.setPhase(ExecutionPhase.QUEUED);
+			try{
+				if (jobToExecute.getPhase() != ExecutionPhase.QUEUED)
+					jobToExecute.setPhase(ExecutionPhase.QUEUED);
 
-			if (!queuedJobs.contains(jobToExecute)){
-				queuedJobs.add(jobToExecute);
-				refresh();
+				if (!queuedJobs.contains(jobToExecute)){
+					queuedJobs.add(jobToExecute);
+					refresh();
+				}
+			}catch(UWSException ue){
+				logger.logJob(LogLevel.ERROR, jobToExecute, "QUEUE", "Can not set the job \"" + jobToExecute.getJobId() + "\" in the QUEUED phase!", ue);
 			}
 		}
 
@@ -217,12 +221,16 @@ public abstract class AbstractQueuedExecutionManager implements ExecutionManager
 	}
 
 	/**
-	 * Removes the given job from the lists of queued and running jobs and then refreshes the manager.
+	 * <p>Removes the given job from the lists of queued and running jobs and then refreshes the manager.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	If any error occurs while removing a job from this manager, it SHOULD be logged using the service logger.
+	 * </i></p>
 	 * 
 	 * @see uws.job.manager.ExecutionManager#remove(uws.job.UWSJob)
 	 */
 	@Override
-	public final synchronized void remove(final UWSJob jobToRemove) throws UWSException{
+	public final synchronized void remove(final UWSJob jobToRemove){
 		if (jobToRemove != null){
 			runningJobs.remove(jobToRemove.getJobId());
 			queuedJobs.remove(jobToRemove);
diff --git a/src/uws/job/manager/DefaultExecutionManager.java b/src/uws/job/manager/DefaultExecutionManager.java
index e00bda0af82d9ef37c49eb428b8156ba306e9979..333cbca0294adc0c4549dc754df0355c57d78091 100644
--- a/src/uws/job/manager/DefaultExecutionManager.java
+++ b/src/uws/job/manager/DefaultExecutionManager.java
@@ -16,7 +16,8 @@ package uws.job.manager;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Iterator;
@@ -24,10 +25,11 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
-
+import uws.UWSToolBox;
 import uws.job.ExecutionPhase;
 import uws.job.UWSJob;
+import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Default implementation of the ExecutionManager interface.</p>
@@ -35,27 +37,35 @@ import uws.job.UWSJob;
  * <p>This manager does not have a queue. That is to say that all jobs are always immediately starting.
  * Consequently this manager is just used to gather all running jobs.</p>
  *
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class DefaultExecutionManager implements ExecutionManager {
-	private static final long serialVersionUID = 1L;
 
 	/** List of running jobs. */
 	protected Map<String,UWSJob> runningJobs;
 
+	protected final UWSLog logger;
+
 	public DefaultExecutionManager(){
+		this(null);
+	}
+
+	public DefaultExecutionManager(final UWSLog logger){
 		runningJobs = new LinkedHashMap<String,UWSJob>(10);
+		this.logger = (logger == null) ? UWSToolBox.getDefaultLogger() : logger;
 	}
 
 	/* ******* */
 	/* GETTERS */
 	/* ******* */
 
+	@Override
 	public final Iterator<UWSJob> getRunningJobs(){
 		return runningJobs.values().iterator();
 	}
 
+	@Override
 	public final int getNbRunningJobs(){
 		return runningJobs.size();
 	}
@@ -65,6 +75,7 @@ public class DefaultExecutionManager implements ExecutionManager {
 	 * 
 	 * @see uws.job.manager.ExecutionManager#getQueuedJobs()
 	 */
+	@Override
 	public final Iterator<UWSJob> getQueuedJobs(){
 		return new Iterator<UWSJob>(){
 			@Override
@@ -89,6 +100,7 @@ public class DefaultExecutionManager implements ExecutionManager {
 	 * 
 	 * @see uws.job.manager.ExecutionManager#getNbQueuedJobs()
 	 */
+	@Override
 	public final int getNbQueuedJobs(){
 		return 0;
 	}
@@ -98,11 +110,13 @@ public class DefaultExecutionManager implements ExecutionManager {
 	 * 
 	 * @see uws.job.manager.ExecutionManager#refresh()
 	 */
-	public final void refresh() throws UWSException{
+	@Override
+	public final void refresh(){
 		;
 	}
 
-	public synchronized ExecutionPhase execute(final UWSJob jobToExecute) throws UWSException{
+	@Override
+	public synchronized ExecutionPhase execute(final UWSJob jobToExecute){
 		if (jobToExecute == null)
 			return null;
 
@@ -113,18 +127,23 @@ public class DefaultExecutionManager implements ExecutionManager {
 		// If the job is already finished, ensure it is not any more in the list of running jobs:
 		else if (jobToExecute.isFinished()){
 			runningJobs.remove(jobToExecute);
-			throw UWSExceptionFactory.incorrectPhaseTransition(jobToExecute.getJobId(), jobToExecute.getPhase(), ExecutionPhase.EXECUTING);
+			logger.logJob(LogLevel.WARNING, jobToExecute, "START", "Job \"" + jobToExecute.getJobId() + "\" already finished!", null);
 
 			// Otherwise start it:
 		}else{
-			jobToExecute.start(false);
-			runningJobs.put(jobToExecute.getJobId(), jobToExecute);
+			try{
+				jobToExecute.start(false);
+				runningJobs.put(jobToExecute.getJobId(), jobToExecute);
+			}catch(UWSException ue){
+				logger.logJob(LogLevel.ERROR, jobToExecute, "START", "Can not start the job \"" + jobToExecute.getJobId() + "\"! This job is not any more part of its execution manager.", ue);
+			}
 		}
 
 		return jobToExecute.getPhase();
 	}
 
-	public synchronized void remove(final UWSJob jobToRemove) throws UWSException{
+	@Override
+	public synchronized void remove(final UWSJob jobToRemove){
 		if (jobToRemove != null)
 			runningJobs.remove(jobToRemove.getJobId());
 	}
diff --git a/src/uws/job/manager/ExecutionManager.java b/src/uws/job/manager/ExecutionManager.java
index 7232cb529e4770b72140a7f591ee49efdc8e564e..f95bd620161e1b87b77309406215bd033073245a 100644
--- a/src/uws/job/manager/ExecutionManager.java
+++ b/src/uws/job/manager/ExecutionManager.java
@@ -16,13 +16,12 @@ package uws.job.manager;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Iterator;
 
-import uws.UWSException;
-
 import uws.job.ExecutionPhase;
 import uws.job.UWSJob;
 
@@ -36,8 +35,8 @@ import uws.job.UWSJob;
  *  and to end ({@link #remove(UWSJob)}).
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public interface ExecutionManager {
 
@@ -70,31 +69,37 @@ public interface ExecutionManager {
 	public int getNbQueuedJobs();
 
 	/**
-	 * Refreshes the lists of running and queued jobs.
+	 * <p>Refreshes the lists of running and queued jobs.</p>
 	 * 
-	 * @throws UWSException		If there is an error while refreshing this manager.
+	 * <p><i>Note:
+	 * 	If any error occurs while refreshing this manager, it SHOULD be logged using the service logger.
+	 * </i></p>
 	 */
-	public void refresh() throws UWSException;
+	public void refresh();
 
 	/**
 	 * <p>Lets deciding whether the given job can start immediately or whether it must be put in the queue.</p>
 	 * 
+	 * <p><i>Note:
+	 * 	If any error occurs while executing the given job, it SHOULD be logged using the service logger.
+	 * </i></p>
+	 * 
 	 * @param job	The job to execute.
 	 * @return		The resulting execution phase of the given job.
 	 * 
-	 * @throws UWSException	If there is an error while changing the execution phase of the given job or if any other error occurs.
-	 * 
 	 * @see UWSJob#start(boolean)
 	 * @see UWSJob#setPhase(ExecutionPhase)
 	 */
-	public ExecutionPhase execute(final UWSJob job) throws UWSException;
+	public ExecutionPhase execute(final UWSJob job);
 
 	/**
-	 * Removes the job from this manager whatever is its current execution phase.
+	 * <p>Removes the job from this manager whatever is its current execution phase.</p>
 	 * 
-	 * @param jobToRemove		The job to remove.
+	 * <p><i>Note:
+	 * 	If any error occurs while removing a job from this manager, it SHOULD be logged using the service logger.
+	 * </i></p>
 	 * 
-	 * @throws UWSException		If there is an error while refreshing the list of running jobs or if any other error occurs.
+	 * @param jobToRemove		The job to remove.
 	 */
-	public void remove(final UWSJob jobToRemove) throws UWSException;
+	public void remove(final UWSJob jobToRemove);
 }
diff --git a/src/uws/job/manager/QueuedExecutionManager.java b/src/uws/job/manager/QueuedExecutionManager.java
index 4d750e431e396175fdb77ea9119d7714de580a88..51402c877d8e097a36b33e4a6c9b9911dfd7c6e1 100644
--- a/src/uws/job/manager/QueuedExecutionManager.java
+++ b/src/uws/job/manager/QueuedExecutionManager.java
@@ -16,7 +16,8 @@ package uws.job.manager;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.UWSException;
@@ -28,11 +29,10 @@ import uws.service.log.UWSLog;
  * if there are more running jobs than a given number, the jobs to execute are put in the queue until a running job stops.
  * The order of queued jobs are preserved: it is implemented by a FIFO queue.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class QueuedExecutionManager extends AbstractQueuedExecutionManager {
-	private static final long serialVersionUID = 1L;
 
 	/** The maximum number of running jobs. */
 	protected int nbMaxRunningJobs = NO_QUEUE;
@@ -69,11 +69,7 @@ public class QueuedExecutionManager extends AbstractQueuedExecutionManager {
 
 	public final void setNoQueue(){
 		nbMaxRunningJobs = NO_QUEUE;
-		try{
-			refresh();
-		}catch(UWSException ue){
-			logger.error("Impossible to refresh the execution manager !", ue);
-		}
+		refresh();
 	}
 
 	/**
@@ -88,8 +84,10 @@ public class QueuedExecutionManager extends AbstractQueuedExecutionManager {
 	/**
 	 * <p>Sets the maximum number of running jobs.</p>
 	 * 
-	 * <p><i><u>Note:</u> If the new maximum number of running jobs is increasing the list of running jobs is immediately updated
-	 * BUT NOT IF it is decreasing (that is to say, running jobs will not be interrupted to be put in the queue, they continue to run) !</i></p>
+	 * <p><i>Note:
+	 * 	If the new maximum number of running jobs is increasing the list of running jobs is immediately updated
+	 * 	BUT NOT IF it is decreasing (that is to say, running jobs will not be interrupted to be put in the queue, they continue to run) !
+	 * </i></p>
 	 * 
 	 * @param maxRunningJobs	The new maximum number of running jobs ({@link #NO_QUEUE} or a negative value means no maximum number of running jobs: there will be no queue any more).
 	 * 
diff --git a/src/uws/job/parameters/DestructionTimeController.java b/src/uws/job/parameters/DestructionTimeController.java
index 103b64ce0e5510516ee6e21153c0e7affe7750d9..011e7d25ec3ea5abd5e54bb6b1268f760ba26a94 100644
--- a/src/uws/job/parameters/DestructionTimeController.java
+++ b/src/uws/job/parameters/DestructionTimeController.java
@@ -16,22 +16,21 @@ package uws.job.parameters;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.Serializable;
-
 import java.text.ParseException;
 import java.util.Calendar;
 import java.util.Date;
-import uws.UWSException;
-import uws.UWSExceptionFactory;
 
+import uws.UWSException;
 import uws.job.UWSJob;
 
 /**
  * <p>
- * 	Let's controlling the destruction time of all jobs managed by a UWS. Thus it is possible to set a default and a maximum value.
+ * 	Let controlling the destruction time of all jobs managed by a UWS. Thus it is possible to set a default and a maximum value.
  * 	Moreover you can indicate whether the destruction time of jobs can be modified by the user or not.
  * </p>
  * 
@@ -46,8 +45,8 @@ import uws.job.UWSJob;
  * 	</i>
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class DestructionTimeController implements InputParamController, Serializable {
 	private static final long serialVersionUID = 1L;
@@ -107,10 +106,10 @@ public class DestructionTimeController implements InputParamController, Serializ
 			try{
 				date = UWSJob.dateFormat.parse(strValue);
 			}catch(ParseException pe){
-				throw UWSExceptionFactory.badFormat(null, UWSJob.PARAM_DESTRUCTION_TIME, strValue, null, "A date not yet expired.");
+				throw new UWSException(UWSException.BAD_REQUEST, pe, "Wrong date format for the destruction time parameter: \"" + strValue + "\"! The format to respect is: " + UWSJob.DEFAULT_DATE_FORMAT);
 			}
 		}else
-			throw UWSExceptionFactory.badFormat(null, UWSJob.PARAM_DESTRUCTION_TIME, value.toString(), value.getClass().getName(), "A date not yet expired.");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the destruction time parameter: class \"" + value.getClass().getName() + "\"! It should be a Date or a string containing a date with the format \"" + UWSJob.DEFAULT_DATE_FORMAT + "\".");
 
 		Date maxDate = getMaxDestructionTime();
 		if (maxDate != null && date.after(maxDate))
@@ -317,6 +316,7 @@ public class DestructionTimeController implements InputParamController, Serializ
 	 * 
 	 * @return <i>true</i> if the destruction time can be modified, <i>false</i> otherwise.
 	 */
+	@Override
 	public final boolean allowModification(){
 		return allowModification;
 	}
diff --git a/src/uws/job/parameters/ExecutionDurationController.java b/src/uws/job/parameters/ExecutionDurationController.java
index 7c2d7eedfb5680724b7a39df4c79b4b48d51771f..45cd04bed2872db8d7451e06b538a95b02eb484c 100644
--- a/src/uws/job/parameters/ExecutionDurationController.java
+++ b/src/uws/job/parameters/ExecutionDurationController.java
@@ -16,19 +16,18 @@ package uws.job.parameters;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.Serializable;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
-
 import uws.job.UWSJob;
 
 /**
  * <p>
- * 	Lets controlling the execution duration of all jobs managed by a UWS. Thus it is possible to set a default and a maximum value.
+ * 	Let controlling the execution duration of all jobs managed by a UWS. Thus it is possible to set a default and a maximum value.
  * 	Moreover you can indicate whether the execution duration of jobs can be modified by the user or not.
  * </p>
  * 
@@ -39,8 +38,8 @@ import uws.job.UWSJob;
  * 	</i>
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class ExecutionDurationController implements InputParamController, Serializable {
 	private static final long serialVersionUID = 1L;
@@ -82,10 +81,10 @@ public class ExecutionDurationController implements InputParamController, Serial
 			try{
 				duration = Long.parseLong(strValue);
 			}catch(NumberFormatException nfe){
-				throw UWSExceptionFactory.badFormat(null, UWSJob.PARAM_EXECUTION_DURATION, strValue, null, "A long value between " + UWSJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
+				throw new UWSException(UWSException.BAD_REQUEST, "Wrong format for the maximum duration parameter: \"" + strValue + "\"! It should be a long numeric value between " + UWSJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
 			}
 		}else
-			throw UWSExceptionFactory.badFormat(null, UWSJob.PARAM_EXECUTION_DURATION, null, value.getClass().getName(), "A long value between " + UWSJob.UNLIMITED_DURATION + " and " + maxDuration + " (Default value: " + defaultDuration + ").");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the maximum duration parameter: class \"" + value.getClass().getName() + "\"! It should be long or a string containing only a long value.");
 
 		if (duration < UWSJob.UNLIMITED_DURATION)
 			duration = UWSJob.UNLIMITED_DURATION;
@@ -148,6 +147,7 @@ public class ExecutionDurationController implements InputParamController, Serial
 	 * 
 	 * @return <i>true</i> if the execution duration can be modified, <i>false</i> otherwise.
 	 */
+	@Override
 	public final boolean allowModification(){
 		return allowModification;
 	}
diff --git a/src/uws/job/parameters/StringParamController.java b/src/uws/job/parameters/StringParamController.java
index 39b42bf2930080f38dd93cd5ec4f8d5ca52d889d..85530e3f06715366a720836d928b2cdc1fcdb5aa 100644
--- a/src/uws/job/parameters/StringParamController.java
+++ b/src/uws/job/parameters/StringParamController.java
@@ -16,17 +16,17 @@ package uws.job.parameters;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 
 /**
- * Lets controlling a String parameter.
+ * Let controlling a String parameter.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class StringParamController implements InputParamController {
 
@@ -133,11 +133,11 @@ public class StringParamController implements InputParamController {
 					if (strValue.equalsIgnoreCase(v))
 						return v;
 				}
-				throw UWSExceptionFactory.badFormat(null, paramName, strValue, null, getExpectedFormat());
+				throw new UWSException(UWSException.BAD_REQUEST, "Unknown value for the job parameter " + paramName + ": \"" + strValue + "\". It should be " + getExpectedFormat());
 			}else
 				return strValue;
 		}else
-			throw UWSExceptionFactory.badFormat(null, paramName, value.toString(), value.getClass().getName(), getExpectedFormat());
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for the parameter " + paramName + ": \"" + value.getClass().getName() + "\"! It should be a String.");
 	}
 
 	/**
@@ -147,12 +147,12 @@ public class StringParamController implements InputParamController {
 	 */
 	protected final String getExpectedFormat(){
 		if (possibleValues == null || possibleValues.length == 0){
-			StringBuffer buffer = new StringBuffer("A String value among: ");
+			StringBuffer buffer = new StringBuffer("a String value among: ");
 			for(int i = 0; i < possibleValues.length; i++)
 				buffer.append((i == 0) ? "" : ", ").append(possibleValues[i]);
 			return buffer.toString();
 		}else
-			return "A String value.";
+			return "a String value.";
 	}
 
 	@Override
diff --git a/src/uws/job/parameters/UWSParameters.java b/src/uws/job/parameters/UWSParameters.java
index 767706ad072510b40f48d4b9f48165b59d543bfb..f33bdcc54a719ff74906dfb99ae0faacf15aaec5 100644
--- a/src/uws/job/parameters/UWSParameters.java
+++ b/src/uws/job/parameters/UWSParameters.java
@@ -16,30 +16,28 @@ package uws.job.parameters;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.text.ParseException;
 import java.util.Collection;
+import java.util.Date;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Date;
-import java.util.Set;
 import java.util.Map;
-import java.util.HashMap;
-
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
 
 import uws.UWSException;
-
 import uws.job.UWSJob;
-
 import uws.service.UWS;
 
 /**
- * <p>Lets extracting all UWS standard and non-standard parameters from a map or a {@link HttpServletRequest}.</p>
+ * <p>Let extracting all UWS standard and non-standard parameters from a map or a {@link HttpServletRequest}.</p>
  * 
  * <h3>Input parameter check</h3>
  * <p>It is possible to check the value of some or all parameters by calling the function {@link InputParamController#check(Object)}
@@ -92,8 +90,8 @@ import uws.service.UWS;
  * <p><i><u>note:</u> All parameters stored under the parameter {@link UWSJob#PARAM_PARAMETERS} (that's to say, additional parameters)
  * are also considered as READ/WRITE parameters !</i></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class UWSParameters implements Iterable<Entry<String,Object>> {
 
@@ -295,6 +293,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
 		return mapParamControllers.entrySet().iterator();
 	}
 
+	@Override
 	public final Iterator<Entry<String,Object>> iterator(){
 		return params.entrySet().iterator();
 	}
@@ -364,7 +363,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
 					// Test whether this parameter is allowed to be modified after its initialization:
 					InputParamController controller = getController(entry.getKey());
 					if (controller != null && !controller.allowModification())
-						throw new UWSException("The parameter \"" + entry.getKey() + "\" can not be modified after initialization !");
+						throw new UWSException(UWSException.NOT_ALLOWED, "The parameter \"" + entry.getKey() + "\" can not be modified after initialization!");
 					// If the value is NULL, removes this parameter:
 					if (entry.getValue() == null)
 						params.remove(entry.getKey());
diff --git a/src/uws/job/serializer/JSONSerializer.java b/src/uws/job/serializer/JSONSerializer.java
index 9ffb8325f1f206f3ee1a7c853f8c36996dddc41a..55fb337549b3aeb644124b17049a3a0b8a8ce781 100644
--- a/src/uws/job/serializer/JSONSerializer.java
+++ b/src/uws/job/serializer/JSONSerializer.java
@@ -16,29 +16,26 @@ package uws.job.serializer;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import org.json.JSONException;
 import org.json.Json4Uws;
 
-import uws.UWSException;
-
 import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWS;
 import uws.service.UWSUrl;
 
 /**
  * Lets serializing any UWS resource in JSON.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see Json4Uws
  */
@@ -51,178 +48,105 @@ public class JSONSerializer extends UWSSerializer {
 	}
 
 	@Override
-	public String getUWS(final UWS uws, final JobOwner user) throws UWSException{
-		try{
-			return Json4Uws.getJson(uws).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getUWS(final UWS uws, final JobOwner user) throws JSONException{
+		return Json4Uws.getJson(uws).toString();
 	}
 
 	@Override
-	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(jobsList, owner).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root) throws JSONException{
+		return Json4Uws.getJson(jobsList, owner).toString();
 	}
 
 	@Override
-	public String getJob(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(job, null, false).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getJob(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(job, null, false).toString();
 	}
 
 	@Override
-	public String getJobRef(final UWSJob job, final UWSUrl jobsListUrl) throws UWSException{
-		try{
-			return Json4Uws.getJson(job, jobsListUrl, true).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getJobRef(final UWSJob job, final UWSUrl jobsListUrl) throws JSONException{
+		return Json4Uws.getJson(job, jobsListUrl, true).toString();
 	}
 
 	@Override
-	public String getJobID(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(UWSJob.PARAM_JOB_ID, job.getJobId()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getJobID(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(UWSJob.PARAM_JOB_ID, job.getJobId()).toString();
 	}
 
 	@Override
-	public String getRunID(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(UWSJob.PARAM_RUN_ID, job.getRunId()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getRunID(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(UWSJob.PARAM_RUN_ID, job.getRunId()).toString();
 	}
 
 	@Override
-	public String getOwnerID(final UWSJob job, final boolean root) throws UWSException{
+	public String getOwnerID(final UWSJob job, final boolean root) throws JSONException{
 		if (job.getOwner() == null)
 			return "{}";
-		else{
-			try{
-				return Json4Uws.getJson(UWSJob.PARAM_OWNER, job.getOwner().getPseudo()).toString();
-			}catch(JSONException je){
-				throw new UWSException(je);
-			}
-		}
+		else
+			return Json4Uws.getJson(UWSJob.PARAM_OWNER, job.getOwner().getPseudo()).toString();
 	}
 
 	@Override
-	public String getPhase(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(UWSJob.PARAM_PHASE, job.getPhase().toString()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getPhase(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(UWSJob.PARAM_PHASE, job.getPhase().toString()).toString();
 	}
 
 	@Override
-	public String getQuote(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(UWSJob.PARAM_QUOTE, job.getQuote()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getQuote(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(UWSJob.PARAM_QUOTE, job.getQuote()).toString();
 	}
 
 	@Override
-	public String getExecutionDuration(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(UWSJob.PARAM_EXECUTION_DURATION, job.getExecutionDuration()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getExecutionDuration(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJson(UWSJob.PARAM_EXECUTION_DURATION, job.getExecutionDuration()).toString();
 	}
 
 	@Override
-	public String getDestructionTime(final UWSJob job, final boolean root) throws UWSException{
+	public String getDestructionTime(final UWSJob job, final boolean root) throws JSONException{
 		if (job.getDestructionTime() != null){
-			try{
-				return Json4Uws.getJson(UWSJob.PARAM_DESTRUCTION_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
-			}catch(JSONException je){
-				throw new UWSException(je);
-			}
+			return Json4Uws.getJson(UWSJob.PARAM_DESTRUCTION_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
 		}else
 			return "{}";
 	}
 
 	@Override
-	public String getStartTime(final UWSJob job, final boolean root) throws UWSException{
-		if (job.getDestructionTime() != null){
-			try{
-				return Json4Uws.getJson(UWSJob.PARAM_START_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
-			}catch(JSONException je){
-				throw new UWSException(je);
-			}
-		}else
+	public String getStartTime(final UWSJob job, final boolean root) throws JSONException{
+		if (job.getDestructionTime() != null)
+			return Json4Uws.getJson(UWSJob.PARAM_START_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
+		else
 			return "{}";
 	}
 
 	@Override
-	public String getEndTime(final UWSJob job, final boolean root) throws UWSException{
-		if (job.getDestructionTime() != null){
-			try{
-				return Json4Uws.getJson(UWSJob.PARAM_END_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
-			}catch(JSONException je){
-				throw new UWSException(je);
-			}
-		}else
+	public String getEndTime(final UWSJob job, final boolean root) throws JSONException{
+		if (job.getDestructionTime() != null)
+			return Json4Uws.getJson(UWSJob.PARAM_END_TIME, UWSJob.dateFormat.format(job.getDestructionTime())).toString();
+		else
 			return "{}";
 	}
 
 	@Override
-	public String getErrorSummary(final ErrorSummary error, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(error).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getErrorSummary(final ErrorSummary error, final boolean root) throws JSONException{
+		return Json4Uws.getJson(error).toString();
 	}
 
 	@Override
-	public String getAdditionalParameters(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJobParamsJson(job).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getAdditionalParameters(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJobParamsJson(job).toString();
 	}
 
 	@Override
-	public String getAdditionalParameter(final String paramName, final Object paramValue, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJson(paramName, (paramValue == null) ? null : paramValue.toString()).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getAdditionalParameter(final String paramName, final Object paramValue, final boolean root) throws JSONException{
+		return Json4Uws.getJson(paramName, (paramValue == null) ? null : paramValue.toString()).toString();
 	}
 
 	@Override
-	public String getResults(final UWSJob job, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJobResultsJson(job).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getResults(final UWSJob job, final boolean root) throws JSONException{
+		return Json4Uws.getJobResultsJson(job).toString();
 	}
 
 	@Override
-	public String getResult(final Result result, final boolean root) throws UWSException{
-		try{
-			return Json4Uws.getJobResultJson(result).toString();
-		}catch(JSONException je){
-			throw new UWSException(je);
-		}
+	public String getResult(final Result result, final boolean root) throws JSONException{
+		return Json4Uws.getJobResultJson(result).toString();
 	}
 
 }
diff --git a/src/uws/job/serializer/UWSSerializer.java b/src/uws/job/serializer/UWSSerializer.java
index 3303987621c111d3af67570c640748042f9ac659..3641a69341deaf2128fe73e245bc5330019f0f07 100644
--- a/src/uws/job/serializer/UWSSerializer.java
+++ b/src/uws/job/serializer/UWSSerializer.java
@@ -16,22 +16,20 @@ package uws.job.serializer;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.Serializable;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
-
 import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
 import uws.job.user.JobOwner;
-
-import uws.service.UWSService;
 import uws.service.UWS;
+import uws.service.UWSService;
 import uws.service.UWSUrl;
 
 /**
@@ -42,8 +40,8 @@ import uws.service.UWSUrl;
  * 	<li>JSON by the class {@link JSONSerializer}</li>
  * </ul>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see XMLSerializer
  * @see JSONSerializer
@@ -72,9 +70,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * @return				The serialization of the given attribute
 	 * 						or the serialization of the whole job if the given attributes array is empty or <i>null</i>.
 	 * 
-	 * @throws UWSException If the specified attribute/parameter/result does not exist.
+	 * @throws Exception	If an error occurs while serializing the specified job/attribute/parameter/result.
 	 */
-	public String getJob(final UWSJob job, final String[] attributes, final boolean root) throws UWSException{
+	public String getJob(final UWSJob job, final String[] attributes, final boolean root) throws Exception{
 		if (attributes == null || attributes.length <= 0)
 			return getJob(job, root);
 
@@ -118,7 +116,7 @@ public abstract class UWSSerializer implements Serializable {
 				if (value != null)
 					return value.toString();
 				else
-					throw UWSExceptionFactory.incorrectJobParameter(job.getJobId(), secondAttribute);
+					throw new UWSException(UWSException.NOT_FOUND, "No parameter named \"" + secondAttribute + "\" in the job \"" + job.getJobId() + "\"!");
 			}
 			// RESULTS LIST:
 		}else if (firstAttribute.equalsIgnoreCase(UWSJob.PARAM_RESULTS)){
@@ -131,7 +129,7 @@ public abstract class UWSSerializer implements Serializable {
 				if (r != null)
 					return getResult(r, root);
 				else
-					throw UWSExceptionFactory.incorrectJobResult(job.getJobId(), secondAttribute);
+					throw new UWSException(UWSException.NOT_FOUND, "No result named \"" + secondAttribute + "\" in the job \"" + job.getJobId() + "\"!");
 			}
 			// ERROR DETAILS or ERROR SUMMARY:
 		}else if (firstAttribute.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY))
@@ -141,7 +139,7 @@ public abstract class UWSSerializer implements Serializable {
 				return getErrorSummary(job.getErrorSummary(), root);
 		// OTHERS:
 		else
-			throw UWSExceptionFactory.incorrectJobParameter(job.getJobId(), firstAttribute);
+			throw new UWSException(UWSException.NOT_FOUND, "No job attribute named \"" + firstAttribute + "\" in the job \"" + job.getJobId() + "\"!");
 	}
 
 	@Override
@@ -160,12 +158,14 @@ public abstract class UWSSerializer implements Serializable {
 	 * Serializes the given UWS.
 	 * 
 	 * @param uws			The UWS to serialize.
+	 * 
 	 * @return				The serialization of the given UWS.
-	 * @throws UWSException	If there is an error during the serialization.
+	 * 
+	 * @throws Exception	If there is an error during the serialization.
 	 * 
 	 * @see UWSSerializer#getUWS(UWSService, String)
 	 */
-	public String getUWS(final UWS uws) throws UWSException{
+	public String getUWS(final UWS uws) throws Exception{
 		return getUWS(uws, null);
 	}
 
@@ -176,9 +176,10 @@ public abstract class UWSSerializer implements Serializable {
 	 * @param user			The user which has asked the serialization of the given UWS.
 	 * 
 	 * @return				The serialization of the UWS.
-	 * @throws UWSException	If there is an error during the serialization.
+	 * 
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getUWS(final UWS uws, final JobOwner user) throws UWSException;
+	public abstract String getUWS(final UWS uws, final JobOwner user) throws Exception;
 
 	/**
 	 * Serializes the given jobs list.
@@ -188,9 +189,10 @@ public abstract class UWSSerializer implements Serializable {
 	 * 						in a top level serialization (for a jobs list: uws), <i>true</i> otherwise.
 	 * 
 	 * @return				The serialization of the given jobs list.
-	 * @throws UWSException If there is an error during the serialization.
+	 * 
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public String getJobList(final JobList jobsList, final boolean root) throws UWSException{
+	public String getJobList(final JobList jobsList, final boolean root) throws Exception{
 		return getJobList(jobsList, null, root);
 	}
 
@@ -203,10 +205,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 						in a top level serialization (for a jobs list: uws), <i>true</i> otherwise.
 	 * @return				The serialization of the given jobs list.
 	 * 
-	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getJobList(final JobList jobsList, JobOwner owner, final boolean root) throws UWSException;
+	public abstract String getJobList(final JobList jobsList, JobOwner owner, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the whole given job.
@@ -217,9 +218,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the given job.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getJob(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getJob(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes just a reference on the given job.
@@ -229,11 +230,11 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of a reference on the given job.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 * 
 	 * @since 3.1
 	 */
-	public abstract String getJobRef(final UWSJob job, final UWSUrl jobsListUrl) throws UWSException;
+	public abstract String getJobRef(final UWSJob job, final UWSUrl jobsListUrl) throws Exception;
 
 	/**
 	 * Serializes the ID of the given job.
@@ -244,9 +245,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the job ID.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getJobID(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getJobID(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the run ID of the given job.
@@ -257,9 +258,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the run ID.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getRunID(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getRunID(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the owner ID of the given job.
@@ -270,9 +271,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the owner ID.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getOwnerID(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getOwnerID(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the phase of the given job.
@@ -283,9 +284,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the phase.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getPhase(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getPhase(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the quote of the given job.
@@ -296,9 +297,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the quote.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getQuote(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getQuote(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the start time of the given job.
@@ -309,9 +310,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the start time.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getStartTime(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getStartTime(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the end time of the given job.
@@ -322,9 +323,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the end time.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getEndTime(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getEndTime(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the execution duration of the given job.
@@ -335,9 +336,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the execution duration.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getExecutionDuration(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getExecutionDuration(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the destruction time of the given job.
@@ -348,9 +349,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the destruction time.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getDestructionTime(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getDestructionTime(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the given error summary.
@@ -361,9 +362,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the error summary.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getErrorSummary(final ErrorSummary error, final boolean root) throws UWSException;
+	public abstract String getErrorSummary(final ErrorSummary error, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the results of the given job.
@@ -374,9 +375,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the results.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getResults(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getResults(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the given result.
@@ -387,9 +388,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the result.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getResult(final Result result, final boolean root) throws UWSException;
+	public abstract String getResult(final Result result, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the parameters of the given job.
@@ -400,9 +401,9 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the parameters.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getAdditionalParameters(final UWSJob job, final boolean root) throws UWSException;
+	public abstract String getAdditionalParameters(final UWSJob job, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the specified parameter.
@@ -414,7 +415,7 @@ public abstract class UWSSerializer implements Serializable {
 	 * 
 	 * @return				The serialization of the parameter.
 	 * 
-	 * @throws UWSException	If there is an error during the serialization.
+	 * @throws Exception	If there is an error during the serialization.
 	 */
-	public abstract String getAdditionalParameter(final String paramName, final Object paramValue, final boolean root) throws UWSException;
+	public abstract String getAdditionalParameter(final String paramName, final Object paramValue, final boolean root) throws Exception;
 }
diff --git a/src/uws/job/serializer/XMLSerializer.java b/src/uws/job/serializer/XMLSerializer.java
index b2f14c60f6cf5e678b8abd4ba66d723f64ea56cd..c5322a4f9b88cc324b0b1be57be996734396ff9e 100644
--- a/src/uws/job/serializer/XMLSerializer.java
+++ b/src/uws/job/serializer/XMLSerializer.java
@@ -23,7 +23,6 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.Iterator;
 
-import uws.UWSException;
 import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
@@ -161,7 +160,7 @@ public class XMLSerializer extends UWSSerializer {
 	}
 
 	@Override
-	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root) throws UWSException{
+	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root){
 		String name = jobsList.getName();
 		StringBuffer xml = new StringBuffer(getHeader());
 
diff --git a/src/uws/service/UWSService.java b/src/uws/service/UWSService.java
index b6555c83a963b685ac64a62cb7de12a54d8d389e..968a0990f9ed0f59b6e1e13638bbce2d4f957e0d 100644
--- a/src/uws/service/UWSService.java
+++ b/src/uws/service/UWSService.java
@@ -16,34 +16,32 @@ package uws.service;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
-
 import java.net.URL;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Vector;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
 import uws.AcceptHeader;
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.job.ExecutionPhase;
 import uws.job.JobList;
 import uws.job.UWSJob;
-
 import uws.job.manager.DefaultExecutionManager;
 import uws.job.serializer.JSONSerializer;
 import uws.job.serializer.UWSSerializer;
 import uws.job.serializer.XMLSerializer;
 import uws.job.user.JobOwner;
-
 import uws.service.actions.AddJob;
 import uws.service.actions.DestroyJob;
 import uws.service.actions.GetJobParam;
@@ -53,12 +51,12 @@ import uws.service.actions.SetJobParam;
 import uws.service.actions.ShowHomePage;
 import uws.service.actions.UWSAction;
 import uws.service.backup.UWSBackupManager;
-
 import uws.service.error.DefaultUWSErrorWriter;
 import uws.service.error.ServiceErrorWriter;
 import uws.service.file.UWSFileManager;
 import uws.service.log.DefaultUWSLog;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <h3>General description</h3>
@@ -182,11 +180,10 @@ import uws.service.log.UWSLog;
  * use the method {@link #setDefaultHomePage()} !</i></p>
  * 
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class UWSService implements UWS {
-	private static final long serialVersionUID = 1L;
 
 	/** Name of this UWS. */
 	protected String name = null;
@@ -239,26 +236,32 @@ public class UWSService implements UWS {
 	/** Lets writing/formatting any exception/throwable in a HttpServletResponse. */
 	protected ServiceErrorWriter errorWriter;
 
+	/** Last generated request ID. If the next generated request ID is equivalent to this one,
+	 * a new one will generate in order to ensure the unicity.
+	 * @since 4.1 */
+	protected static String lastRequestID = null;
+
 	/* ************ */
 	/* CONSTRUCTORS */
-	/* ************ *//**
-						* <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
-						* 
-						* <p>
-						* 	By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
-						* 	All the default actions of a UWS are also already implemented.
-						* 	However, you still have to create at least one job list !
-						* </p>
-						* 
-						* <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
-						* 
-						* @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
-						* @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
-						* 
-						* @throws NullPointerException	If at least one of the parameters is <i>null</i>.
-						* 
-						* @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
-						*/
+	/* ************ */
+	/**
+	 * <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
+	 * 
+	 * <p>
+	 * 	By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
+	 * 	All the default actions of a UWS are also already implemented.
+	 * 	However, you still have to create at least one job list !
+	 * </p>
+	 * 
+	 * <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
+	 * 
+	 * @param jobFactory	Object which lets creating the UWS jobs managed by this UWS and their thread/task.
+	 * @param fileManager	Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
+	 * 
+	 * @throws NullPointerException	If at least one of the parameters is <i>null</i>.
+	 * 
+	 * @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
+	 */
 	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager){
 		this(jobFactory, fileManager, (UWSLog)null);
 	}
@@ -280,15 +283,15 @@ public class UWSService implements UWS {
 	 */
 	public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger){
 		if (jobFactory == null)
-			throw new NullPointerException("Missing UWS factory ! Can not create a UWSService.");
+			throw new NullPointerException("Missing UWS factory! Can not create a UWSService.");
 		factory = jobFactory;
 
 		if (fileManager == null)
-			throw new NullPointerException("Missing UWS file manager ! Can not create a UWSService.");
+			throw new NullPointerException("Missing UWS file manager! Can not create a UWSService.");
 		this.fileManager = fileManager;
 
 		this.logger = (logger == null) ? new DefaultUWSLog(this) : logger;
-		errorWriter = new DefaultUWSErrorWriter(this);
+		errorWriter = new DefaultUWSErrorWriter(this.logger);
 
 		// Initialize the list of jobs:
 		mapJobLists = new LinkedHashMap<String,JobList>();
@@ -345,11 +348,22 @@ public class UWSService implements UWS {
 
 		// Extract the name of the UWS:
 		try{
+			// Set the URL interpreter:
 			urlInterpreter = new UWSUrl(baseURI);
+
+			// ...and the name of this service:
 			name = urlInterpreter.getUWSName();
-			getLogger().uwsInitialized(this);
-		}catch(UWSException ex){
-			throw new UWSException(UWSException.BAD_REQUEST, ex, "Invalid base UWS URI (" + baseURI + ") !");
+
+			// Log the successful initialization:
+			logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized!", null);
+
+		}catch(NullPointerException ex){
+			// Log the exception:
+			// (since the first constructor has already been called successfully, the logger is now NOT NULL):
+			logger.logUWS(LogLevel.FATAL, null, "INIT", "Invalid base UWS URI: " + baseURI + "! You should check the configuration of the service.", ex);
+
+			// Throw a new UWSException with a more understandable message:
+			throw new UWSException(UWSException.BAD_REQUEST, ex, "Invalid base UWS URI (" + baseURI + ")!");
 		}
 	}
 
@@ -380,12 +394,13 @@ public class UWSService implements UWS {
 		this(jobFactory, fileManager, logger);
 		setUrlInterpreter(urlInterpreter);
 		if (this.urlInterpreter != null)
-			getLogger().uwsInitialized(this);
+			logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
 	}
 
 	/* ************** */
 	/* LOG MANAGEMENT */
 	/* ************** */
+	@Override
 	public UWSLog getLogger(){
 		return logger;
 	}
@@ -414,6 +429,7 @@ public class UWSService implements UWS {
 	/* ***************** */
 	/* GETTERS & SETTERS */
 	/* ***************** */
+	@Override
 	public final String getName(){
 		return name;
 	}
@@ -427,6 +443,7 @@ public class UWSService implements UWS {
 		this.name = name;
 	}
 
+	@Override
 	public final String getDescription(){
 		return description;
 	}
@@ -451,6 +468,7 @@ public class UWSService implements UWS {
 		return (urlInterpreter == null) ? null : urlInterpreter.getBaseURI();
 	}
 
+	@Override
 	public final UWSUrl getUrlInterpreter(){
 		return urlInterpreter;
 	}
@@ -472,6 +490,7 @@ public class UWSService implements UWS {
 	 * 
 	 * @return	The used UserIdentifier (MAY BE NULL).
 	 */
+	@Override
 	public final UserIdentifier getUserIdentifier(){
 		return userIdentifier;
 	}
@@ -485,14 +504,17 @@ public class UWSService implements UWS {
 		userIdentifier = identifier;
 	}
 
+	@Override
 	public final UWSFactory getFactory(){
 		return factory;
 	}
 
+	@Override
 	public final UWSFileManager getFileManager(){
 		return fileManager;
 	}
 
+	@Override
 	public final UWSBackupManager getBackupManager(){
 		return backupManager;
 	}
@@ -596,7 +618,7 @@ public class UWSService implements UWS {
 		if (serializers.containsKey(mimeType))
 			defaultSerializer = mimeType;
 		else
-			throw UWSExceptionFactory.missingSerializer(mimeType, "Impossible to set the default serializer.");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeType + "! The default serializer won't be set.");
 	}
 
 	/**
@@ -646,6 +668,7 @@ public class UWSService implements UWS {
 		return serializers.values().iterator();
 	}
 
+	@Override
 	public final UWSSerializer getSerializer(String mimeTypes) throws UWSException{
 		choosenSerializer = null;
 
@@ -725,14 +748,17 @@ public class UWSService implements UWS {
 	 * 
 	 * @see java.lang.Iterable#iterator()
 	 */
+	@Override
 	public final Iterator<JobList> iterator(){
 		return mapJobLists.values().iterator();
 	}
 
+	@Override
 	public final JobList getJobList(String name){
 		return mapJobLists.get(name);
 	}
 
+	@Override
 	public final int getNbJobList(){
 		return mapJobLists.size();
 	}
@@ -749,6 +775,7 @@ public class UWSService implements UWS {
 	 * @see JobList#setUWS(AbstractUWS)
 	 * @see UWS#addJobList(JobList)
 	 */
+	@Override
 	public final boolean addJobList(JobList jl){
 		if (jl == null)
 			return false;
@@ -759,44 +786,14 @@ public class UWSService implements UWS {
 			jl.setUWS(this);
 			mapJobLists.put(jl.getName(), jl);
 		}catch(IllegalStateException ise){
-			logger.error("The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + " !", ise);
+			logger.logUWS(LogLevel.ERROR, jl, "ADD_JOB_LIST", "The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + ": it may already be associated with one!", ise);
 			return false;
 		}
 
 		return true;
 	}
 
-	/*public final JobList removeJobList(String name){
-		JobList jl = mapJobLists.get(name);
-		if (jl != null){
-			if (removeJobList(jl))
-				return jl;
-		}
-		return null;
-	}*/
-
-	/*
-	 * Removes the given jobs list from this UWS.
-	 * 
-	 * @param jl	The jobs list to remove.
-	 * 
-	 * @return		<i>true</i> if the jobs list has been successfully removed, <i>false</i> otherwise.
-	 * 
-	 * @see JobList#removeAll()
-	 * @see JobList#setUWS(UWSService)
-	 *
-	public boolean removeJobList(JobList jl){
-		if (jl == null)
-			return false;
-
-		jl = mapJobLists.remove(jl.getName());
-		if (jl != null){
-			jl.removeAll();
-			jl.setUWS(null);
-		}
-		return jl != null;
-	}*/
-
+	@Override
 	public final boolean destroyJobList(String name){
 		return destroyJobList(mapJobLists.get(name));
 	}
@@ -821,23 +818,12 @@ public class UWSService implements UWS {
 				jl.clear();
 				jl.setUWS(null);
 			}catch(IllegalStateException ise){
-				getLogger().warning("Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\", because: " + ise.getMessage());
+				logger.logUWS(LogLevel.WARNING, jl, "DESTROY_JOB_LIST", "Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\"!", ise);
 			}
 		}
 		return jl != null;
 	}
 
-	/*
-	 * Removes all managed jobs lists.
-	 * 
-	 * @see #removeJobList(String)
-	 *
-	public final void removeAllJobLists(){
-		ArrayList<String> jlNames = new ArrayList<String>(mapJobLists.keySet());
-		for(String jlName : jlNames)
-			removeJobList(jlName);
-	}*/
-
 	/**
 	 * Destroys all managed jobs lists.
 	 * 
@@ -1000,6 +986,27 @@ public class UWSService implements UWS {
 	/* ********************** */
 	/* UWS MANAGEMENT METHODS */
 	/* ********************** */
+
+	/**
+	 * <p>Generate a unique ID for the given request.</p>
+	 * 
+	 * <p>By default, a timestamp is returned.</p>
+	 * 
+	 * @param request	Request whose an ID is asked.
+	 * 
+	 * @return	The ID of the given request.
+	 * 
+	 * @since 4.1
+	 */
+	protected synchronized String generateRequestID(final HttpServletRequest request){
+		String id;
+		do{
+			id = System.currentTimeMillis() + "";
+		}while(lastRequestID != null && lastRequestID.startsWith(id));
+		lastRequestID = id;
+		return id;
+	}
+
 	/**
 	 * <p>Executes the given request according to the <a href="http://www.ivoa.net/Documents/UWS/20100210/">IVOA Proposed Recommendation of 2010-02-10</a>.
 	 * The result is returned in the given response.</p>
@@ -1030,16 +1037,26 @@ public class UWSService implements UWS {
 		if (request == null || response == null)
 			return false;
 
+		// Generate a unique ID for this request execution (for log purpose only):
+		final String reqID = generateRequestID(request);
+
+		// Log the reception of the request:
+		logger.logHttp(LogLevel.INFO, request, reqID, null, null);
+
 		boolean actionApplied = false;
 		UWSAction action = null;
 		JobOwner user = null;
 
 		try{
-			// Update the UWS URL interpreter:
 			if (urlInterpreter == null){
+				// Initialize the URL interpreter if not already done:
 				setUrlInterpreter(new UWSUrl(request));
-				getLogger().uwsInitialized(this);
+
+				// Log the successful initialization:
+				logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
 			}
+
+			// Update the UWS URL interpreter:
 			urlInterpreter.load(request);
 
 			// Identify the user:
@@ -1056,17 +1073,20 @@ public class UWSService implements UWS {
 
 			// If no corresponding action has been found, throw an error:
 			if (action == null)
-				throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action ! This HTTP request can not be interpreted by this UWS service !");
+				throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
 			response.flushBuffer();
-			logger.httpRequest(request, user, (action != null) ? action.getName() : null, HttpServletResponse.SC_OK, "[OK]", null);
+
+			// Log the successful execution of the action:
+			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
 
 		}catch(UWSException ex){
+			// If redirection, flag the action as executed with success:
 			if (ex.getHttpErrorCode() == UWSException.SEE_OTHER)
 				actionApplied = true;
-			sendError(ex, request, user, (action != null) ? action.getName() : null, response);
+			sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
 		}catch(Exception ex){
-			sendError(ex, request, user, (action != null) ? action.getName() : null, response);
+			sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
 		}finally{
 			executedAction = action;
 		}
@@ -1091,7 +1111,6 @@ public class UWSService implements UWS {
 		response.setContentType(request.getContentType());
 		response.setHeader("Location", url);
 		response.flushBuffer();
-		logger.httpRequest(request, user, uwsAction, HttpServletResponse.SC_SEE_OTHER, "[Redirection toward " + url + "]", null);
 	}
 
 	/**
@@ -1103,6 +1122,7 @@ public class UWSService implements UWS {
 	 * 
 	 * @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.
@@ -1113,16 +1133,14 @@ public class UWSService implements UWS {
 	 * @see #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)
 	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
 	 */
-	public final void sendError(UWSException error, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
-		if (error.getHttpErrorCode() == UWSException.SEE_OTHER)
+	public final void sendError(UWSException error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
+		if (error.getHttpErrorCode() == UWSException.SEE_OTHER){
+			// Log the redirection, if any:
+			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.SEE_OTHER + " [Redirection toward " + error.getMessage() + "] - Action \"" + uwsAction + "\" successfully executed.", null);
+			// Apply the redirection:
 			redirect(error.getMessage(), request, user, uwsAction, response);
-		else{
-			errorWriter.writeError(error, response, request, user, uwsAction);
-			/*if (error.getHttpErrorCode() == UWSException.INTERNAL_SERVER_ERROR)
-				logger.error(error);
-			response.sendError(error.getHttpErrorCode(), error.getMessage());
-			logger.httpRequest(request, user, uwsAction, error.getHttpErrorCode(), error.getMessage(), error);*/
-		}
+		}else
+			sendError(error, request, reqID, user, uwsAction, response);
 	}
 
 	/**
@@ -1136,20 +1154,20 @@ public class UWSService implements UWS {
 	 * 
 	 * @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)}.
-	 * @throws UWSException
 	 * 
-	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
+	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)}
 	 */
-	public final void sendError(Exception error, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
-		errorWriter.writeError(error, response, request, user, uwsAction);
-		/*logger.error(error);
-		response.sendError(UWSException.INTERNAL_SERVER_ERROR, error.getMessage());
-		logger.httpRequest(request, user, uwsAction, UWSException.INTERNAL_SERVER_ERROR, error.getMessage(), error);*/
+	public final void sendError(Exception error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
+		// Write the error in the response and return the appropriate HTTP status code:
+		errorWriter.writeError(error, response, request, reqID, user, uwsAction);
+		// Log the error:
+		logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
 	}
 
 }
diff --git a/src/uws/service/UWSServlet.java b/src/uws/service/UWSServlet.java
index bacb48034a2d9288e97467f24ed830c0407157e4..2936d8dab7a19284d3cc9a83e5f5dcb25cb5af96 100644
--- a/src/uws/service/UWSServlet.java
+++ b/src/uws/service/UWSServlet.java
@@ -16,14 +16,14 @@ package uws.service;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -31,13 +31,10 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import java.lang.IllegalStateException;
-
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -48,35 +45,28 @@ import uws.AcceptHeader;
 import uws.UWSException;
 import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
-
 import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.parameters.DestructionTimeController;
+import uws.job.parameters.DestructionTimeController.DateField;
 import uws.job.parameters.ExecutionDurationController;
 import uws.job.parameters.InputParamController;
 import uws.job.parameters.UWSParameters;
-import uws.job.parameters.DestructionTimeController.DateField;
-
 import uws.job.serializer.JSONSerializer;
 import uws.job.serializer.UWSSerializer;
 import uws.job.serializer.XMLSerializer;
-
 import uws.job.user.JobOwner;
-
 import uws.service.actions.UWSAction;
-
 import uws.service.backup.UWSBackupManager;
-
 import uws.service.error.DefaultUWSErrorWriter;
 import uws.service.error.ServiceErrorWriter;
 import uws.service.file.LocalUWSFileManager;
 import uws.service.file.UWSFileManager;
-
 import uws.service.log.DefaultUWSLog;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>
@@ -107,6 +97,13 @@ import uws.service.log.UWSLog;
  * 	attributes: {@link #name} and {@link #description}.
  * </p>
  * 
+ * <p><i>Note:
+ * 	If any error occurs while the initialization or the creation of a {@link UWSServlet} instance, a {@link ServletException}
+ * 	will be thrown with a basic message dedicated to the service users. This basic and non-informative message is
+ * 	obviously not intended to the administrator which will be able to get the reason of the failure
+ * 	(with a stack trace when available) in the log files.
+ * </i></p>
+ * 
  * <h3>UWS customization</h3>
  * <p>
  * 	As for the classic HTTP servlets, this servlet has one method for each method of the implemented protocol.
@@ -131,8 +128,8 @@ import uws.service.log.UWSLog;
  * 	So, they can be overridden as in any HTTP servlet.
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory {
 	private static final long serialVersionUID = 1L;
@@ -183,6 +180,8 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 
 	@Override
 	public final void init() throws ServletException{
+		final String INIT_ERROR_MSG = "UWS initialization ERROR! Contact the administrator of the service to figure out the failure.";
+
 		// Set the general information about this UWS:
 		name = getServletConfig().getInitParameter("name");
 		description = getServletConfig().getInitParameter("description");
@@ -190,15 +189,18 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		// Set the file manager to use:
 		try{
 			fileManager = createFileManager();
-			if (fileManager == null)
-				throw new ServletException("Missing file manager ! The function createFileManager() MUST return a valid instanceof UWSFileManager !");
+			if (fileManager == null){
+				logger.logUWS(LogLevel.FATAL, null, "INIT", "Missing file manager! The function createFileManager() MUST return a valid instanceof UWSFileManager!", null);
+				throw new ServletException(INIT_ERROR_MSG);
+			}
 		}catch(UWSException ue){
-			throw new ServletException("Error while setting the file manager.", ue);
+			logger.logUWS(LogLevel.FATAL, null, "INIT", "Can't create a file manager!", ue);
+			throw new ServletException(INIT_ERROR_MSG, ue);
 		}
 
 		// Set the logger:
 		logger = new DefaultUWSLog(this);
-		errorWriter = new DefaultUWSErrorWriter(this);
+		errorWriter = new DefaultUWSErrorWriter(logger);
 
 		// Initialize the list of jobs:
 		mapJobLists = new LinkedHashMap<String,JobList>();
@@ -209,11 +211,15 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		addSerializer(new JSONSerializer());
 
 		try{
+			// Initialize the service:
 			initUWS();
-			logger.uwsInitialized(this);
+
+			// Log the successful initialization:
+			logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
+
 		}catch(UWSException ue){
-			logger.error("UWS initialization impossible !", ue);
-			throw new ServletException("Error while initializing UWS ! See the log for more details.");
+			logger.logUWS(LogLevel.FATAL, null, "INIT", "Can't execute the custom initialization of this UWS service (UWSServlet.initUWS())!", ue);
+			throw new ServletException(INIT_ERROR_MSG);
 		}
 	}
 
@@ -241,11 +247,28 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		super.service(req, resp);
 	}
 
+	protected static String lastRequestID = null;
+
+	protected synchronized String generateRequestID(final HttpServletRequest request){
+		String id;
+		do{
+			id = System.currentTimeMillis() + "";
+		}while(lastRequestID != null && lastRequestID.startsWith(id));
+		lastRequestID = id;
+		return id;
+	}
+
 	@Override
 	protected final void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
 		String uwsAction = null;
 		JobOwner user = null;
 
+		// Generate a unique ID for this request execution (for log purpose only):
+		final String reqID = generateRequestID(req);
+
+		// Log the reception of the request:
+		logger.logHttp(LogLevel.INFO, req, reqID, null, null);
+
 		try{
 			String method = req.getMethod();
 
@@ -282,11 +305,8 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 					uwsAction = UWSAction.GET_JOB_PARAM;
 					doGetJobParam(requestUrl, req, resp, user);
 
-				}else{
-					logger.httpRequest(req, user, null, 0, null, null);
-					super.service(req, resp);
-					return;
-				}
+				}else
+					throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
 			}// METHOD POST:
 			else if (method.equals("POST")){
@@ -310,11 +330,8 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 					uwsAction = UWSAction.DESTROY_JOB;
 					doDestroyJob(requestUrl, req, resp, user);
 
-				}else{
-					logger.httpRequest(req, user, null, 0, null, null);
-					super.service(req, resp);
-					return;
-				}
+				}else
+					throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
 			}// METHOD PUT:
 			else if (method.equals("PUT")){
@@ -323,11 +340,8 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 					uwsAction = UWSAction.SET_JOB_PARAM;
 					doSetJobParam(requestUrl, req, resp, user);
 
-				}else{
-					logger.httpRequest(req, user, null, 0, null, null);
-					super.service(req, resp);
-					return;
-				}
+				}else
+					throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
 			}// METHOD DELETE:
 			else if (method.equals("DELETE")){
@@ -336,31 +350,24 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 					uwsAction = UWSAction.DESTROY_JOB;
 					doDestroyJob(requestUrl, req, resp, user);
 
-				}else{
-					logger.httpRequest(req, user, null, 0, null, null);
-					super.service(req, resp);
-					return;
-				}
+				}else
+					throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
-			}// ELSE => DEFAULT BEHAVIOR:
-			else{
-				logger.httpRequest(req, user, null, 0, null, null);
-				super.service(req, resp);
-				return;
-			}
+			}// ELSE ERROR:
+			else
+				throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
 
 			resp.flushBuffer();
-			logger.httpRequest(req, user, uwsAction, HttpServletResponse.SC_OK, "[OK]", null);
 
-		}catch(UWSException ex){
-			sendError(ex, req, user, uwsAction, resp);
+			// Log the successful execution of the action:
+			logger.logHttp(LogLevel.INFO, resp, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + uwsAction + "\" successfully executed.", null);
+
+		}catch(UWSException ue){
+			sendError(ue, req, reqID, user, uwsAction, resp);
 		}catch(ClientAbortException cae){
-			logger.info("Request aborted by the user !");
-			logger.httpRequest(req, user, uwsAction, HttpServletResponse.SC_OK, "[Client abort => ClientAbortException]", null);
+			logger.logHttp(LogLevel.INFO, req, reqID, "HTTP " + UWSException.OK + " - Action \"" + uwsAction + "\" aborted by the client! [Client abort => ClientAbortException]", cae);
 		}catch(Throwable t){
-			logger.error("Request unexpectedly aborted !", t);
-			logger.httpRequest(req, user, uwsAction, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage(), t);
-			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
+			sendError(t, req, reqID, user, uwsAction, resp);
 		}
 	}
 
@@ -370,13 +377,22 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	protected void writeHomePage(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{
 		UWSSerializer serializer = getSerializer(req.getHeader("Accept"));
 		resp.setContentType(serializer.getMimeType());
-		String serialization = serializer.getUWS(this);
+		String serialization;
+		try{
+			serialization = serializer.getUWS(this);
+		}catch(Exception e){
+			if (!(e instanceof UWSException)){
+				getLogger().logUWS(LogLevel.WARNING, requestUrl, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
+				throw new UWSException(UWSException.NO_CONTENT, e, "No home page available for this UWS service!");
+			}else
+				throw (UWSException)e;
+		}
 		if (serialization != null){
 			PrintWriter output = resp.getWriter();
 			output.print(serialization);
 			output.flush();
 		}else
-			throw UWSExceptionFactory.incorrectSerialization(serialization, "the UWS " + getName());
+			throw new UWSException(UWSException.NO_CONTENT, "No home page available for this UWS service.");
 	}
 
 	protected void doListJob(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{
@@ -386,7 +402,15 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		// Write the jobs list:
 		UWSSerializer serializer = getSerializer(req.getHeader("Accept"));
 		resp.setContentType(serializer.getMimeType());
-		jobsList.serialize(resp.getOutputStream(), serializer, user);
+		try{
+			jobsList.serialize(resp.getOutputStream(), serializer, user);
+		}catch(Exception e){
+			if (!(e instanceof UWSException)){
+				getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize the jobs list \"" + jobsList.getName() + "\"!", e);
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the jobs list \"" + jobsList.getName() + "\"!");
+			}else
+				throw (UWSException)e;
+		}
 	}
 
 	protected void doAddJob(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{
@@ -395,7 +419,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 
 		// Forbids the job creation if the user has not the WRITE permission for the specified jobs list:
 		if (user != null && !user.hasWritePermission(jobsList))
-			throw UWSExceptionFactory.writePermissionDenied(user, true, jobsList.getName());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, jobsList.getName()));
 
 		// Create the job:
 		UWSJob newJob = createJob(req, user);
@@ -413,7 +437,12 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		JobList jobsList = getJobList(requestUrl.getJobListName());
 
 		// Destroy the job:
-		jobsList.destroyJob(requestUrl.getJobId(), user);
+		try{
+			jobsList.destroyJob(requestUrl.getJobId(), user);
+		}catch(UWSException ue){
+			getLogger().logUWS(LogLevel.ERROR, requestUrl, "DESTROY_JOB", "Can not destroy the job \"" + requestUrl.getJobId() + "\"!", ue);
+			throw ue;
+		}
 
 		// Make a redirection to the jobs list:
 		redirect(requestUrl.listJobs(jobsList.getName()).getRequestURL(), req, user, UWSAction.DESTROY_JOB, resp);
@@ -426,7 +455,15 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		// Write the job summary:
 		UWSSerializer serializer = getSerializer(req.getHeader("Accept"));
 		resp.setContentType(serializer.getMimeType());
-		job.serialize(resp.getOutputStream(), serializer, user);
+		try{
+			job.serialize(resp.getOutputStream(), serializer, user);
+		}catch(Exception e){
+			if (!(e instanceof UWSException)){
+				getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize the job \"" + job.getJobId() + "\"!", e);
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the job \"" + job.getJobId() + "\"!");
+			}else
+				throw (UWSException)e;
+		}
 	}
 
 	protected void doGetJobParam(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{
@@ -439,7 +476,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_RESULTS) && attributes.length > 1){
 			Result result = job.getResult(attributes[1]);
 			if (result == null)
-				throw UWSExceptionFactory.incorrectJobResult(job.getJobId(), attributes[1]);
+				throw new UWSException(UWSException.NOT_FOUND, "No result identified with \"" + attributes[1] + "\" in the job \"" + job.getJobId() + "\"!");
 			else if (result.getHref() != null && !result.getHref().trim().isEmpty() && !result.getHref().equalsIgnoreCase(req.getRequestURL().toString()))
 				redirect(result.getHref(), req, user, UWSAction.GET_JOB_PARAM, resp);
 			else{
@@ -448,6 +485,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 					input = getFileManager().getResultInput(result, job);
 					UWSToolBox.write(input, result.getMimeType(), result.getSize(), resp);
 				}catch(IOException ioe){
+					getLogger().logUWS(LogLevel.ERROR, result, "GET_RESULT", "Can not read the content of the result \"" + result.getId() + "\" of the job \"" + job.getJobId() + "\"!", ioe);
 					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the content of the result " + result.getId() + " (job ID: " + job.getJobId() + ").");
 				}finally{
 					if (input != null)
@@ -458,13 +496,14 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		else if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) && attributes.length > 1 && attributes[1].equalsIgnoreCase("details")){
 			ErrorSummary error = job.getErrorSummary();
 			if (error == null)
-				throw UWSExceptionFactory.noErrorSummary(job.getJobId());
+				throw new UWSException(UWSException.NOT_FOUND, "No error summary for the job \"" + job.getJobId() + "\"!");
 			else{
 				InputStream input = null;
 				try{
 					input = getFileManager().getErrorInput(error, job);
 					UWSToolBox.write(input, "text/plain", getFileManager().getErrorSize(error, job), resp);
 				}catch(IOException ioe){
+					getLogger().logUWS(LogLevel.ERROR, error, "GET_ERROR", "Can not read the details of the error summary of the job \"" + job.getJobId() + "\"!", ioe);
 					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the error details (job ID: " + job.getJobId() + ").");
 				}finally{
 					if (input != null)
@@ -476,11 +515,22 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 			// Write the value/content of the selected attribute:
 			UWSSerializer serializer = getSerializer(req.getHeader("Accept"));
 			String uwsField = attributes[0];
-			if (uwsField == null || uwsField.trim().isEmpty() || (attributes.length <= 1 && (uwsField.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) || uwsField.equalsIgnoreCase(UWSJob.PARAM_RESULTS) || uwsField.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS))))
+			boolean jobSerialization = false;
+			if (uwsField == null || uwsField.trim().isEmpty() || (attributes.length <= 1 && (uwsField.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) || uwsField.equalsIgnoreCase(UWSJob.PARAM_RESULTS) || uwsField.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)))){
 				resp.setContentType(serializer.getMimeType());
-			else
+				jobSerialization = true;
+			}else
 				resp.setContentType("text/plain");
-			job.serialize(resp.getOutputStream(), attributes, serializer);
+			try{
+				job.serialize(resp.getOutputStream(), attributes, serializer);
+			}catch(Exception e){
+				if (!(e instanceof UWSException)){
+					String errorMsgPart = (jobSerialization ? "the job \"" + job.getJobId() + "\"" : "the parameter " + uwsField + " of the job \"" + job.getJobId() + "\"");
+					getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize " + errorMsgPart + "!", e);
+					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly " + errorMsgPart + "!");
+				}else
+					throw (UWSException)e;
+			}
 		}
 	}
 
@@ -502,19 +552,19 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	}
 
 	public UWSJob getJob(UWSUrl requestUrl, JobOwner user) throws UWSException{
-		// Get the jobs list:
-		JobList jobsList = getJobList(requestUrl.getJobListName());
-
 		// Get the job ID:
 		String jobId = requestUrl.getJobId();
-
-		if (jobId == null)
-			throw UWSExceptionFactory.missingJobID();
-
-		// Get the job:
-		UWSJob job = jobsList.getJob(jobId, user);
-		if (job == null)
-			throw UWSExceptionFactory.incorrectJobID(jobsList.getName(), jobId);
+		UWSJob job = null;
+
+		if (jobId != null){
+			// Get the jobs list:
+			JobList jobsList = getJobList(requestUrl.getJobListName());
+			// Get the job:
+			job = jobsList.getJob(jobId, user);
+			if (job == null)
+				throw new UWSException(UWSException.NOT_FOUND, "Incorrect job ID! The job \"" + jobId + "\" does not exist in the jobs list \"" + jobsList.getName() + "\".");
+		}else
+			throw new UWSException(UWSException.BAD_REQUEST, "Missing job ID!");
 
 		return job;
 	}
@@ -565,7 +615,6 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		response.setContentType(request.getContentType());
 		response.setHeader("Location", url);
 		response.flushBuffer();
-		logger.httpRequest(request, user, uwsAction, HttpServletResponse.SC_SEE_OTHER, "[Redirection toward " + url + "]", null);
 	}
 
 	/**
@@ -577,19 +626,62 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * 
 	 * @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)}.
+	 * @throws UWSException	If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse))}.
 	 * 
 	 * @see #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)
-	 * @see #writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)
+	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
 	 */
-	public void sendError(UWSException error, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException, IOException{
-		if (error.getHttpErrorCode() == UWSException.SEE_OTHER)
-			redirect(error.getMessage(), request, user, uwsAction, response);
-		else{
-			errorWriter.writeError(error, response, request, user, uwsAction);
+	public final void sendError(UWSException error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException{
+		if (error.getHttpErrorCode() == UWSException.SEE_OTHER){
+			// Log the redirection, if any:
+			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.SEE_OTHER + " [Redirection toward " + error.getMessage() + "] - Action \"" + uwsAction + "\" successfully executed.", null);
+			// Apply the redirection:
+			try{
+				redirect(error.getMessage(), request, user, uwsAction, response);
+			}catch(IOException ioe){
+				logger.logHttp(LogLevel.FATAL, request, reqID, "Can not redirect the response toward " + error.getMessage(), error);
+				throw new ServletException("Can not redirect the response! You should notify the administrator of the service (FATAL-" + reqID + "). However, while waiting a correction of this problem, you can manually go toward " + error.getMessage() + ".");
+			}
+		}else
+			sendError(error, request, reqID, user, uwsAction, response);
+	}
+
+	/**
+	 * <p>
+	 * 	Fills the response with the given error.
+	 * 	The stack trace of the error is printed on the standard output and then the function
+	 * 	{@link HttpServletResponse#sendError(int, String)} is called with the HTTP status code is {@link UWSException#INTERNAL_SERVER_ERROR}
+	 * 	and the message of the given exception.
+	 * </p>
+	 * 
+	 * 
+	 * @param error			The error to send/display.
+	 * @param request		The request which has caused the given error <i>(not used by default)</i>.
+	 * @param reqID			ID of the request.
+	 * @param user			The user which executes the given request.
+	 * @param uwsAction	The UWS action corresponding to the given request.
+	 * @param response		The response in which the error must be published.
+	 * 
+	 * @throws IOException	If there is an error when calling {@link HttpServletResponse#sendError(int, String)}.
+	 * @throws UWSException
+	 * 
+	 * @see {@link 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 ServletException{
+		try{
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(error, response, request, reqID, user, uwsAction);
+			// Log the error:
+			logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
+		}catch(IOException ioe){
+			logger.logHttp(LogLevel.FATAL, response, reqID, user, "Can not write the response!", ioe);
+			throw new ServletException("Can not write the error response: \"" + error.getMessage() + "\"! You should notify the administrator of the service (FATAL-" + reqID + ").");
 		}
 	}
 
@@ -798,6 +890,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	/**
 	 * @return The name.
 	 */
+	@Override
 	public final String getName(){
 		return name;
 	}
@@ -805,6 +898,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	/**
 	 * @return The description.
 	 */
+	@Override
 	public final String getDescription(){
 		return description;
 	}
@@ -812,25 +906,29 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	/* ******************** */
 	/* JOBS LIST MANAGEMENT */
 	/* ******************** */
+	@Override
 	public final Iterator<JobList> iterator(){
 		return mapJobLists.values().iterator();
 	}
 
+	@Override
 	public JobList getJobList(String name) throws UWSException{
 		if (name != null)
 			name = name.trim();
 		if (name == null || name.length() == 0)
-			throw UWSExceptionFactory.missingJobListName();
+			throw new UWSException(UWSException.BAD_REQUEST, "Missing job list name!");
 		else if (!mapJobLists.containsKey(name))
-			throw UWSExceptionFactory.incorrectJobListName(name);
+			throw new UWSException(UWSException.NOT_FOUND, "Incorrect job list name ! The jobs list \"" + name + "\" does not exist.");
 		else
 			return mapJobLists.get(name);
 	}
 
+	@Override
 	public final int getNbJobList(){
 		return mapJobLists.size();
 	}
 
+	@Override
 	public final boolean addJobList(JobList jl){
 		if (jl == null)
 			return false;
@@ -841,44 +939,14 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 			jl.setUWS(this);
 			mapJobLists.put(jl.getName(), jl);
 		}catch(IllegalStateException ise){
-			logger.error("The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + " !", ise);
+			logger.logUWS(LogLevel.ERROR, jl, "ADD_JOB_LIST", "The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + ": it may already be associated with one!", ise);
 			return false;
 		}
 
 		return true;
 	}
 
-	/*public final JobList removeJobList(String name){
-		JobList jl = mapJobLists.get(name);
-		if (jl != null){
-			if (removeJobList(jl))
-				return jl;
-		}
-		return null;
-	}*/
-
-	/*
-	 * Removes the given jobs list from this UWS.
-	 * 
-	 * @param jl	The jobs list to remove.
-	 * 
-	 * @return		<i>true</i> if the jobs list has been successfully removed, <i>false</i> otherwise.
-	 * 
-	 * @see JobList#removeAll()
-	 * @see JobList#setUWS(AbstractUWS)
-	 *
-	public boolean removeJobList(JobList jl){
-		if (jl == null)
-			return false;
-
-		jl = mapJobLists.remove(jl.getName());
-		if (jl != null){
-			jl.removeAll();
-			jl.setUWS(null);
-		}
-		return jl != null;
-	}*/
-
+	@Override
 	public final boolean destroyJobList(String name){
 		return destroyJobList(mapJobLists.get(name));
 	}
@@ -903,7 +971,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 				jl.clear();
 				jl.setUWS(null);
 			}catch(IllegalStateException ise){
-				getLogger().warning("Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\", because: " + ise.getMessage());
+				logger.logUWS(LogLevel.WARNING, jl, "DESTROY_JOB_LIST", "Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\"!", ise);
 			}
 		}
 		return jl != null;
@@ -946,6 +1014,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * @see AcceptHeader#AcceptHeader(String)
 	 * @see AcceptHeader#getOrderedMimeTypes()
 	 */
+	@Override
 	public final UWSSerializer getSerializer(String mimeTypes) throws UWSException{
 		UWSSerializer choosenSerializer = null;
 
@@ -963,7 +1032,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		if (choosenSerializer == null){
 			choosenSerializer = serializers.get(defaultSerializer);
 			if (choosenSerializer == null)
-				throw UWSExceptionFactory.missingSerializer(mimeTypes + " (given MIME types) and " + defaultSerializer + " (default serializer MIME type)");
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeTypes + " (given MIME types) and " + defaultSerializer + " (default serializer MIME type)" + "!");
 		}
 
 		return choosenSerializer;
@@ -1017,6 +1086,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * 
 	 * @return	The used UserIdentifier (MAY BE NULL).
 	 */
+	@Override
 	public final UserIdentifier getUserIdentifier(){
 		return userIdentifier;
 	}
@@ -1038,6 +1108,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * 
 	 * @return	Its UWS URL interpreter.
 	 */
+	@Override
 	public final UWSUrl getUrlInterpreter(){
 		return urlInterpreter;
 	}
@@ -1061,6 +1132,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * 
 	 * @return Its backup manager.
 	 */
+	@Override
 	public final UWSBackupManager getBackupManager(){
 		return backupManager;
 	}
diff --git a/src/uws/service/UWSUrl.java b/src/uws/service/UWSUrl.java
index 2376fa777a700f1ac61ac48702458d27c18797ce..78e46cfaa7b2ec1c92f9629fea62b2926c51cd05 100644
--- a/src/uws/service/UWSUrl.java
+++ b/src/uws/service/UWSUrl.java
@@ -27,7 +27,6 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
-import uws.UWSException;
 import uws.UWSToolBox;
 import uws.job.UWSJob;
 
@@ -35,7 +34,7 @@ import uws.job.UWSJob;
  * This class helps managing with UWS URLs and URIs.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 06/2014
+ * @version 4.1 (09/2014)
  */
 public class UWSUrl implements Serializable {
 	private static final long serialVersionUID = 1L;
@@ -89,16 +88,16 @@ public class UWSUrl implements Serializable {
 	 * 
 	 * @param baseURI			The baseURI to consider in all URL or request parsing.
 	 * 
-	 * @throws UWSException		If the given baseURI is <i>null</i> or is an empty string.
+	 * @throws NullPointerException		If the given baseURI is <i>null</i> or is an empty string.
 	 */
-	public UWSUrl(String baseURI) throws UWSException{
+	public UWSUrl(String baseURI) throws NullPointerException{
 		if (baseURI == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "The given base UWS URI is NULL !");
+			throw new NullPointerException("The given base UWS URI is NULL!");
 
 		this.baseURI = normalizeURI(baseURI);
 
 		if (baseURI.length() == 0)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "The given base UWS URI is empty !");
+			throw new NullPointerException("The given base UWS URI is empty!");
 	}
 
 	/**
@@ -106,14 +105,14 @@ public class UWSUrl implements Serializable {
 	 * 
 	 * @param request		The request to parse to get the baseURI.
 	 * 
-	 * @throws UWSException	If the given request is <i>null</i> or if the extracted baseURI is <i>null</i> or is an empty string.
+	 * @throws NullPointerException	If the given request is <i>null</i> or if the extracted baseURI is <i>null</i> or is an empty string.
 	 * 
 	 * @see #extractBaseURI(HttpServletRequest)
 	 */
-	public UWSUrl(HttpServletRequest request) throws UWSException{
+	public UWSUrl(HttpServletRequest request) throws NullPointerException{
 		String uri = extractBaseURI(request);
 		if (uri == null)
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "The extracted base UWS URI is NULL !");
+			throw new NullPointerException("The extracted base UWS URI is NULL!");
 
 		baseURI = normalizeURI(uri);
 	}
diff --git a/src/uws/service/actions/AddJob.java b/src/uws/service/actions/AddJob.java
index a7265016ad2f5f296e7db559587cdecbca4ec5e3..5dd182b4b29ef1bfc289902343df18348caee7ef 100644
--- a/src/uws/service/actions/AddJob.java
+++ b/src/uws/service/actions/AddJob.java
@@ -16,7 +16,8 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -26,14 +27,12 @@ import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
 import uws.UWSExceptionFactory;
-
 import uws.job.JobList;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>The "Add Job" action of a UWS.</p>
@@ -43,8 +42,8 @@ import uws.service.UWSUrl;
  * <p>This action creates a new job and adds it to the specified jobs list.
  * The response of this action is a redirection to the new job resource (that is to say: a redirection to the job summary of the new job).</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class AddJob extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -100,10 +99,16 @@ public class AddJob extends UWSAction {
 
 		// Forbids the job creation if the user has not the WRITE permission for the specified jobs list:
 		if (user != null && !user.hasWritePermission(jobsList))
-			throw UWSExceptionFactory.writePermissionDenied(user, true, jobsList.getName());
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, jobsList.getName()));
 
 		// Create the job:
-		UWSJob newJob = uws.getFactory().createJob(request, user);
+		UWSJob newJob;
+		try{
+			newJob = uws.getFactory().createJob(request, user);
+		}catch(UWSException ue){
+			getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "ADD_JOB", "Can not create a new job!", ue);
+			throw ue;
+		}
 
 		// Add it to the jobs list:
 		if (jobsList.addNewJob(newJob) != null){
@@ -113,7 +118,7 @@ public class AddJob extends UWSAction {
 
 			return true;
 		}else
-			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Unable to add the new job to the jobs list. (ID of the new job = \"" + newJob.getJobId() + "\" ; ID already used = " + (jobsList.getJob(newJob.getJobId()) != null) + ")");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Unable to add the new job to the jobs list for an unknown reason. (ID of the new job = \"" + newJob.getJobId() + "\" ; ID already used = " + (jobsList.getJob(newJob.getJobId()) != null) + ")");
 	}
 
 }
diff --git a/src/uws/service/actions/DestroyJob.java b/src/uws/service/actions/DestroyJob.java
index d6e5d33ef9bafda2d79580816a8b8d33f7932dd8..5b7625f46996560fe12165d0d8c76bb5a3e67fdd 100644
--- a/src/uws/service/actions/DestroyJob.java
+++ b/src/uws/service/actions/DestroyJob.java
@@ -16,7 +16,8 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -25,14 +26,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-
 import uws.job.JobList;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>The "Destroy Job" action of a UWS.</p>
@@ -42,8 +41,8 @@ import uws.service.UWSUrl;
  * <p>This action destroys the job specified in the UWS URL.
  * The response of this action is a redirection to the jobs list.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class DestroyJob extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -100,7 +99,13 @@ public class DestroyJob extends UWSAction {
 		JobList jobsList = getJobsList(urlInterpreter);
 
 		// Destroy the job:
-		boolean destroyed = jobsList.destroyJob(urlInterpreter.getJobId(), user);
+		boolean destroyed;
+		try{
+			destroyed = jobsList.destroyJob(urlInterpreter.getJobId(), user);
+		}catch(UWSException ue){
+			getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "DESTROY_JOB", "Can not destroy the job \"" + urlInterpreter.getJobId() + "\"!", ue);
+			throw ue;
+		}
 
 		// Make a redirection to the jobs list:
 		uws.redirect(urlInterpreter.listJobs(jobsList.getName()).getRequestURL(), request, user, getName(), response);
diff --git a/src/uws/service/actions/GetJobParam.java b/src/uws/service/actions/GetJobParam.java
index 048ceffaaa583fb12050a1bb614d7ce9e982cb48..0df5a2b783e7ae58dad6df7fa545fc5fb7351c3c 100644
--- a/src/uws/service/actions/GetJobParam.java
+++ b/src/uws/service/actions/GetJobParam.java
@@ -16,31 +16,27 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 
 import javax.servlet.ServletOutputStream;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
-
 import uws.job.ErrorSummary;
 import uws.job.Result;
 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;
 
 /**
  * <p>The "Get Job Parameter" action of a UWS.</p>
@@ -52,8 +48,8 @@ import uws.service.UWSUrl;
  * whereas it is a complex type (i.e. results, parameters, ...) the value is the serialization of the job attribute itself.
  * The serializer is choosen in function of the HTTP Accept header.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class GetJobParam extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -116,7 +112,7 @@ public class GetJobParam extends UWSAction {
 		if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_RESULTS) && attributes.length > 1){
 			Result result = job.getResult(attributes[1]);
 			if (result == null)
-				throw UWSExceptionFactory.incorrectJobResult(job.getJobId(), attributes[1]);
+				throw new UWSException(UWSException.NOT_FOUND, "No result identified with \"" + attributes[1] + "\" in the job \"" + job.getJobId() + "\"!");
 			else if (result.isRedirectionRequired())
 				uws.redirect(result.getHref(), request, user, getName(), response);
 			else{
@@ -125,6 +121,7 @@ public class GetJobParam extends UWSAction {
 					input = uws.getFileManager().getResultInput(result, job);
 					UWSToolBox.write(input, result.getMimeType(), result.getSize(), response);
 				}catch(IOException ioe){
+					getLogger().logUWS(LogLevel.ERROR, result, "GET_RESULT", "Can not read the content of the result \"" + result.getId() + "\" of the job \"" + job.getJobId() + "\"!", ioe);
 					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the content of the result " + result.getId() + " (job ID: " + job.getJobId() + ").");
 				}finally{
 					if (input != null)
@@ -135,13 +132,14 @@ public class GetJobParam extends UWSAction {
 		else if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) && attributes.length > 1 && attributes[1].equalsIgnoreCase("details")){
 			ErrorSummary error = job.getErrorSummary();
 			if (error == null)
-				throw UWSExceptionFactory.noErrorSummary(job.getJobId());
+				throw new UWSException(UWSException.NOT_FOUND, "No error summary for the job \"" + job.getJobId() + "\"!");
 			else{
 				InputStream input = null;
 				try{
 					input = uws.getFileManager().getErrorInput(error, job);
 					UWSToolBox.write(input, "text/plain", uws.getFileManager().getErrorSize(error, job), response);
 				}catch(IOException ioe){
+					getLogger().logUWS(LogLevel.ERROR, error, "GET_ERROR", "Can not read the details of the error summary of the job \"" + job.getJobId() + "\"!", ioe);
 					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the error details (job ID: " + job.getJobId() + ").");
 				}finally{
 					if (input != null)
@@ -154,11 +152,22 @@ public class GetJobParam extends UWSAction {
 			// Write the value/content of the selected attribute:
 			UWSSerializer serializer = uws.getSerializer(request.getHeader("Accept"));
 			String uwsField = attributes[0];
-			if (uwsField == null || uwsField.trim().isEmpty() || (attributes.length <= 1 && (uwsField.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) || uwsField.equalsIgnoreCase(UWSJob.PARAM_RESULTS) || uwsField.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS))))
+			boolean jobSerialization = false;
+			if (uwsField == null || uwsField.trim().isEmpty() || (attributes.length <= 1 && (uwsField.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) || uwsField.equalsIgnoreCase(UWSJob.PARAM_RESULTS) || uwsField.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)))){
 				response.setContentType(serializer.getMimeType());
-			else
+				jobSerialization = true;
+			}else
 				response.setContentType("text/plain");
-			job.serialize(response.getOutputStream(), attributes, serializer);
+			try{
+				job.serialize(response.getOutputStream(), attributes, serializer);
+			}catch(Exception e){
+				if (!(e instanceof UWSException)){
+					String errorMsgPart = (jobSerialization ? "the job \"" + job.getJobId() + "\"" : "the parameter " + uwsField + " of the job \"" + job.getJobId() + "\"");
+					getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "SERIALIZE", "Can not serialize " + errorMsgPart + "!", e);
+					throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly " + errorMsgPart + "!");
+				}else
+					throw (UWSException)e;
+			}
 		}
 
 		return true;
diff --git a/src/uws/service/actions/JobSummary.java b/src/uws/service/actions/JobSummary.java
index adf9ed059e341dcaa2cbf355c3a45dc08c195df3..d645259e9d7eb06e8c5c29aa6e69484893ce1337 100644
--- a/src/uws/service/actions/JobSummary.java
+++ b/src/uws/service/actions/JobSummary.java
@@ -16,26 +16,23 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 
 import javax.servlet.ServletOutputStream;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-
 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;
 
 /**
  * <p>The "Get Job" action of a UWS.</p>
@@ -45,8 +42,8 @@ import uws.service.UWSUrl;
  * <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>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class JobSummary extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -103,7 +100,15 @@ public class JobSummary extends UWSAction {
 		// Write the job summary:
 		UWSSerializer serializer = uws.getSerializer(request.getHeader("Accept"));
 		response.setContentType(serializer.getMimeType());
-		job.serialize(response.getOutputStream(), serializer, user);
+		try{
+			job.serialize(response.getOutputStream(), serializer, user);
+		}catch(Exception e){
+			if (!(e instanceof UWSException)){
+				getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "SERIALIZE", "Can not serialize the job \"" + job.getJobId() + "\"!", e);
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the job \"" + job.getJobId() + "\"!");
+			}else
+				throw (UWSException)e;
+		}
 
 		return true;
 	}
diff --git a/src/uws/service/actions/ListJobs.java b/src/uws/service/actions/ListJobs.java
index dfeeb4fee6413d55d03114b069f88a4f4a33216f..18be0642f222c41dab5e461bf8e5b4fd23852d55 100644
--- a/src/uws/service/actions/ListJobs.java
+++ b/src/uws/service/actions/ListJobs.java
@@ -16,26 +16,23 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 
 import javax.servlet.ServletOutputStream;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-
 import uws.job.JobList;
-
 import uws.job.serializer.UWSSerializer;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>The "List Jobs" action of a UWS.</p>
@@ -45,8 +42,8 @@ import uws.service.UWSUrl;
  * <p>This action returns the list of jobs contained in the jobs list specified by the URL of the request.
  * This list is serialized by the {@link UWSSerializer} choosed in function of the HTTP Accept header.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class ListJobs extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -102,7 +99,15 @@ public class ListJobs extends UWSAction {
 		// Write the jobs list:
 		UWSSerializer serializer = uws.getSerializer(request.getHeader("Accept"));
 		response.setContentType(serializer.getMimeType());
-		jobsList.serialize(response.getOutputStream(), serializer, user);
+		try{
+			jobsList.serialize(response.getOutputStream(), serializer, user);
+		}catch(Exception e){
+			if (!(e instanceof UWSException)){
+				getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "SERIALIZE", "Can not serialize the jobs list \"" + jobsList.getName() + "\"!", e);
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the jobs list \"" + jobsList.getName() + "\"!");
+			}else
+				throw (UWSException)e;
+		}
 
 		return true;
 	}
diff --git a/src/uws/service/actions/SetJobParam.java b/src/uws/service/actions/SetJobParam.java
index 02026bbff3fa922f0552a16b597afa26a2ea6c45..5d9a92a05c46c487e8919631f4bbcd7cab6bdfc7 100644
--- a/src/uws/service/actions/SetJobParam.java
+++ b/src/uws/service/actions/SetJobParam.java
@@ -16,7 +16,8 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -25,14 +26,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-
 import uws.job.UWSJob;
-
 import uws.job.parameters.UWSParameters;
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>The "Set Job Parameter" action of a UWS.</p>
@@ -42,8 +41,8 @@ import uws.service.UWSUrl;
  * <p>This action sets the value of the specified job attribute.
  * The response of this action is a redirection to the job summary.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class SetJobParam extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -99,7 +98,13 @@ public class SetJobParam extends UWSAction {
 		// Get the job:
 		UWSJob job = getJob(urlInterpreter);
 
-		UWSParameters params = uws.getFactory().createUWSParameters(request);
+		UWSParameters params;
+		try{
+			params = uws.getFactory().createUWSParameters(request);
+		}catch(UWSException ue){
+			getLogger().logUWS(LogLevel.ERROR, request, "SET_PARAM", "Can not parse the sent UWS parameters!", ue);
+			throw ue;
+		}
 
 		// Update the job parameters:
 		boolean updated = job.addOrUpdateParameters(params, user);
diff --git a/src/uws/service/actions/ShowHomePage.java b/src/uws/service/actions/ShowHomePage.java
index c43749750a5c16ab68f84a14037dcaceeed0c748..c713c9222d3f135b66da07b9c052cd0cc46cd899 100644
--- a/src/uws/service/actions/ShowHomePage.java
+++ b/src/uws/service/actions/ShowHomePage.java
@@ -16,28 +16,25 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-
 import java.net.URL;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
-
 import uws.job.serializer.UWSSerializer;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>The "Show UWS Home Page" action of a UWS.</p>
@@ -46,8 +43,8 @@ import uws.service.UWSUrl;
  * 
  * <p>This action displays the UWS home page.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  */
 public class ShowHomePage extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -99,16 +96,29 @@ public class ShowHomePage extends UWSAction {
 	 */
 	@Override
 	public boolean apply(UWSUrl urlInterpreter, JobOwner user, HttpServletRequest request, HttpServletResponse response) throws UWSException, IOException{
+
 		if (uws.isDefaultHomePage()){
 			UWSSerializer serializer = uws.getSerializer(request.getHeader("Accept"));
 			response.setContentType(serializer.getMimeType());
-			String serialization = serializer.getUWS(uws);
+			// Get a short and simple serialization of this UWS:
+			String serialization;
+			try{
+				serialization = serializer.getUWS(uws);
+			}catch(Exception e){
+				if (!(e instanceof UWSException)){
+					getLogger().logUWS(LogLevel.WARNING, urlInterpreter, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
+					throw new UWSException(UWSException.NO_CONTENT, e, "No home page available for this UWS service!");
+				}else
+					throw (UWSException)e;
+			}
+			// Write the simple UWS serialization in the given response:
 			if (serialization != null){
 				PrintWriter output = response.getWriter();
 				output.print(serialization);
 				output.flush();
 			}else
-				throw UWSExceptionFactory.incorrectSerialization(serialization, "the UWS " + uws.getName());
+				throw new UWSException(UWSException.NO_CONTENT, "No home page available for this UWS service.");
+
 		}else{
 			if (uws.isHomePageRedirection())
 				uws.redirect(uws.getHomePage(), request, user, getName(), response);
diff --git a/src/uws/service/actions/UWSAction.java b/src/uws/service/actions/UWSAction.java
index 381336386e74807182c44d071d2dcd913d7bfb20..7de64b9f1dc8a1cb70b5b5e5743e6da9fd13457d 100644
--- a/src/uws/service/actions/UWSAction.java
+++ b/src/uws/service/actions/UWSAction.java
@@ -16,7 +16,8 @@ package uws.service.actions;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -26,15 +27,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
-
 import uws.job.JobList;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWSService;
 import uws.service.UWSUrl;
+import uws.service.log.UWSLog;
 
 /**
  * <p>Action of a UWS (i.e. "List Jobs", "Get Job", etc...). An instance of a UWSAction can be added to a given UWS thanks to the method
@@ -43,8 +41,8 @@ import uws.service.UWSUrl;
  * <p><b><u>WARNING:</u> The action of a UWS have, each one, a different name. So be careful about the name of your UWS action !
  * By default the name of a UWS action is the full java name of the class !</b></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see UWSService
  */
@@ -93,6 +91,17 @@ public abstract class UWSAction implements Serializable {
 		return uws;
 	}
 
+	/**
+	 * Get the logger associated with this UWS service.
+	 * 
+	 * @return	UWS logger.
+	 * 
+	 * @since 4.1
+	 */
+	public final UWSLog getLogger(){
+		return uws.getLogger();
+	}
+
 	/**
 	 * <p>Gets the name of this UWS action. <b>MUST BE UNIQUE !</b></p>
 	 * 
@@ -139,9 +148,9 @@ public abstract class UWSAction implements Serializable {
 		if (jlName != null){
 			jobsList = uws.getJobList(jlName);
 			if (jobsList == null)
-				throw UWSExceptionFactory.incorrectJobListName(jlName);
+				throw new UWSException(UWSException.NOT_FOUND, "Incorrect job list name! The jobs list " + jlName + " does not exist.");
 		}else
-			throw UWSExceptionFactory.missingJobListName();
+			throw new UWSException(UWSException.BAD_REQUEST, "Missing job list name!");
 
 		return jobsList;
 	}
@@ -193,9 +202,9 @@ public abstract class UWSAction implements Serializable {
 			JobList jobsList = getJobsList(urlInterpreter);
 			job = jobsList.getJob(jobId, user);
 			if (job == null)
-				throw UWSExceptionFactory.incorrectJobID(jobsList.getName(), jobId);
+				throw new UWSException(UWSException.NOT_FOUND, "Incorrect job ID! The job \"" + jobId + "\" does not exist in the jobs list \"" + jobsList.getName() + "\".");
 		}else
-			throw UWSExceptionFactory.missingJobID();
+			throw new UWSException(UWSException.BAD_REQUEST, "Missing job ID!");
 
 		return job;
 	}
@@ -245,12 +254,12 @@ public abstract class UWSAction implements Serializable {
 
 		if (jobId != null){
 			if (jobsList == null)
-				throw UWSExceptionFactory.missingJobListName();
+				throw new UWSException(UWSException.BAD_REQUEST, "Missing job list name!");
 			job = jobsList.getJob(jobId, user);
 			if (job == null)
-				throw UWSExceptionFactory.incorrectJobID(jobsList.getName(), jobId);
+				throw new UWSException(UWSException.NOT_FOUND, "Incorrect job ID! The job \"" + jobId + "\" does not exist in the jobs list \"" + jobsList.getName() + "\".");
 		}else
-			throw UWSExceptionFactory.missingJobID();
+			throw new UWSException(UWSException.BAD_REQUEST, "Missing job ID!");
 
 		return job;
 	}
diff --git a/src/uws/service/backup/DefaultUWSBackupManager.java b/src/uws/service/backup/DefaultUWSBackupManager.java
index 2a9db4b418bc27e94ba27f1d975c870bfdafadd2..be9a0cf60d2d7cf6106a72c8cbb945c173c0d6a4 100644
--- a/src/uws/service/backup/DefaultUWSBackupManager.java
+++ b/src/uws/service/backup/DefaultUWSBackupManager.java
@@ -16,14 +16,14 @@ package uws.service.backup;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
@@ -42,24 +42,19 @@ import org.json.JSONWriter;
 import org.json.Json4Uws;
 
 import uws.UWSException;
-import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
-
 import uws.job.ErrorSummary;
 import uws.job.ErrorType;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.parameters.UWSParameters;
 import uws.job.serializer.JSONSerializer;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWS;
 import uws.service.file.UWSFileManager;
-
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Default implementation of the interface {@link UWSBackupManager}.</p>
@@ -80,8 +75,8 @@ import uws.service.log.UWSLog;
  * </ul>
  * <p>Another positive value will be considered as the frequency (in milliseconds) of the automatic backup (= {@link #saveAll()}).</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class DefaultUWSBackupManager implements UWSBackupManager {
 
@@ -181,7 +176,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 		this.backupFreq = frequency;
 
 		if (byUser && uws.getUserIdentifier() == null)
-			throw new UWSException("Impossible to save/restore a UWS by user, if the user identification is disabled (no UserIdentifier is set to the UWS) !");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Impossible to save/restore a UWS by user if the user identification is disabled (no UserIdentifier is set to the UWS)!");
 
 		if (backupFreq == AT_USER_ACTION && !byUser)
 			backupFreq = MANUAL;
@@ -206,6 +201,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 		return enabled;
 	}
 
+	@Override
 	public final void setEnabled(boolean enabled){
 		this.enabled = enabled;
 		if (backupFreq > 0){
@@ -310,6 +306,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 	/* SAVE METHODS */
 	/* ************ */
 
+	@Override
 	public int[] saveAll(){
 		if (!enabled)
 			return null;
@@ -362,7 +359,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 						out.value(getJSONUser(user));
 						nbSavedOwners++;
 					}catch(JSONException je){
-						getLogger().error("Unexpected JSON error while saving the user '" + user.getID() + "' !", je);
+						getLogger().logUWS(LogLevel.ERROR, user, "BACKUP", "Unexpected JSON error while saving the user '" + user.getID() + "'!", je);
 					}
 				}
 				out.endArray();
@@ -378,9 +375,9 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 							nbSavedJobs++;
 							writer.flush();
 						}catch(UWSException ue){
-							getLogger().error("Unexpected UWS error while saving the job '" + job.getJobId() + "' !", ue);
+							getLogger().logUWS(LogLevel.ERROR, job, "BACKUP", "Unexpected UWS error while saving the job '" + job.getJobId() + "'!", ue);
 						}catch(JSONException je){
-							getLogger().error("Unexpected JSON error while saving the job '" + job.getJobId() + "' !", je);
+							getLogger().logUWS(LogLevel.ERROR, job, "BACKUP", "Unexpected JSON error while saving the job '" + job.getJobId() + "'!", je);
 						}
 					}
 				}
@@ -390,9 +387,9 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 				out.endObject();
 
 			}catch(JSONException je){
-				getLogger().error("Unexpected JSON error while saving the whole UWS !", je);
+				getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the whole UWS !", je);
 			}catch(IOException ie){
-				getLogger().error("Unexpected IO error while saving the whole UWS !", ie);
+				getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected IO error while saving the whole UWS !", ie);
 			}finally{
 				// Close the writer:
 				if (writer != null)
@@ -402,13 +399,14 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 
 		// Build the report and log it:
 		int[] report = new int[]{nbSavedJobs,nbJobs,nbSavedOwners,nbOwners};
-		getLogger().uwsSaved(uws, report);
+		getLogger().logUWS(LogLevel.INFO, report, "BACKUPED", "UWS backuped! (" + nbSavedJobs + "/" + nbJobs + " jobs backuped ; " + nbSavedOwners + "/" + nbOwners + " users backuped)", null);
 
 		lastBackup = new Date();
 
 		return report;
 	}
 
+	@Override
 	public int[] saveOwner(JobOwner user){
 		if (!enabled)
 			return null;
@@ -453,9 +451,9 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 						saveReport[0]++;
 						writer.flush();
 					}catch(JSONException je){
-						getLogger().error("Unexpected JSON error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'  !", je);
+						getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'!", je);
 					}catch(UWSException ue){
-						getLogger().error("Unexpected UWS error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'  !", ue);
+						getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected UWS error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'!", ue);
 					}
 				}
 			}
@@ -465,16 +463,16 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			out.endObject();
 
 			// Log the "save" report:
-			getLogger().ownerJobsSaved(user, saveReport);
+			getLogger().logUWS(LogLevel.INFO, saveReport, "BACKUPED", "UWS backuped! (" + saveReport[0] + "/" + saveReport[1] + " users backuped)", null);
 
 			lastBackup = new Date();
 
 			return saveReport;
 
 		}catch(IOException ie){
-			getLogger().error("Unexpected IO error while saving the jobs of user '" + user.getID() + "'  !", ie);
+			getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected IO error while saving the jobs of user '" + user.getID() + "'!", ie);
 		}catch(JSONException je){
-			getLogger().error("Unexpected JSON error while saving the jobs of user '" + user.getID() + "'  !", je);
+			getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the jobs of user '" + user.getID() + "'!", je);
 		}finally{
 			// Close the writer:
 			if (writer != null)
@@ -548,6 +546,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 	/* RESTORATION METHODS */
 	/* ******************* */
 
+	@Override
 	public int[] restoreAll(){
 		// Removes all current jobs from the UWS before restoring it from files:
 		for(JobList jl : uws)
@@ -564,7 +563,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 		// Get the list of the input streams (on all the backup files to read):
 		if (byUser){
 			if (!userIdentificationEnabled){
-				getLogger().error("[restoration] Impossible to restore a UWS by user if the user identification is disabled (that's to say, the UWS has no UserIdentifier) !");
+				getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Impossible to restore a UWS by user if the user identification is disabled (that's to say, the UWS has no UserIdentifier)!", null);
 				return null;
 			}else
 				itInput = fileManager.getAllUserBackupInputs();
@@ -572,7 +571,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			try{
 				itInput = new SingleInputIterator(fileManager.getBackupInput());
 			}catch(IOException ioe){
-				getLogger().error("[restoration] Restoration of the UWS " + uws.getName() + " failed because an unexpected IO error has occured.", ioe);
+				getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Restoration of the UWS " + uws.getName() + " failed because an unexpected IO error has occured.", ioe);
 				return null;
 			}
 		}
@@ -588,7 +587,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 
 			HashMap<String,JobOwner> users = new HashMap<String,JobOwner>();
 			String key;
-			JSONObject object;
+			JSONObject object = null;
 
 			try{
 				// Reads progressively the general structure (which is theoretically a JSON object):
@@ -623,7 +622,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 								}
 							}
 						}catch(UWSException ue){
-							getLogger().error("[restoration] A job owner can not be restored !", ue);
+							getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "A job owner can not be restored!", ue);
 							//break;	// Because, the key "user" is found ONLY in the backup file of a user. If the user can not be restored, its jobs won't be !
 						}
 
@@ -649,7 +648,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 									}
 								}
 							}catch(UWSException ue){
-								getLogger().error("[restoration] The " + nbUsers + "-th user can not be restored !", ue);
+								getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "The " + nbUsers + "-th user can not be restored!", ue);
 							}
 						}
 
@@ -670,26 +669,26 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 								if (restoreJob(object, users))
 									nbRestoredJobs++;
 							}catch(UWSException ue){
-								getLogger().error("[restoration] The " + nbJobs + "-th job can not be restored !", ue);
+								getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "The " + nbJobs + "-th job can not be restored!", ue);
 							}
 						}
 
 					}// any other key is ignore but with a warning message:
 					else
-						getLogger().warning("[restoration] Key '" + key + "' ignored because unknown ! The UWS may be not completely restored !");
+						getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", "Key '" + key + "' ignored because unknown! The UWS may be not completely restored.", null);
 				}
 			}catch(JSONException je){
-				getLogger().error("[restoration] Incorrect JSON format for a UWS backup file !", je);
+				getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format for a UWS backup file!", je);
 				return null;
 			}catch(Exception e){
-				getLogger().error("[restoration] Unexpected error while restoring the UWS !", e);
+				getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Unexpected error while restoring the UWS!", e);
 				return null;
 			}finally{
 				// Close the reader:
 				try{
 					inputStream.close();
 				}catch(IOException ioe){
-					getLogger().error("[restoration] Can not close the input stream opened on a user backup file !", ioe);
+					getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Can not close the input stream opened on a user backup file!", ioe);
 				}
 				// Set the last restoration date:
 				lastRestoration = new Date();
@@ -697,11 +696,11 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 		}
 
 		if (!userIdentificationEnabled && nbUsers > 0)
-			getLogger().warning("[restoration] " + nbUsers + " job owners have not been restored because the user identification is disabled in this UWS ! => Jobs of these users have not been restored !");
+			getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", nbUsers + " job owners have not been restored because the user identification is disabled in this UWS! => Jobs of these users have not been restored.", null);
 
 		// Build the restoration report and log it:
 		int[] report = new int[]{nbRestoredJobs,nbJobs,nbRestoredUsers,nbUsers};
-		getLogger().uwsRestored(uws, report);
+		getLogger().logUWS(LogLevel.INFO, report, "RESTORED", "UWS restored! (" + nbRestoredJobs + "/" + nbJobs + " jobs restored ; " + nbRestoredUsers + "/" + nbUsers + " users restored)", null);
 
 		return report;
 	}
@@ -734,13 +733,13 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 				else
 					userData.put(key, json.getString(key));
 			}catch(JSONException je){
-				getLogger().error("[restoration] Incorrect JSON format for the serialization of the user " + ID + " !", je);
+				getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", "Incorrect JSON format for the serialization of the user \"" + ID + "\"! The restoration of this job may be incomplete.", je);
 			}
 		}
 
 		// Check that the ID exists:
 		if (ID == null || ID.trim().isEmpty())
-			throw UWSExceptionFactory.restoreUserImpossible("Missing user ID !");
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, null, "Impossible to restore a user from the backup file(s): no ID has been found!");
 
 		return uws.getUserIdentifier().restoreUser(ID, pseudo, userData);
 	}
@@ -808,7 +807,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 						tmp = json.getString(key);
 						inputParams.put(UWSJob.PARAM_DESTRUCTION_TIME, UWSJob.dateFormat.parse(tmp));
 					}catch(ParseException pe){
-						getLogger().error("[restoration] Incorrect date format for the '" + key + "' parameter !", pe);
+						getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe);
 					}
 
 				}// key=START_TIME:
@@ -818,7 +817,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 						Date d = UWSJob.dateFormat.parse(tmp);
 						startTime = d.getTime();
 					}catch(ParseException pe){
-						getLogger().error("[restoration] Incorrect date format for the '" + key + "' parameter !", pe);
+						getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe);
 					}
 
 				}// key=END_TIME:
@@ -828,7 +827,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 						Date d = UWSJob.dateFormat.parse(tmp);
 						endTime = d.getTime();
 					}catch(ParseException pe){
-						getLogger().error("[restoration] Incorrect date format for the '" + key + "' parameter !", pe);
+						getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe);
 					}
 
 				}// key=PARAMETERS:
@@ -845,24 +844,24 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 
 				}// Ignore any other key but with a warning message:
 				else
-					getLogger().warning("[restoration] The job attribute '" + key + "' has been ignored because unknown ! A job may be not completely restored !");
+					getLogger().logUWS(LogLevel.WARNING, json, "RESTORATION", "The job attribute '" + key + "' has been ignored because unknown! A job may be not completely restored!", null);
 
 			}catch(JSONException je){
-				getLogger().error("[restoration] Incorrect JSON format for a job serialization (attribute: \"" + key + "\") !", je);
+				getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect JSON format for a job serialization (attribute: \"" + key + "\")!", je);
 			}
 		}
 
 		// The job list name is REQUIRED:
 		if (jobListName == null || jobListName.isEmpty())
-			getLogger().error("[restoration] Missing job list name ! => Can not restore the job " + jobId + " !");
+			getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Missing job list name! => Can not restore the job " + jobId + "!", null);
 
 		// The job list name MUST correspond to an existing job list:
 		else if (uws.getJobList(jobListName) == null)
-			getLogger().error("[restoration] No job list named " + jobListName + " ! => Can not restore the job " + jobId + " !");
+			getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "No job list named " + jobListName + "! => Can not restore the job " + jobId + "!", null);
 
 		// The job ID is REQUIRED:
 		else if (jobId == null || jobId.isEmpty())
-			getLogger().error("[restoration] Missing job ID ! => Can not restore a job !");
+			getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Missing job ID! => Can not restore a job!", null);
 
 		// Otherwise: the job can be created and restored:
 		else{
@@ -871,7 +870,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 
 			// If the specified user is unknown, display a warning and create the job without owner:
 			if (ownerID != null && !ownerID.isEmpty() && owner == null){
-				getLogger().error("[restoration] Unknown job owner: " + ownerID + " ! => Can not restore the job " + jobId + " !");
+				getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Unknown job owner: " + ownerID + "! => Can not restore the job " + jobId + "!", null);
 				return false;
 			}
 
@@ -880,7 +879,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			try{
 				uwsParams = uws.getFactory().createUWSParameters(inputParams);
 			}catch(UWSException ue){
-				getLogger().error("[restoration] Error with at least one of the UWS parameters to restore !", ue);
+				getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Error with at least one of the UWS parameters to restore!", ue);
 				return false;
 			}
 
@@ -936,7 +935,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			try{
 				params.put(n, obj.get(n));
 			}catch(JSONException je){
-				getLogger().error("Incorrect JSON format for the serialization of the parameter '" + n + "' !", je);
+				getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for the serialization of the parameter '" + n + "'!", je);
 			}
 		}
 		return params;
@@ -965,7 +964,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 				if (r != null)
 					results.add(r);
 			}catch(JSONException je){
-				getLogger().error("Incorrect JSON format for the serialization of the " + (i + 1) + "-th result !", je);
+				getLogger().logUWS(LogLevel.ERROR, array, "RESTORATION", "Incorrect JSON format for the serialization of the " + (i + 1) + "-th result!", je);
 			}
 		}
 
@@ -1004,11 +1003,11 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			else if (n.equalsIgnoreCase("size"))
 				size = obj.getLong(n);
 			else
-				getLogger().warning("[restoration] The result parameter '" + n + "' has been ignored because unknown ! A result may be not completely restored !");
+				getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "The result parameter '" + n + "' has been ignored because unknown! A result may be not completely restored!", null);
 		}
 
 		if (id == null){
-			getLogger().error("[restoration] Missing result ID ! => A result can not be restored !");
+			getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Missing result ID! => A result can not be restored!", null);
 			return null;
 		}else{
 			Result r = new Result(id, type, href, redirection);
@@ -1044,9 +1043,9 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 				else if (n.equalsIgnoreCase("message"))
 					message = obj.getString(n);
 				else
-					getLogger().warning("[restoration] The error attribute '" + n + "' has been ignored because unknown ! => An error summary may be not completely restored !");
+					getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "The error attribute '" + n + "' has been ignored because unknown! => An error summary may be not completely restored!", null);
 			}catch(JSONException je){
-				getLogger().error("Incorrect JSON format for an error serialization !", je);
+				getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for an error serialization!", je);
 			}
 		}
 		if (message != null)
@@ -1199,7 +1198,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 				readNext();
 				return nextKey;
 			}catch(JSONException je){
-				logger.error("Incorrect JSON format in an object !", je);
+				logger.logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format in an object!", je);
 				endReached = true;
 				return null;
 			}
@@ -1296,7 +1295,7 @@ public class DefaultUWSBackupManager implements UWSBackupManager {
 			try{
 				readNext();
 			}catch(JSONException je){
-				logger.error("Incorrect JSON format in an Array !", je);
+				logger.logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format in an Array!", je);
 				endReached = true;
 				nextObj = null;
 			}
diff --git a/src/uws/service/error/AbstractServiceErrorWriter.java b/src/uws/service/error/AbstractServiceErrorWriter.java
deleted file mode 100644
index a71b45c2013623a3fae48cb431857716fdc10d06..0000000000000000000000000000000000000000
--- a/src/uws/service/error/AbstractServiceErrorWriter.java
+++ /dev/null
@@ -1,291 +0,0 @@
-package uws.service.error;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.json.JSONException;
-import org.json.JSONWriter;
-
-import uws.AcceptHeader;
-import uws.job.ErrorType;
-import uws.job.serializer.UWSSerializer;
-import uws.job.user.JobOwner;
-import uws.service.log.UWSLog;
-
-/**
- * <p>Abstract implementation of the {@link ServiceErrorWriter} interface.</p>
- * 
- * <p>
- * 	The only abstract method is the function {@link #getLogger()}. It MUST return a NON-NULL logger.
- * 	The other functions ({@link #writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
- * 	and {@link #writeError(String, ErrorType, int, HttpServletResponse, HttpServletRequest, JobOwner, String)}) have
- * 	already a default implementation but may be overridden if needed. Both of them call the function
- * 	{@link #formatError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)}
- * 	to format and write the error in the given {@link HttpServletResponse} in the HTML format with
- * 	the appropriate HTTP error code. The  (full) stack trace of the error may be printed if asked.
- * </p>
- * 
- * <p>2 formats are managed by this implementation: HTML (default) and JSON. That means the writer will format and
- * write a given error in the best appropriate format. This format is chosen thanks to the "Accept" header of the HTTP request.
- * If no request is provided or if there is no known format, the HTML format is chosen by default.</p>
- * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
- */
-public abstract class AbstractServiceErrorWriter implements ServiceErrorWriter {
-
-	protected final String[] managedFormats = new String[]{"application/json","json","text/json","text/html","html"};
-
-	/**
-	 * Logger to use to display the given errors in the appropriate log files.
-	 * @return	A NON-NULL and VALID logger.
-	 */
-	protected abstract UWSLog getLogger();
-
-	@Override
-	public void writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, JobOwner user, String action) throws IOException{
-		if (t != null && response != null){
-			formatError(t, true, ErrorType.FATAL, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, action, user, response, (request != null) ? request.getHeader("Accept") : null);
-			getLogger().error(t);
-			String errorMsg = t.getMessage();
-			if (errorMsg == null || errorMsg.trim().isEmpty())
-				errorMsg = t.getClass().getName() + " (no error message)";
-			getLogger().httpRequest(request, user, action, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMsg, t);
-		}
-	}
-
-	@Override
-	public void writeError(String message, ErrorType type, int httpErrorCode, HttpServletResponse response, HttpServletRequest request, JobOwner user, String action) throws IOException{
-		if (message != null && response != null){
-			formatError(new Exception(message), false, type, httpErrorCode, action, user, response, (request != null) ? request.getHeader("Accept") : null);
-			getLogger().httpRequest(request, user, action, httpErrorCode, message, null);
-		}
-	}
-
-	/**
-	 * Parses the header "Accept", splits it in a list of MIME type and compare each one to each managed formats ({@link #managedFormats}).
-	 * If there is a match (not case sensitive), return the corresponding managed format immediately.
-	 * 
-	 * @param acceptHeader	The header item named "Accept" (which lists all expected response formats).
-	 * @return				The first format common to the "Accept" header and the managed formats of this writer.
-	 */
-	protected final String chooseFormat(final String acceptHeader){
-		if (acceptHeader != null && !acceptHeader.trim().isEmpty()){
-			// Parse the given MIME types list:
-			AcceptHeader accept = new AcceptHeader(acceptHeader);
-			ArrayList<String> lstMimeTypes = accept.getOrderedMimeTypes();
-			for(String acceptedFormat : lstMimeTypes){
-				for(String f : managedFormats){
-					if (acceptedFormat.equalsIgnoreCase(f))
-						return f;
-				}
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
-	 * <p>The format is chosen thanks to the Accept header of the HTTP request.
-	 * If unknown, the HTML output is chosen.</p>
-	 * 
-	 * @param t					Exception to format and to write.
-	 * @param printStackTrace	<i>true</i> to print the (full) stack trace, <i>false</i> otherwise.
-	 * @param type				Type of the error: FATAL or TRANSIENT.
-	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
-	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
-	 * @param user				User which is at the origin of the request/action which generates the error.
-	 * @param response			Response in which the error must be written.
-	 * @param acceptHeader		Value of the header named "Accept" (which lists all allowed response format).
-	 * 
-	 * @throws IOException		If there is an error while writing the given exception.
-	 * 
-	 * @see #formatHTMLError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)
-	 * @see #formatJSONError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)
-	 */
-	protected void formatError(final Throwable t, final boolean printStackTrace, final ErrorType type, final int httpErrorCode, final String action, final JobOwner user, final HttpServletResponse response, final String acceptHeader) throws IOException{
-		// Reset the whole response to ensure the output stream is free:
-		if (response.isCommitted())
-			return;
-		response.reset();
-
-		String format = chooseFormat(acceptHeader);
-		if (format != null && (format.equalsIgnoreCase("application/json") || format.equalsIgnoreCase("text/json") || format.equalsIgnoreCase("json")))
-			formatJSONError(t, printStackTrace, type, httpErrorCode, action, user, response);
-		else
-			formatHTMLError(t, printStackTrace, type, httpErrorCode, action, user, response);
-	}
-
-	/**
-	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
-	 * <p>A full HTML response is printed with: the HTTP error code, the error type, the name of the exception, the message and the full stack trace.</p>
-	 * 
-	 * @param t					Exception to format and to write.
-	 * @param printStackTrace	<i>true</i> to print the (full) stack trace, <i>false</i> otherwise.
-	 * @param type				Type of the error: FATAL or TRANSIENT.
-	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
-	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
-	 * @param user				User which is at the origin of the request/action which generates the error.
-	 * @param response			Response in which the error must be written.
-	 * 
-	 * @throws IOException		If there is an error while writing the given exception.
-	 */
-	protected void formatHTMLError(final Throwable t, final boolean printStackTrace, final ErrorType type, final int httpErrorCode, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
-		// Set the HTTP status code and the content type of the response:
-		response.setStatus(httpErrorCode);
-		response.setContentType(UWSSerializer.MIME_TYPE_HTML);
-
-		PrintWriter out = response.getWriter();
-
-		// Header:
-		out.println("<html>\n\t<head>");
-		out.println("\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");
-		out.println("\t\t<style type=\"text/css\">");
-		out.println("\t\t\tbody { background-color: white; color: black; }");
-		out.println("\t\t\th2 { font-weight: bold; font-variant: small-caps; text-decoration: underline; font-size: 1.5em; color: #4A4A4A; }");
-		out.println("\t\t\tul, ol { margin-left: 2em; margin-top: 0.2em; text-align: justify; }");
-		out.println("\t\t\tli { margin-bottom: 0.2em; margin-top: 0; }");
-		out.println("\t\t\tp, p.listheader { text-align: justify; text-indent: 2%; margin-top: 0; }");
-		out.println("\t\t\ttable { border-collapse: collapse; }");
-		out.println("\t\t\ttable, th, td { border: 1px solid #FC8813; }");
-		out.println("\t\t\tth { background-color: #F29842; color: white; font-size: 1.1em; }");
-		out.println("\t\t\ttr.alt { background-color: #FFDAB6; }");
-		out.println("\t\t</style>");
-		out.println("\t\t<title>SERVICE ERROR</title>");
-		out.println("\t</head>\n\t<body>");
-
-		// Title:
-		String errorColor = (type == ErrorType.FATAL) ? "red" : "orange";
-		out.println("\t\t<h1 style=\"text-align: center; background-color:" + errorColor + "; color: white; font-weight: bold;\">SERVICE ERROR - " + httpErrorCode + "</h1>");
-
-		// Description part:
-		out.println("\t\t<h2>Description</h2>");
-		out.println("\t\t<ul>");
-		out.println("\t\t\t<li><b>Type: </b>" + type + "</li>");
-		if (action != null && !action.trim().isEmpty())
-			out.println("\t\t\t<li><b>Action: </b>" + action + "</li>");
-		String context = null;
-		String msg = t.getMessage();
-		if (msg != null && !msg.trim().isEmpty()){
-			int start = msg.indexOf("["), end = msg.indexOf("]");
-			if (start >= 0 && start < end){
-				context = msg.substring(start + 1, end);
-				msg = msg.substring(end + 1);
-			}
-		}else
-			msg = "";
-		if (context != null)
-			out.println("\t\t\t<li><b>Context: </b>" + context + "</li>");
-		if (printStackTrace)
-			out.println("\t\t\t<li><b>Exception: </b>" + t.getClass().getName() + "</li>");
-		out.println("\t\t\t<li><b>Message:</b><p>" + msg + "</p></li>");
-		out.println("\t\t</ul>");
-
-		// Stack trace part:
-		if (printStackTrace){
-			out.println("\t\t<h2>Stack trace</h2>");
-			Throwable cause = t;
-			do{
-				out.println("\t\t<table style=\"width: ihnerit;\">");
-				out.println("\t\t\t<tr><th>Class</th><th>Method</th><th>Line</th></tr>");
-				StackTraceElement[] trace = cause.getStackTrace();
-				for(int i = 0; i < trace.length; i++)
-					out.println("\t\t\t<tr" + ((i % 2 != 0) ? " class=\"alt\"" : "") + "><td>" + trace[i].getClassName() + "</td><td>" + trace[i].getMethodName() + "</td><td>" + trace[i].getLineNumber() + "</td></tr>");
-				out.println("\t\t</table>");
-
-				// Print the stack trace of the "next" error:
-				cause = cause.getCause();
-				if (cause != null){
-					out.println("\t\t<p><b>Caused by " + cause.getClass().getName() + ":</b></p>");
-					out.println("\t\t<p>" + cause.getMessage() + "</p>");
-				}
-			}while(cause != null);
-		}
-
-		out.println("\t</body>\n</html>");
-		out.close();
-	}
-
-	/**
-	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
-	 * <p>A JSON response is printed with: the HTTP error code, the error type, the name of the exception, the message and the list of all causes' message.</p>
-	 * 
-	 * @param t					Exception to format and to write.
-	 * @param printStackTrace	<i>true</i> to print the (full) stack trace, <i>false</i> otherwise.
-	 * @param type				Type of the error: FATAL or TRANSIENT.
-	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
-	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
-	 * @param user				User which is at the origin of the request/action which generates the error.
-	 * @param response			Response in which the error must be written.
-	 * 
-	 * @throws IOException		If there is an error while writing the given exception.
-	 */
-	protected void formatJSONError(final Throwable t, final boolean printStackTrace, final ErrorType type, final int httpErrorCode, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
-		// Set the HTTP status code and the content type of the response:
-		response.setStatus(httpErrorCode);
-		response.setContentType(UWSSerializer.MIME_TYPE_JSON);
-
-		PrintWriter out = response.getWriter();
-		try{
-			JSONWriter json = new JSONWriter(out);
-
-			json.object();
-			json.key("errorcode").value(httpErrorCode);
-			json.key("errortype").value(type.toString());
-			json.key("action").value(action);
-
-			String context = null;
-			String msg = t.getMessage();
-			if (msg != null && !msg.trim().isEmpty()){
-				int start = msg.indexOf("["), end = msg.indexOf("]");
-				if (start >= 0 && start < end){
-					context = msg.substring(start + 1, end);
-					msg = msg.substring(end + 1);
-				}
-			}else
-				msg = "";
-			if (context != null)
-				json.key("context").value(context);
-			if (printStackTrace)
-				json.key("exception").value(t.getClass().getName());
-			json.key("message").value(msg);
-
-			// Stack trace part:
-			if (printStackTrace){
-				json.key("cause").array();
-				Throwable cause = t;
-				do{
-					json.object();
-					json.key("exception").value(cause.getClass().getName());
-					json.key("stacktrace").array();
-					StackTraceElement[] trace = cause.getStackTrace();
-					for(int i = 0; i < trace.length; i++){
-						json.object();
-						json.key("class").value(trace[i].getClassName());
-						json.key("method").value(trace[i].getMethodName());
-						json.key("line").value(trace[i].getLineNumber());
-						json.endObject();
-					}
-					json.endArray().endObject();
-
-					// Print the stack trace of the "next" error:
-					cause = cause.getCause();
-				}while(cause != null);
-				json.endArray();
-			}
-
-			json.endObject();
-		}catch(JSONException je){
-			getLogger().error("Impossible to format/write an error in JSON !", je);
-			throw new IOException("Error while formatting the error in JSON !", je);
-		}finally{
-			out.flush();
-			out.close();
-		}
-	}
-
-}
diff --git a/src/uws/service/error/DefaultUWSErrorWriter.java b/src/uws/service/error/DefaultUWSErrorWriter.java
index 82529f65d5cbf231655ad006924be7868e91e2c5..50386f45798ed84d6dd24e53a3fd2b5777f2f137 100644
--- a/src/uws/service/error/DefaultUWSErrorWriter.java
+++ b/src/uws/service/error/DefaultUWSErrorWriter.java
@@ -1,58 +1,275 @@
 package uws.service.error;
 
+/*
+ * This file is part of UWSLibrary.
+ * 
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
+ */
+
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.json.JSONException;
+import org.json.JSONWriter;
+
+import tap.TAPException;
+import uws.AcceptHeader;
 import uws.UWSException;
-import uws.UWSToolBox;
+import uws.job.ErrorType;
+import uws.job.serializer.UWSSerializer;
 import uws.job.user.JobOwner;
-import uws.service.UWS;
 import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
- * <p>Default implementation of {@link ServiceErrorWriter} for a UWS service.</p>
- * 
- * <p>All errors are written using the function {@link #formatError(Throwable, boolean, uws.job.ErrorType, int, String, JobOwner, HttpServletResponse)}
- * of the abstract implementation of the error writer: {@link AbstractServiceErrorWriter}.</p>
+ * <p>Default implementation of a {@link ServiceErrorWriter} interface for a UWS service.</p>
  * 
- * <p>A {@link UWSException} may precise the HTTP error code to apply. That's why, {@link #writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
- * has been overridden: to get this error code and submit it to the {@link #formatError(Throwable, boolean, uws.job.ErrorType, int, String, JobOwner, HttpServletResponse)}
- * function. Besides, the stack trace of {@link UWSException}s is not printed (except if the message is NULL or empty).
- * And this error will be logged only if its error code is {@link UWSException#INTERNAL_SERVER_ERROR}.</p>
+ * <p>
+ * 	All errors are written using the function {@link #formatError(String, ErrorType, int, String, String, JobOwner, HttpServletResponse, String)}
+ * 	in order to format the error in the most appropriate format. 2 formats are managed by default by this implementation: HTML (default) and JSON.
+ * 	This format is chosen thanks to the "Accept" header of the HTTP request. If no request is provided or if there is no known format,
+ * 	the HTML format is chosen by default.
+ * </p>
  * 
- * <p>2 formats are managed by this implementation: HTML (default) and JSON. That means the writer will format and
- * write a given error in the best appropriate format. This format is chosen thanks to the "Accept" header of the HTTP request.
- * If no request is provided or if there is no known format, the HTML format is chosen by default.</p>
+ * <p>
+ * 	{@link UWSException}s may precise the HTTP error code to apply,
+ * 	which will be used to set the HTTP status of the response. If it is a different kind of exception,
+ * 	the HTTP status 500 (INTERNAL SERVER ERROR) will be used.
+ * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * <p>
+ * 	Besides, all exceptions except {@link UWSException} and {@link TAPException} will be logged as FATAL in the TAP context
+ * 	(with no event and no object). Thus the full stack trace is available to the administrator so that the error can
+ * 	be understood as easily and quickly as possible.
+ * 	<i>The stack trace is no longer displayed to the user.</i>
+ * </p>
  * 
- * @see AbstractServiceErrorWriter
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
-public class DefaultUWSErrorWriter extends AbstractServiceErrorWriter {
+public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 
-	protected final UWS uws;
+	/** List of all managed output formats. */
+	protected final String[] managedFormats = new String[]{"application/json","json","text/json","text/html","html"};
 
-	public DefaultUWSErrorWriter(final UWS uws){
-		this.uws = uws;
-	}
+	/** Logger to use when grave error must be logged or if a JSON error occurs. */
+	protected final UWSLog logger;
 
-	@Override
-	protected final UWSLog getLogger(){
-		return (uws != null && uws.getLogger() != null) ? uws.getLogger() : UWSToolBox.getDefaultLogger();
+	/**
+	 * Build an error writer which will log any error in response of an HTTP request.
+	 * 
+	 * @param logger	Object to use to log errors.
+	 */
+	public DefaultUWSErrorWriter(final UWSLog logger){
+		if (logger == null)
+			throw new NullPointerException("Missing logger! Can not write a default error writer without.");
+
+		this.logger = logger;
 	}
 
 	@Override
-	public void writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, JobOwner user, String action) throws IOException{
+	public void writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action) throws IOException{
+		if (t == null || response == null)
+			return;
+
+		// If expected error, just write it:
 		if (t instanceof UWSException){
 			UWSException ue = (UWSException)t;
-			formatError(ue, (ue.getMessage() == null || ue.getMessage().trim().isEmpty()), ue.getUWSErrorType(), ue.getHttpErrorCode(), action, user, response, request.getHeader("Accept"));
-			if (ue.getHttpErrorCode() == UWSException.INTERNAL_SERVER_ERROR)
-				getLogger().error(ue);
-			getLogger().httpRequest(request, user, action, ue.getHttpErrorCode(), ue.getMessage(), ue);
-		}else
-			super.writeError(t, response, request, user, action);
+			writeError(ue.getMessage(), ue.getUWSErrorType(), ue.getHttpErrorCode(), response, request, reqID, user, action);
+		}
+		// Otherwise, log it and write a message to the user:
+		else{
+			// log the error as GRAVE/FATAL (because unexpected/unmanaged):
+			logger.logUWS(LogLevel.FATAL, null, null, "[REQUEST N°" + reqID + "] " + t.getMessage(), t);
+			// write a message to the user:
+			writeError("INTERNAL SERVER ERROR! Sorry, this error is unexpected and no explaination can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following 'Request ID').", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
+		}
+	}
+
+	@Override
+	public void writeError(String message, ErrorType type, int httpErrorCode, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action) throws IOException{
+		if (message != null || response != null)
+			return;
+
+		// Just format and write the error message:
+		formatError(message, type, httpErrorCode, reqID, action, user, response, (request != null) ? request.getHeader("Accept") : null);
+	}
+
+	/**
+	 * Parses the header "Accept", splits it in a list of MIME type and compare each one to each managed formats ({@link #managedFormats}).
+	 * If there is a match (not case sensitive), return the corresponding managed format immediately.
+	 * 
+	 * @param acceptHeader	The header item named "Accept" (which lists all expected response formats).
+	 * @return				The first format common to the "Accept" header and the managed formats of this writer.
+	 */
+	protected final String chooseFormat(final String acceptHeader){
+		if (acceptHeader != null && !acceptHeader.trim().isEmpty()){
+			// Parse the given MIME types list:
+			AcceptHeader accept = new AcceptHeader(acceptHeader);
+			ArrayList<String> lstMimeTypes = accept.getOrderedMimeTypes();
+			for(String acceptedFormat : lstMimeTypes){
+				for(String f : managedFormats){
+					if (acceptedFormat.equalsIgnoreCase(f))
+						return f;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
+	 * <p>The format is chosen thanks to the Accept header of the HTTP request.
+	 * If unknown, the HTML output is chosen.</p>
+	 * 
+	 * @param t					Exception to format and to write.
+	 * @param type				Type of the error: FATAL or TRANSIENT.
+	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
+	 * @param reqID				ID of the request at the origin of the specified error.
+	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
+	 * @param user				User which is at the origin of the request/action which generates the error.
+	 * @param response			Response in which the error must be written.
+	 * @param acceptHeader		Value of the header named "Accept" (which lists all allowed response format).
+	 * 
+	 * @throws IOException		If there is an error while writing the given exception.
+	 * 
+	 * @see #formatHTMLError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)
+	 * @see #formatJSONError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)
+	 */
+	protected void formatError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response, final String acceptHeader) throws IOException{
+		// Reset the whole response to ensure the output stream is free:
+		if (response.isCommitted())
+			return;
+		response.reset();
+
+		String format = chooseFormat(acceptHeader);
+		if (format != null && (format.equalsIgnoreCase("application/json") || format.equalsIgnoreCase("text/json") || format.equalsIgnoreCase("json")))
+			formatJSONError(message, type, httpErrorCode, reqID, action, user, response);
+		else
+			formatHTMLError(message, type, httpErrorCode, reqID, action, user, response);
+	}
+
+	/**
+	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
+	 * <p>A full HTML response is printed with: the HTTP error code, the error type, the name of the exception, the message and the full stack trace.</p>
+	 * 
+	 * @param t					Exception to format and to write.
+	 * @param type				Type of the error: FATAL or TRANSIENT.
+	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
+	 * @param reqID				ID of the request at the origin of the specified error.
+	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
+	 * @param user				User which is at the origin of the request/action which generates the error.
+	 * @param response			Response in which the error must be written.
+	 * 
+	 * @throws IOException		If there is an error while writing the given exception.
+	 */
+	protected void formatHTMLError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
+		// Erase anything written previously in the HTTP response:
+		response.reset();
+
+		// Set the HTTP status code and the content type of the response:
+		response.setStatus(httpErrorCode);
+		response.setContentType(UWSSerializer.MIME_TYPE_HTML);
+
+		PrintWriter out = response.getWriter();
+
+		// Header:
+		out.println("<html>\n\t<head>");
+		out.println("\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");
+		out.println("\t\t<style type=\"text/css\">");
+		out.println("\t\t\tbody { background-color: white; color: black; }");
+		out.println("\t\t\th2 { font-weight: bold; font-variant: small-caps; text-decoration: underline; font-size: 1.5em; color: #4A4A4A; }");
+		out.println("\t\t\tul, ol { margin-left: 2em; margin-top: 0.2em; text-align: justify; }");
+		out.println("\t\t\tli { margin-bottom: 0.2em; margin-top: 0; }");
+		out.println("\t\t\tp, p.listheader { text-align: justify; text-indent: 2%; margin-top: 0; }");
+		out.println("\t\t\ttable { border-collapse: collapse; }");
+		out.println("\t\t\ttable, th, td { border: 1px solid #FC8813; }");
+		out.println("\t\t\tth { background-color: #F29842; color: white; font-size: 1.1em; }");
+		out.println("\t\t\ttr.alt { background-color: #FFDAB6; }");
+		out.println("\t\t</style>");
+		out.println("\t\t<title>SERVICE ERROR</title>");
+		out.println("\t</head>\n\t<body>");
+
+		// Title:
+		String errorColor = (type == ErrorType.FATAL) ? "red" : "orange";
+		out.println("\t\t<h1 style=\"text-align: center; background-color:" + errorColor + "; color: white; font-weight: bold;\">SERVICE ERROR - " + httpErrorCode + "</h1>");
+
+		// Description part:
+		out.println("\t\t<h2>Description</h2>");
+		out.println("\t\t<ul>");
+		out.println("\t\t\t<li><b>Type: </b>" + type + "</li>");
+		if (reqID != null)
+			out.println("\t\t\t<li><b>Request ID: </b>" + reqID + "</li>");
+		if (action != null)
+			out.println("\t\t\t<li><b>Action: </b>" + action + "</li>");
+		out.println("\t\t\t<li><b>Message:</b><p>" + message + "</p></li>");
+		out.println("\t\t</ul>");
+
+		out.println("\t</body>\n</html>");
+		out.close();
+	}
+
+	/**
+	 * <p>Formats and writes the given error in the HTTP servlet response.</p>
+	 * <p>A JSON response is printed with: the HTTP error code, the error type, the name of the exception, the message and the list of all causes' message.</p>
+	 * 
+	 * @param t					Exception to format and to write.
+	 * @param type				Type of the error: FATAL or TRANSIENT.
+	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
+	 * @param reqID				ID of the request at the origin of the specified error.
+	 * @param action			Action which generates the error <i><u>note:</u> displayed only if not NULL and not empty.
+	 * @param user				User which is at the origin of the request/action which generates the error.
+	 * @param response			Response in which the error must be written.
+	 * 
+	 * @throws IOException		If there is an error while writing the given exception.
+	 */
+	protected void formatJSONError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
+		// Erase anything written previously in the HTTP response:
+		response.reset();
+
+		// Set the HTTP status code and the content type of the response:
+		response.setStatus(httpErrorCode);
+		response.setContentType(UWSSerializer.MIME_TYPE_JSON);
+
+		PrintWriter out = response.getWriter();
+		try{
+			JSONWriter json = new JSONWriter(out);
+
+			json.object();
+			json.key("errorcode").value(httpErrorCode);
+			json.key("errortype").value(type.toString());
+			if (reqID != null)
+				json.key("requestid").value(reqID);
+			if (action != null)
+				json.key("action").value(action);
+			json.key("message").value(message);
+
+			json.endObject();
+		}catch(JSONException je){
+			logger.logUWS(LogLevel.ERROR, null, "FORMAT_ERROR", "Impossible to format/write an error in JSON!", je);
+			throw new IOException("Error while formatting the error in JSON!", je);
+		}finally{
+			out.flush();
+			out.close();
+		}
 	}
 
 }
diff --git a/src/uws/service/error/ServiceErrorWriter.java b/src/uws/service/error/ServiceErrorWriter.java
index 9c19a50a692db3c310da52980bdc48d34e4cc49d..42b9a08f6b8283472b73aa7c4f104bc43153ebdc 100644
--- a/src/uws/service/error/ServiceErrorWriter.java
+++ b/src/uws/service/error/ServiceErrorWriter.java
@@ -1,5 +1,25 @@
 package uws.service.error;
 
+/*
+ * This file is part of UWSLibrary.
+ * 
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
+ */
+
 import java.io.IOException;
 
 import javax.servlet.http.HttpServletRequest;
@@ -11,37 +31,49 @@ import uws.job.user.JobOwner;
 /**
  * Let's writing/formatting any Exception/Throwable in a {@link HttpServletResponse}.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public interface ServiceErrorWriter {
 
 	/**
-	 * Writes the given exception in the given response.
+	 * <p>Writes the given exception in the given response.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	If this function is called without at least an exception and an HTTP response, nothing should be done.
+	 * 	No error may be thrown.
+	 * </i></p>
 	 * 
-	 * @param t						Exception to write/format.
-	 * @param response				Response in which the given exception must be written.
-	 * @param request				Request at the origin of the error (MAY BE NULL).
-	 * @param user					User which sends the given request (which generates the error) (MAY BE NULL).
-	 * @param action				Type/Name of the action which generates the error (MAY BE NULL).
+	 * @param t					Exception to write/format.
+	 * @param response			Response in which the given exception must be written.
+	 * @param request			Request at the origin of the error (MAY BE NULL).
+	 * @param reqID				ID of the request (which let the user and the administrator identify the failed request). (MAY BE NULL if the request is not provided)
+	 * @param user				User which sends the given request (which generates the error) (MAY BE NULL).
+	 * @param action			Type/Name of the action which generates the error (MAY BE NULL).
 	 * 
-	 * @throws IOException			If there is an error while writing the response.
+	 * @throws IOException		If there is an error while writing the response.
 	 */
-	public void writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final JobOwner user, final String action) throws IOException;
+	public void writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException;
 
 	/**
-	 * Writes the described error in the given response.
+	 * <p>Writes the described error in the given response.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	If this function is called without at least a message and an HTTP response, nothing should be done.
+	 * 	No error may be thrown.
+	 * </i></p>
 	 * 
 	 * @param message			Message to display.
 	 * @param type				Type of the error: FATAL or TRANSIENT.
 	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
 	 * @param response			Response in which the described error must be written.
 	 * @param request			Request which causes this error.
+	 * @param reqID				ID of the request (which let the user and the administrator identify the failed request).
 	 * @param user				User which sends the HTTP request.
 	 * @param action			Action corresponding to the given request.
 	 * 
 	 * @throws IOException		If there is an error while writing the response.
 	 */
-	public void writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final JobOwner user, final String action) throws IOException;
+	public void writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException;
 
 }
diff --git a/src/uws/service/file/EventFrequency.java b/src/uws/service/file/EventFrequency.java
new file mode 100644
index 0000000000000000000000000000000000000000..98815e7a665bcfaaf1fc8d7a805983b41bb38953
--- /dev/null
+++ b/src/uws/service/file/EventFrequency.java
@@ -0,0 +1,498 @@
+package uws.service.file;
+
+/*
+ * 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 2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                  Astronomisches Rechen Institut (ARI)
+ */
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Scanner;
+import java.util.regex.MatchResult;
+
+/**
+ * <p>Let interpret and computing a frequency.</p>
+ * 
+ * <h3>Frequency syntax</h3>
+ * 
+ * <p>The frequency is expressed as a string at initialization of this object. This string must respect the following syntax:</p>
+ * <ul>
+ * 	<li>'D' hh mm : daily schedule at hh:mm</li>
+ * 	<li>'W' dd hh mm : weekly schedule at the given day of the week (1:sunday, 2:monday, ..., 7:saturday) at hh:mm</li>
+ * 	<li>'M' dd hh mm : monthly schedule at the given day of the month at hh:mm</li>
+ * 	<li>'h' mm : hourly schedule at the given minute</li>
+ * 	<li>'m' : scheduled every minute (for completness :-))</li>
+ * </ul>
+ * <p><i>Where: hh = integer between 0 and 23, mm = integer between 0 and 59, dd (for 'W') = integer between 1 and 7 (1:sunday, 2:monday, ..., 7:saturday),
+ * dd (for 'M') = integer between 1 and 31.</i></p>
+ * 
+ * <p><i><b>Warning:</b>
+ * 	The frequency type is case sensitive! Then you should particularly pay attention at the case
+ * 	when using the frequency types 'M' (monthly) and 'm' (every minute).
+ * </p>
+ * 
+ * <p>
+ * 	Parsing errors are not thrown but "resolved" silently. The "solution" depends of the error.
+ * 	2 cases of errors are considered:
+ * </p>
+ * <ul>
+ * 	<li><b>Frequency type mismatch:</b> It happens when the first character is not one of the expected (D, W, M, h, m).
+ * 	                                    That means: bad case (i.e. 'd' rather than 'D'), another character.
+ * 	                                    In this case, the frequency will be: <b>daily at 00:00</b>.</li>
+ * 
+ * 	<li><b>Parameter(s) missing or incorrect:</b> With the "daily" frequency ('D'), at least 2 parameters must be provided ;
+ * 	                                             3 for "weekly" ('W') and "monthly" ('M') ; only 1 for "hourly" ('h') ; none for "every minute" ('m').
+ * 	                                             This number of parameters is a minimum: only the n first parameters will be considered while
+ * 	                                             the others will be ignored.
+ * 	                                             If this minimum number of parameters is not respected or if a parameter value is incorrect,
+ * 	                                             <b>all parameters will be set to their default value</b>
+ * 	                                             (which is 0 for all parameter except dd for which it is 1).</li>
+ * </ul>
+ * 
+ * <p>Examples:</p>
+ * <ul>
+ * 	<li><i>"" or NULL</i> = every day at 00:00</li>
+ * 	<li><i>"D 06 30" or "D 6 30"</i> = every day at 06:30</li>
+ * 	<li><i>"D 24 30"</i> = every day at 00:00, because hh must respect the rule: 0 &le; hh &le; 23</li>
+ * 	<li><i>"d 06 30" or "T 06 30"</i> = every day at 00:00, because the frequency type "d" (lower case of "D") or "T" do not exist</li>
+ * 	<li><i>"W 2 6 30"</i> = every week on Tuesday at 06:30</li>
+ * 	<li><i>"W 8 06 30"</i> = every week on Sunday at 00:00, because with 'W' dd must respect the rule: 1 &le; dd &le; 7</li>
+ * 	<li><i>"M 2 6 30"</i> = every month on the 2nd at 06:30</li>
+ * 	<li><i>"M 32 6 30"</i> = every month on the 1st at 00:00, because with 'M' dd must respect the rule: 1 &le; dd &le; 31</li>
+ * 	<li><i>"M 5 6 30 12"</i> = every month on the 5th at 06:30, because at least 3 parameters are expected and so considered: "12" and eventual other parameters are ignored</li>
+ * </ul>
+ * 
+ * <h3>Computing next event date</h3>
+ * 
+ * <p>
+ * 	When this class is initialized with a frequency, it is able to compute the date of the event following a given date.
+ * 	The functions {@link #nextEvent()} and {@link #nextEvent(Date)} will compute this next event date
+ * 	from, respectively, now (current date/time) and the given date (the date of the last event). Both are computing the date of the next
+ * 	event by "adding" the frequency to the given date. And finally, the computed date is stored and returned.
+ * </p>
+ * 
+ * <p>Then, you have 2 possibilities to trigger the desired event:</p>
+ * <ul>
+ * 	<li>By calling {@link #isTimeElapsed()}, you can test whether at the current moment the date of the next event has been reached or not.
+ * 	    In function of the value returned by this function you will be then able to process the desired action or not.</li>
+ * 	<li>By creating a Timer with the next date event. Thus, the desired action will be automatically triggered at the exact moment.</li>
+ * </p>
+ *  
+ * 
+ * @author Marc Wenger (CDS)
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.1 (09/2014)
+ * @since 4.1
+ */
+public final class EventFrequency {
+
+	/** String format of a hour or a minute number. */
+	private static final NumberFormat NN = new DecimalFormat("00");
+
+	/** Date-Time format to use in order to identify a frequent event. */
+	private static final DateFormat EVENT_ID_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmm");
+
+	/** Ordered list of all week days (there, the first week day is Sunday). */
+	private static final String[] WEEK_DAYS = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
+
+	/** Ordinal day number suffix (1<b>st</b>, 2<b>nd</b>, 3<b>rd</b> and <b>th</b> for the others). */
+	private static final String[] DAY_SUFFIX = {"st","nd","rd","th"};
+
+	/** Frequency type (D, W, M, h, m). Default value: 'D' */
+	private char dwm = 'D';
+
+	/** "day" (dd) parameter of the frequency. */
+	private int day = 0;
+	/** "hour" (hh) parameter of the frequency. */
+	private int hour = 0;
+	/** "minute" (mm) parameter of the frequency. */
+	private int min = 0;
+
+	/** ID of the next event. By default, it is built using the date of the last event with the format {@link #EVENT_ID_FORMAT}. */
+	private String eventID = "";
+
+	/** Date (in millisecond) of the next event. */
+	private long nextEvent = -1;
+
+	/**
+	 * <p>Create a new event frequency.</p>
+	 * 
+	 * <p>The frequency string must respect the following syntax:</p>
+	 * <ul>
+	 * 	<li>'D' hh mm : daily schedule at hh:mm</li>
+	 * 	<li>'W' dd hh mm : weekly schedule at the given day of the week (1:sunday, 2:monday, ..., 7:saturday) at hh:mm</li>
+	 * 	<li>'M' dd hh mm : monthly schedule at the given day of the month at hh:mm</li>
+	 * 	<li>'h' mm : hourly schedule at the given minute</li>
+	 * 	<li>'m' : scheduled every minute (for completness :-))</li>
+	 * </ul>
+	 * <p><i>Where: hh = integer between 0 and 23, mm = integer between 0 and 59, dd (for 'W') = integer between 1 and 7 (1:sunday, 2:monday, ..., 7:saturday),
+	 * dd (for 'M') = integer between 1 and 31.</i></p>
+	 * 
+	 * <p><i><b>Warning:</b>
+	 * 	The frequency type is case sensitive! Then you should particularly pay attention at the case
+	 * 	when using the frequency types 'M' (monthly) and 'm' (every minute).
+	 * </p>
+	 * 
+	 * <p>
+	 * 	Parsing errors are not thrown but "resolved" silently. The "solution" depends of the error.
+	 * 	2 cases of errors are considered:
+	 * </p>
+	 * <ul>
+	 * 	<li><b>Frequency type mismatch:</b> It happens when the first character is not one of the expected (D, W, M, h, m).
+	 * 	                                    That means: bad case (i.e. 'd' rather than 'D'), another character.
+	 * 	                                    In this case, the frequency will be: <b>daily at 00:00</b>.</li>
+	 * 
+	 * 	<li><b>Parameter(s) missing or incorrect:</b> With the "daily" frequency ('D'), at least 2 parameters must be provided ;
+	 * 	                                             3 for "weekly" ('W') and "monthly" ('M') ; only 1 for "hourly" ('h') ; none for "every minute" ('m').
+	 * 	                                             This number of parameters is a minimum: only the n first parameters will be considered while
+	 * 	                                             the others will be ignored.
+	 * 	                                             If this minimum number of parameters is not respected or if a parameter value is incorrect,
+	 * 	                                             <b>all parameters will be set to their default value</b>
+	 * 	                                             (which is 0 for all parameter except dd for which it is 1).</li>
+	 * </ul>
+	 * 
+	 * <p>Examples:</p>
+	 * <ul>
+	 * 	<li><i>"" or NULL</i> = every day at 00:00</li>
+	 * 	<li><i>"D 06 30" or "D 6 30"</i> = every day at 06:30</li>
+	 * 	<li><i>"D 24 30"</i> = every day at 00:00, because hh must respect the rule: 0 &le; hh &le; 23</li>
+	 * 	<li><i>"d 06 30" or "T 06 30"</i> = every day at 00:00, because the frequency type "d" (lower case of "D") or "T" do not exist</li>
+	 * 	<li><i>"W 2 6 30"</i> = every week on Tuesday at 06:30</li>
+	 * 	<li><i>"W 8 06 30"</i> = every week on Sunday at 00:00, because with 'W' dd must respect the rule: 1 &le; dd &le; 7</li>
+	 * 	<li><i>"M 2 6 30"</i> = every month on the 2nd at 06:30</li>
+	 * 	<li><i>"M 32 6 30"</i> = every month on the 1st at 00:00, because with 'M' dd must respect the rule: 1 &le; dd &le; 31</li>
+	 * 	<li><i>"M 5 6 30 12"</i> = every month on the 5th at 06:30, because at least 3 parameters are expected and so considered: "12" and eventual other parameters are ignored</li>
+	 * </ul>
+	 * 
+	 * @param interval	A string defining the event frequency (see above for the string format).
+	 */
+	public EventFrequency(String interval){
+		String str;
+
+		// Determine the separation between the frequency type character (D, W, M, h, m) and the parameters
+		// and normalize the given interval:
+		int p = -1;
+		if (interval == null)
+			interval = "";
+		else{
+			interval = interval.trim();
+			p = interval.indexOf(' ');
+		}
+
+		// Parse the given interval ONLY IF a frequency type is provided (even if there is no parameter):
+		if (p == 1 || interval.length() == 1){
+			MatchResult result;
+			Scanner scan = null;
+
+			// Extract and identify the frequency type:
+			dwm = interval.charAt(0);
+			str = interval.substring(p + 1);
+			scan = new Scanner(str);
+
+			// Extract the parameters in function of the frequency type:
+			switch(dwm){
+			// CASE: DAILY
+				case 'D':
+					scan.findInLine("(\\d{1,2}) (\\d{1,2})");
+					try{
+						result = scan.match();
+						hour = parseHour(result.group(1));
+						min = parseMinute(result.group(2));
+					}catch(IllegalStateException ise){
+						day = hour = min = 0;
+					}
+					break;
+
+				// CASE: WEEKLY AND MONTHLY
+				case 'W':
+				case 'M':
+					scan.findInLine("(\\d{1,2}) (\\d{1,2}) (\\d{1,2})");
+					try{
+						result = scan.match();
+						day = (dwm == 'W') ? parseDayOfWeek(result.group(1)) : parseDayOfMonth(result.group(1));
+						hour = parseHour(result.group(2));
+						min = parseMinute(result.group(3));
+					}catch(IllegalStateException ise){
+						day = (dwm == 'W') ? 0 : 1;
+						hour = min = 0;
+					}
+					break;
+
+				// CASE: HOURLY
+				case 'h':
+					scan.findInLine("(\\d{1,2})");
+					try{
+						result = scan.match();
+						min = parseMinute(result.group(1));
+					}catch(IllegalStateException ise){
+						min = 0;
+					}
+					break;
+
+				// CASE: EVERY MINUTE
+				case 'm':
+					// no other data needed
+					break;
+
+				// CASE: UNKNOWN FREQUENCY TYPE
+				default:
+					dwm = 'D';
+					day = hour = min = 0;
+			}
+			if (scan != null)
+				scan.close();
+		}
+	}
+
+	/**
+	 * Parse a string representing the day of the week (as a number).
+	 * 
+	 * @param dayNbStr	String containing an integer representing a week day.
+	 * 
+	 * @return	The identified week day. (integer between 0 and 6 (included))
+	 * 
+	 * @throws IllegalStateException	If the given string does not contain an integer or is not between 1 and 7 (included).
+	 */
+	private int parseDayOfWeek(final String dayNbStr) throws IllegalStateException{
+		try{
+			int d = Integer.parseInt(dayNbStr);
+			if (d >= 1 && d <= WEEK_DAYS.length)
+				return d - 1;
+		}catch(Exception e){}
+		throw new IllegalStateException("Incorrect day of week (" + dayNbStr + ") ; it should be between 1 and 7 (both included)!");
+	}
+
+	/**
+	 * Parse a string representing the day of the month.
+	 * 
+	 * @param dayStr	String containing an integer representing a month day.
+	 * 
+	 * @return	The identified month day. (integer between 1 and 31 (included))
+	 * 
+	 * @throws IllegalStateException	If the given string does not contain an integer or is not between 1 and 31 (included).
+	 */
+	private int parseDayOfMonth(final String dayStr) throws IllegalStateException{
+		try{
+			int d = Integer.parseInt(dayStr);
+			if (d >= 1 && d <= 31)
+				return d;
+		}catch(Exception e){}
+		throw new IllegalStateException("Incorrect day of month (" + dayStr + ") ; it should be between 1 and 31 (both included)!");
+	}
+
+	/**
+	 * Parse a string representing the hour part of a time (<b>hh</b>:mm).
+	 * 
+	 * @param hourStr	String containing an integer representing an hour.
+	 * 
+	 * @return	The identified hour. (integer between 0 and 23 (included))
+	 * 
+	 * @throws IllegalStateException	If the given string does not contain an integer or is not between 0 and 23 (included).
+	 */
+	private int parseHour(final String hourStr) throws IllegalStateException{
+		try{
+			int h = Integer.parseInt(hourStr);
+			if (h >= 0 && h <= 23)
+				return h;
+		}catch(Exception e){}
+		throw new IllegalStateException("Incorrect hour number(" + hourStr + ") ; it should be between 0 and 23 (both included)!");
+	}
+
+	/**
+	 * Parse a string representing the minute part of a time (hh:<b>mm</b>).
+	 * 
+	 * @param minStr	String containing an integer representing a minute.
+	 * 
+	 * @return	The identified minute. (integer between 0 and 59 (included))
+	 * 
+	 * @throws IllegalStateException	If the given string does not contain an integer or is not between 0 and 59 (included).
+	 */
+	private int parseMinute(final String minStr) throws IllegalStateException{
+		try{
+			int m = Integer.parseInt(minStr);
+			if (m >= 0 && m <= 59)
+				return m;
+		}catch(Exception e){}
+		throw new IllegalStateException("Incorrect minute number (" + minStr + ") ; it should be between 0 and 59 (both included)!");
+	}
+
+	/**
+	 * Tell whether the interval between the last event and now is greater or equals to the frequency represented by this object.
+	 * 
+	 * @return	<i>true</i> if the next event date has been reached, <i>false</i> otherwise.
+	 */
+	public boolean isTimeElapsed(){
+		return (nextEvent <= 0) || (System.currentTimeMillis() >= nextEvent);
+	}
+
+	/**
+	 * Get the date of the next event.
+	 * 
+	 * @return	Date of the next event, or NULL if no date has yet been computed.
+	 */
+	public Date getNextEvent(){
+		return (nextEvent <= 0) ? null : new Date(nextEvent);
+	}
+
+	/**
+	 * <p>Get a string which identity the period between the last event and the next one (whose the date has been computed by this object).</p>
+	 * 
+	 * <p>This ID is built by formatting in string the given date of the last event.</p>
+	 * 
+	 * @return	ID of the period before the next event.
+	 */
+	public String getEventID(){
+		return eventID;
+	}
+
+	/**
+	 * <p>Compute the date of the event, by adding the interval represented by this object to the current date/time.</p>
+	 * 	
+	 * <p>
+	 * 	The role of this function is to compute the next event date, not to get it. After computation, you can get this date
+	 * 	thanks to {@link #getNextEvent()}. Furthermore, using {@link #isTimeElapsed()} after having called this function will
+	 * 	let you test whether the next event should (have) occur(red).
+	 * </p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function computes the next event date by taking the current date as the date of the last event. However,
+	 * 	if the last event occurred at a different date, you should use {@link #nextEvent(Date)}.
+	 * </i></p>
+	 * 
+	 * @return	Date at which the next event should occur. (basically, it is: NOW + frequency)
+	 */
+	public Date nextEvent(){
+		return nextEvent(new Date());
+	}
+
+	/**
+	 * <p>Compute the date of the event, by adding the interval represented by this object to the given date/time.</p>
+	 * 	
+	 * <p>
+	 * 	The role of this function is to compute the next event date, not to get it. After computation, you can get this date
+	 * 	thanks to {@link #getNextEvent()}. Furthermore, using {@link #isTimeElapsed()} after having called this function will
+	 * 	let you test whether the next event should (have) occur(red).
+	 * </p>
+	 * 
+	 * @return	Date at which the next event should occur. (basically, it is lastEventDate + frequency)
+	 */
+	public Date nextEvent(final Date lastEventDate){
+		// Set the calendar to the given date:
+		GregorianCalendar date = new GregorianCalendar();
+		date.setTime(lastEventDate);
+
+		// Compute the date of the next event:
+		switch(dwm){
+		// CASE: DAILY
+			case 'D':
+				date.add(Calendar.DAY_OF_YEAR, 1);
+				date.set(Calendar.HOUR_OF_DAY, hour);
+				date.set(Calendar.MINUTE, min);
+				date.set(Calendar.SECOND, 0);
+				break;
+
+			// CASE: WEEKLY
+			case 'W':
+				// find the next right day to trigger the rotation
+				int weekday = date.get(Calendar.DAY_OF_WEEK);	// sunday=1, ... saturday=7
+				if (weekday == day){
+					date.add(Calendar.WEEK_OF_YEAR, 1);
+				}else{
+					// for the first scheduling which can happen any day
+					int delta = day - weekday;
+					if (delta <= 0)
+						delta += 7;
+					date.add(Calendar.DAY_OF_YEAR, delta);
+				}
+				date.set(Calendar.HOUR_OF_DAY, hour);
+				date.set(Calendar.MINUTE, min);
+				date.set(Calendar.SECOND, 0);
+				break;
+
+			// CASE: MONTHLY
+			case 'M':
+				date.add(Calendar.MONTH, 1);
+				date.set(Calendar.DAY_OF_MONTH, day);
+				date.set(Calendar.HOUR_OF_DAY, hour);
+				date.set(Calendar.MINUTE, min);
+				date.set(Calendar.SECOND, 0);
+				break;
+
+			// CASE: HOURLY
+			case 'h':
+				date.add(Calendar.HOUR_OF_DAY, 1);
+				date.set(Calendar.MINUTE, min);
+				date.set(Calendar.SECOND, 0);
+				break;
+
+			// CASE: EVERY MINUTE
+			case 'm':
+				date.add(Calendar.MINUTE, 1);
+				date.set(Calendar.SECOND, 0);
+				break;
+
+		/* OTHERWISE, the next event date is the given date! */
+		}
+
+		// Save it in millisecond for afterward comparison with the current time (so that telling whether the time is elapsed or not):
+		nextEvent = date.getTimeInMillis();
+
+		// Build the ID of this waiting period (the period between the last event and the next one):
+		eventID = EVENT_ID_FORMAT.format(new Date());
+
+		// Return the date of the next event:
+		return date.getTime();
+	}
+
+	/**
+	 * Display in a human readable way the frequency represented by this object.
+	 * 
+	 * @return a string, i.e. weekly on Sunday at HH:MM
+	 */
+	@Override
+	public String toString(){
+		StringBuilder str = new StringBuilder();
+		switch(dwm){
+			case 'D':
+				str.append("daily");
+				str.append(" at ").append(NN.format(hour)).append(':').append(NN.format(min));
+				break;
+			case 'W':
+				str.append("weekly on ").append(WEEK_DAYS[day % 7]);
+				str.append(" at ").append(NN.format(hour)).append(':').append(NN.format(min));
+				break;
+			case 'M':
+				str.append("monthly on the ").append(day).append(DAY_SUFFIX[Math.min(day - 1, 3)]);
+				str.append(" at ").append(NN.format(hour)).append(':').append(NN.format(min));
+				break;
+			case 'h':
+				str.append("hourly at ").append(NN.format(min));
+				break;
+			case 'm':
+				str.append("every minute");
+				break;
+		}
+
+		return str.toString();
+	}
+}
\ No newline at end of file
diff --git a/src/uws/service/file/LocalUWSFileManager.java b/src/uws/service/file/LocalUWSFileManager.java
index 41f89f79d6b53b168d19a5a9018a4204333ec9d7..d67f01384ebf549fa3f4ad80c30eca75f1fbe00f 100644
--- a/src/uws/service/file/LocalUWSFileManager.java
+++ b/src/uws/service/file/LocalUWSFileManager.java
@@ -16,10 +16,12 @@ package uws.service.file;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.File;
+import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -27,63 +29,71 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.NoSuchElementException;
 
-import java.io.FileFilter;
-
 import uws.UWSException;
 import uws.UWSToolBox;
-
 import uws.job.ErrorSummary;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
-import uws.service.log.UWSLogType;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>All UWS files are stored in the local machine into the specified directory.</p>
+ * 
  * <p>
  * 	The name of the log file, the result files and the backup files may be customized by overriding the following functions:
- * 	{@link #getLogFileName()}, {@link #getResultFileName(Result, UWSJob)}, {@link #getBackupFileName(JobOwner)} and {@link #getBackupFileName()}.
+ * 	{@link #getLogFileName(LogLevel, String)}, {@link #getResultFileName(Result, UWSJob)}, {@link #getBackupFileName(JobOwner)} and {@link #getBackupFileName()}.
  * </p>
+ * 
  * <p>
  * 	By default, results and backups are grouped by owner/user and owners/users are grouped thanks to {@link DefaultOwnerGroupIdentifier}.
  * 	By using the appropriate constructor, you can change these default behaviors.
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * <p>
+ * 	A log file rotation is set by default so that avoiding a too big log file after several months/years of use.
+ * 	By default the rotation is done every month on the 1st at 6am. This frequency can be changed easily thanks to the function
+ * 	{@link #setRotationFreq(String)}.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class LocalUWSFileManager implements UWSFileManager {
 
 	/** Format to use to format dates. */
 	private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
-	protected static final String DEFAULT_HTTP_LOG_FILE_NAME = "service_http_activity.log";
-	protected static final String DEFAULT_DEBUG_LOG_FILE_NAME = "service_debug.log";
-	protected static final String DEFAULT_LOG_FILE_NAME = "service_activity.log";
+	/** Default name of the log file. */
+	protected static final String DEFAULT_LOG_FILE_NAME = "uws.log";
+	/** Default name of the general UWS backup file. */
 	protected static final String DEFAULT_BACKUP_FILE_NAME = "uws.backup";
 
-	private static final String UNKNOWN_LOG_TYPE_GROUP = "???";
-
+	/** Directory in which all files managed by this class will be written and read. */
 	protected final File rootDirectory;
 
+	/** Output toward the service log file. */
+	protected PrintWriter logOutput = null;
+	/** Frequency at which the log file must be "rotated" (the file is renamed with the date of its first write and a new log file is created).
+	 * Thus, too big log files can be avoided. */
+	protected EventFrequency logRotation = new EventFrequency("M 1 06 00");	// Log file rotation every month on the 1st at 6am. 
+
+	/** Indicate whether a directory must be used to gather all jobs, results and errors related to one identified user.
+	 * If FALSE, all jobs, results and errors will be in only one directory, whoever owns them. */
 	protected final boolean oneDirectoryForEachUser;
+	/** Gather user directories, set by set. At the end, several user group directories may be created.
+	 * This option is considered only if {@link #oneDirectoryForEachUser} is TRUE. */
 	protected final boolean groupUserDirectories;
+	/** Object giving the policy about how to group user directories. */
 	protected final OwnerGroupIdentifier ownerGroupId;
 
-	protected Map<String,PrintWriter> logOutputs = new HashMap<String,PrintWriter>();
-
 	/**
 	 * <p>Builds a {@link UWSFileManager} which manages all UWS files in the given directory.</p>
 	 * <p>
@@ -225,64 +235,109 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/* ******************* */
 	/* LOG FILE MANAGEMENT */
 	/* ******************* */
+
+	/**
+	 * Get the frequency of the log file rotation
+	 * in a human readable way.
+	 * 
+	 * @return	A human readable frequency of the log file rotation.
+	 */
+	public final String getRotationFreq(){
+		return logRotation.toString();
+	}
+
 	/**
-	 * <p>Lets grouping log messages by log type.</p>
-	 * <p>For instance: by default all messages of type INFO, WARNING and ERROR are written in the same file.</p>
+	 * <p>Set the frequency at which a rotation of the log file must be done.</p>
 	 * 
-	 * @param logType	Type of the message to log.
+	 * <p>
+	 * 	"rotation" means here, to close the currently used log file, to rename it so that suffixing it
+	 * 	with the date at which the first log has been written in it, and to create a new log file.
+	 * </p>
 	 * 
-	 * @return			Name of the log type group.
+	 * <p>The frequency string must respect the following syntax:</p>
+	 * <ul>
+	 * 	<li>'D' hh mm : daily schedule at hh:mm</li>
+	 * 	<li>'W' dd hh mm : weekly schedule at the given day of the week (1:sunday, 2:monday, ..., 7:saturday) at hh:mm</li>
+	 * 	<li>'M' dd hh mm : monthly schedule at the given day of the month at hh:mm</li>
+	 * 	<li>'h' mm : hourly schedule at the given minute</li>
+	 * 	<li>'m' : scheduled every minute (for completness :-))</li>
+	 * </ul>
+	 * <p><i>Where: hh = integer between 0 and 23, mm = integer between 0 and 59, dd (for 'W') = integer between 1 and 7 (1:sunday, 2:monday, ..., 7:saturday),
+	 * dd (for 'M') = integer between 1 and 31.</i></p>
+	 * 
+	 * <p><i><b>Warning:</b>
+	 * 	The frequency type is case sensitive! Then you should particularly pay attention at the case
+	 * 	when using the frequency types 'M' (monthly) and 'm' (every minute).
+	 * </p>
+	 * 
+	 * <p>
+	 * 	Parsing errors are not thrown but "resolved" silently. The "solution" depends of the error.
+	 * 	2 cases of errors are considered:
+	 * </p>
+	 * <ul>
+	 * 	<li><b>Frequency type mismatch:</b> It happens when the first character is not one of the expected (D, W, M, h, m).
+	 * 	                                    That means: bad case (i.e. 'd' rather than 'D'), another character.
+	 * 	                                    In this case, the frequency will be: <b>daily at 00:00</b>.</li>
+	 * 
+	 * 	<li><b>Parameter(s) missing or incorrect:</b> With the "daily" frequency ('D'), at least 2 parameters must be provided ;
+	 * 	                                             3 for "weekly" ('W') and "monthly" ('M') ; only 1 for "hourly" ('h') ; none for "every minute" ('m').
+	 * 	                                             This number of parameters is a minimum: only the n first parameters will be considered while
+	 * 	                                             the others will be ignored.
+	 * 	                                             If this minimum number of parameters is not respected or if a parameter value is incorrect,
+	 * 	                                             <b>all parameters will be set to their default value</b>
+	 * 	                                             (which is 0 for all parameter except dd for which it is 1).</li>
+	 * </ul>
+	 * 
+	 * <p>Examples:</p>
+	 * <ul>
+	 * 	<li><i>"" or NULL</i> = every day at 00:00</li>
+	 * 	<li><i>"D 06 30" or "D 6 30"</i> = every day at 06:30</li>
+	 * 	<li><i>"D 24 30"</i> = every day at 00:00, because hh must respect the rule: 0 &le; hh &le; 23</li>
+	 * 	<li><i>"d 06 30" or "T 06 30"</i> = every day at 00:00, because the frequency type "d" (lower case of "D") or "T" do not exist</li>
+	 * 	<li><i>"W 2 6 30"</i> = every week on Tuesday at 06:30</li>
+	 * 	<li><i>"W 8 06 30"</i> = every week on Sunday at 00:00, because with 'W' dd must respect the rule: 1 &le; dd &le; 7</li>
+	 * 	<li><i>"M 2 6 30"</i> = every month on the 2nd at 06:30</li>
+	 * 	<li><i>"M 32 6 30"</i> = every month on the 1st at 00:00, because with 'M' dd must respect the rule: 1 &le; dd &le; 31</li>
+	 * 	<li><i>"M 5 6 30 12"</i> = every month on the 5th at 06:30, because at least 3 parameters are expected and so considered: "12" and eventual other parameters are ignored</li>
+	 * </ul>
+	 * 
+	 * @param interval	Interval between two log rotations.
 	 */
-	protected String getLogTypeGroup(final UWSLogType logType){
-		switch(logType){
-			case INFO:
-			case WARNING:
-			case ERROR:
-				return "DefaultLog";
-			case DEBUG:
-			case HTTP_ACTIVITY:
-				return logType.toString();
-			case CUSTOM:
-				return logType.getCustomType();
-			default:
-				return UNKNOWN_LOG_TYPE_GROUP;
-		}
+	public final void setRotationFreq(final String interval){
+		logRotation = new EventFrequency(interval);
 	}
 
 	/**
 	 * <p>Gets the name of the UWS log file.</p>
-	 * <p>By default: {@link #DEFAULT_LOG_FILE_NAME} or {@link #DEFAULT_HTTP_LOG_FILE_NAME} (to log an activity message, that's to say: thread status or http request).</p>
 	 * 
-	 * @param logType	Type of message to log.
+	 * <p>By default: {@link #DEFAULT_LOG_FILE_NAME}.</p>
+	 * 
+	 * @param level		Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
+	 * @param context	Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
 	 * 
 	 * @return	The name of the UWS log file.
 	 */
-	protected String getLogFileName(final String logTypeGroup){
-		if (logTypeGroup == UWSLogType.HTTP_ACTIVITY.toString())
-			return DEFAULT_HTTP_LOG_FILE_NAME;
-		else if (logTypeGroup.equals(UWSLogType.DEBUG.toString()))
-			return DEFAULT_DEBUG_LOG_FILE_NAME;
-		else
-			return DEFAULT_LOG_FILE_NAME;
+	protected String getLogFileName(final LogLevel level, final String context){
+		return DEFAULT_LOG_FILE_NAME;
 	}
 
 	/**
 	 * Gets the UWS log file.
 	 * 
-	 * @param logType	Type of message to log.
+	 * @param level		Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
+	 * @param context	Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
 	 * 
 	 * @return	The UWS log file.
 	 * 
-	 * @see #getLogFileName()
+	 * @see #getLogFileName(LogLevel, String)
 	 */
-	protected File getLogFile(final String logTypeGroup){
-		return new File(rootDirectory, getLogFileName(logTypeGroup));
+	protected File getLogFile(final LogLevel level, final String context){
+		return new File(rootDirectory, getLogFileName(level, context));
 	}
 
 	@Override
-	public InputStream getLogInput(final UWSLogType logType) throws IOException{
-		String logTypeGroup = getLogTypeGroup(logType);
-		File logFile = getLogFile(logTypeGroup);
+	public InputStream getLogInput(final LogLevel level, final String context) throws IOException{
+		File logFile = getLogFile(level, context);
 		if (logFile.exists())
 			return new FileInputStream(logFile);
 		else
@@ -290,24 +345,52 @@ public class LocalUWSFileManager implements UWSFileManager {
 	}
 
 	@Override
-	public PrintWriter getLogOutput(final UWSLogType logType) throws IOException{
-		String logTypeGroup = getLogTypeGroup(logType);
-		PrintWriter output = logOutputs.get(logTypeGroup);
-		if (output == null){
-			File logFile = getLogFile(logTypeGroup);
+	public synchronized PrintWriter getLogOutput(final LogLevel level, final String context) throws IOException{
+		// If a file rotation is needed...
+		if (logOutput != null && logRotation != null && logRotation.isTimeElapsed()){
+			// ...Close the output stream:
+			logOutput.close();
+			logOutput = null;
+
+			// ...Rename this log file:
+			// get the file:
+			File logFile = getLogFile(level, context);
+			// and its name:
+			String logFileName = logFile.getName();
+			// separate the file name from the extension:
+			String fileExt = "";
+			int indFileExt = logFileName.lastIndexOf('.');
+			if (indFileExt >= 0){
+				fileExt = logFileName.substring(indFileExt);
+				logFileName = logFileName.substring(0, indFileExt);
+			}
+			// build the new file name and rename the log file:
+			logFile.renameTo(new File(logFile.getParentFile(), logFileName + "_" + logRotation.getEventID() + fileExt));
+		}
+
+		// If the log output is not yet set or if a file rotation has been done...
+		if (logOutput == null){
+			// ...Create the output:
+			File logFile = getLogFile(level, context);
 			createParentDir(logFile);
-			output = new PrintWriter(new FileOutputStream(logFile, true), true);
-			printLogHeader(output);
-			logOutputs.put(logTypeGroup, output);
+			logOutput = new PrintWriter(new FileOutputStream(logFile, true), true);
+
+			// ...Write a log header:
+			printLogHeader(logOutput);
+
+			// ...Set the date of the next rotation:
+			if (logRotation != null)
+				logRotation.nextEvent();
 		}
-		return output;
+
+		return logOutput;
 	}
 
 	/**
 	 * Print a header into the log file so that separating older log messages to the new ones.
 	 */
 	protected void printLogHeader(final PrintWriter out){
-		String msgHeader = "########################################### LOG STARTS " + dateFormat.format(new Date()) + " ###########################################";
+		String msgHeader = "########################################### LOG STARTS " + dateFormat.format(new Date()) + " (file rotation: " + logRotation + ") ###########################################";
 		StringBuffer buf = new StringBuffer("");
 		for(int i = 0; i < msgHeader.length(); i++)
 			buf.append('#');
@@ -477,7 +560,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 */
 	protected String getBackupFileName(final JobOwner owner) throws IllegalArgumentException{
 		if (owner == null || owner.getID() == null || owner.getID().trim().isEmpty())
-			throw new IllegalArgumentException("Missing owner ! Can not get the backup file of an unknown owner. See LocalUWSFileManager.getBackupFile(JobOwner)");
+			throw new IllegalArgumentException("Missing owner! Can not get the backup file of an unknown owner.");
 		return owner.getID().replaceAll(File.separator, "_") + ".backup";
 	}
 
diff --git a/src/uws/service/file/UWSFileManager.java b/src/uws/service/file/UWSFileManager.java
index bc8520b60b1ffa9cf3d2165b6399c94478da680f..2de7431c268ae9d91110b0a2bf402672b6439588 100644
--- a/src/uws/service/file/UWSFileManager.java
+++ b/src/uws/service/file/UWSFileManager.java
@@ -16,23 +16,21 @@ package uws.service.file;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-
 import java.util.Iterator;
 
 import uws.job.ErrorSummary;
-import uws.job.UWSJob;
 import uws.job.Result;
-
+import uws.job.UWSJob;
 import uws.job.user.JobOwner;
-
-import uws.service.log.UWSLogType;
+import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Lets accessing any file managed by a UWS service.</p>
@@ -42,8 +40,8 @@ import uws.service.log.UWSLogType;
  * 	the results, log or backup file generated and read by a UWS.
  * </p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (08/2014)
  * 
  * @see LocalUWSFileManager
  */
@@ -55,20 +53,28 @@ public interface UWSFileManager {
 
 	/**
 	 * Gets an input stream on the log file of this UWS.
-	 * @param logType		Type of the message to log.
+	 * 
+	 * @param level			Level of the message to log (DEBUG, INFO, WARNING, ERROR or FATAL).
+	 * @param context		Context of the message to log (UWS, HTTP, JOB, THREAD, ...).
+	 * 
 	 * @return				An input on the log file or <i>null</i> if there is no log file.
+	 * 
 	 * @throws IOException	If there is an error while opening an input stream on the log file.
 	 */
-	public InputStream getLogInput(final UWSLogType logType) throws IOException;
+	public InputStream getLogInput(final LogLevel level, final String context) throws IOException;
 
 	/**
 	 * <p>Gets an output stream on the log file of this UWS.</p>
 	 * <p><i><u>note:</u> The log file must be automatically created if needed.</i></p>
-	 * @param logType		Type of the message to log.
+	 * 
+	 * @param level			Level of the message to log (DEBUG, INFO, WARNING, ERROR or FATAL).
+	 * @param context		Context of the message to log (UWS, HTTP, JOB, THREAD, ...).
+	 * 
 	 * @return				An output on the log file.
+	 * 
 	 * @throws IOException	If there is an error while creating the log file or while opening an output stream on it.
 	 */
-	public PrintWriter getLogOutput(final UWSLogType logType) throws IOException;
+	public PrintWriter getLogOutput(final LogLevel level, final String context) throws IOException;
 
 	/* *********************** */
 	/* RESULT FILES MANAGEMENT */
diff --git a/src/uws/service/log/DefaultUWSLog.java b/src/uws/service/log/DefaultUWSLog.java
index 5fd9586ef9d5052a1e8393df7c16e36ffe539bb6..100f1801ade4d7c82835318f9b5dcef17bf5937f 100644
--- a/src/uws/service/log/DefaultUWSLog.java
+++ b/src/uws/service/log/DefaultUWSLog.java
@@ -16,37 +16,31 @@ package uws.service.log;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-
 import java.util.Date;
 import java.util.Enumeration;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-import uws.UWSException;
-
-import uws.job.JobList;
 import uws.job.UWSJob;
-
 import uws.job.user.JobOwner;
-
 import uws.service.UWS;
-
 import uws.service.file.UWSFileManager;
 
 /**
  * <p>Default implementation of {@link UWSLog} interface which lets logging any message about a UWS.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public class DefaultUWSLog implements UWSLog {
 
@@ -57,12 +51,6 @@ public class DefaultUWSLog implements UWSLog {
 	protected final UWSFileManager fileManager;
 	protected final PrintWriter defaultOutput;
 
-	/**
-	 * <p>The minimum value of the HTTP status code required to print the stack trace of a HTTP error.</p>
-	 * <p><i><u>note:</u> This value is used only by the function {@link #httpRequest(HttpServletRequest, JobOwner, String, int, String, Throwable)}. </i></p>
-	 */
-	protected int minResponseCodeForStackTrace = 500;
-
 	/**
 	 * <p>Builds a {@link UWSLog} which will use the file manager
 	 * of the given UWS to get the log output (see {@link UWSFileManager#getLogOutput(UWSLogType)}).</p>
@@ -85,6 +73,7 @@ public class DefaultUWSLog implements UWSLog {
 	 * 
 	 * <p><i><u>note 1</u>: This constructor is particularly useful if the way of managing log output may change in the given file manager.
 	 * Indeed, the output may change in function of the type of message to log ({@link UWSLogType}).</i></p>
+	 * 
 	 * <p><i><u>note 2</u> If no output can be found in the file manager the standard error output ({@link System#err})
 	 * will be chosen automatically for all log messages.</i></p>
 	 * 
@@ -141,44 +130,24 @@ public class DefaultUWSLog implements UWSLog {
 			this.dateFormat = dateFormat;
 	}
 
-	/**
-	 * <p>Gets the minimum value of the HTTP status code required to print the stack trace of a HTTP error.</p>
-	 * 
-	 * <p><i><u>note:</u> This value is used only by the function {@link #httpRequest(HttpServletRequest, JobOwner, String, int, String, Throwable)}. </i></p>
-	 * 
-	 * @return	A HTTP response status code.
-	 */
-	public int getMinResponseCodeForStackTrace(){
-		return minResponseCodeForStackTrace;
-	}
-
-	/**
-	 * <p>Sets the minimum value of the HTTP status code required to print the stack trace of a HTTP error.</p>
-	 * 
-	 * <p><i><u>note:</u> This value is used only by the function {@link #httpRequest(HttpServletRequest, JobOwner, String, int, String, Throwable)}. </i></p>
-	 * 
-	 * @param httpCode	A HTTP response status code.
-	 */
-	public void setMinResponseCodeForStackTrace(final int httpCode){
-		minResponseCodeForStackTrace = httpCode;
-	}
-
 	/**
 	 * <p>Gets an output for the given type of message to print.</p>
 	 * 
 	 * <p>The {@link System#err} output is used if none can be found in the {@link UWS} or the {@link UWSFileManager}
 	 * given at the creation, or if the given output stream or writer is NULL.</p>
 	 * 
-	 * @param logType	Type of the message to print;
+	 * @param level		Level of the message to print (DEBUG, INFO, WARNING, ERROR or FATAL).
+	 * @param context	Context of the message to print (UWS, HTTP, JOB, THREAD).
+	 * 
 	 * @return			A writer.
 	 */
-	protected PrintWriter getOutput(final UWSLogType logType){
+	protected PrintWriter getOutput(final LogLevel level, final String context){
 		try{
 			if (uws != null){
 				if (uws.getFileManager() != null)
-					return uws.getFileManager().getLogOutput(logType);
+					return uws.getFileManager().getLogOutput(level, context);
 			}else if (fileManager != null)
-				return fileManager.getLogOutput(logType);
+				return fileManager.getLogOutput(level, context);
 			else if (defaultOutput != null)
 				return defaultOutput;
 		}catch(IOException ioe){
@@ -191,194 +160,136 @@ public class DefaultUWSLog implements UWSLog {
 	/* GENERAL LOGGING METHODS */
 	/* *********************** */
 
+	@Override
+	public void log(LogLevel level, final String context, final String message, final Throwable error){
+		log(level, context, null, null, message, error);
+	}
+
 	/**
-	 * Logs the given message (and exception, if any).
+	 * <p>Logs a full message and/or error.</p>
 	 * 
-	 * @param type	Type of the message to print. <i><u>note:</u> (If NULL, it will be ERROR if an exception is given, INFO otherwise.)</i>
-	 * @param msg	Message to print. (may be NULL)
-	 * @param t		Exception to print. (may be NULL)
+	 * <p><i>Note:
+	 * 	If no message and error is provided, nothing will be written.
+	 * </i></p>
+	 * 
+	 * @param level		Level of the error (DEBUG, INFO, WARNING, ERROR, FATAL).	<i>SHOULD NOT be NULL</i>
+	 * @param context	Context of the error (UWS, HTTP, THREAD, JOB). <i>MAY be NULL</i>
+	 * @param event		Context event during which this log is emitted. <i>MAY be NULL</i>
+	 * @param ID		ID of the job or HTTP request (it may also be an ID of anything else). <i>MAY BE NULL</i>
+	 * @param message	Message of the error. <i>MAY be NULL</i>
+	 * @param error		Error at the origin of the log error/warning/fatal. <i>MAY be NULL</i>
+	 * 
+	 * @since 4.1
 	 */
-	public void log(UWSLogType type, final String msg, final Throwable t){
+	protected final void log(LogLevel level, final String context, final String event, final String ID, final String message, final Throwable error){
+		// If no message and no error is provided, nothing to log, so nothing to write:
+		if ((message == null || message.length() <= 0) && error == null)
+			return;
+
 		// If the type is missing:
-		if (type == null)
-			type = (t != null) ? UWSLogType.ERROR : UWSLogType.INFO;
+		if (level == null)
+			level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
 
-		PrintWriter out = getOutput(type);
+		StringBuffer buf = new StringBuffer();
 		// Print the date/time:
-		out.print(dateFormat.format(new Date()));
-		out.print('\t');
-		out.print(String.format("%1$-13s", type.toString()));
-		out.print('\t');
+		buf.append(dateFormat.format(new Date())).append('\t');
+		// Print the level of error (debug, info, warning, error, fatal):
+		buf.append(level.toString()).append('\t');
+		// Print the context of the error (uws, thread, job, http):
+		buf.append((context == null) ? "" : context).append('\t');
+		// Print the context event:
+		buf.append((event == null) ? "" : event).append('\t');
+		// Print an ID (jobID, requestID):
+		buf.append((ID == null) ? "" : ID).append('\t');
 		// Print the message:
-		if (msg != null)
-			out.println(msg);
-		else if (t != null && t instanceof UWSException){
-			UWSException uwsEx = (UWSException)t;
-			out.println("EXCEPTION " + uwsEx.getClass().getName() + "\t" + uwsEx.getUWSErrorType() + "\tHTTP-" + uwsEx.getHttpErrorCode() + "\t" + uwsEx.getMessage());
-		}else
-			out.println();
+		if (message != null)
+			buf.append(message);
+		else if (error != null)
+			buf.append("[EXCEPTION ").append(error.getClass().getName()).append("] ").append(error.getMessage());
+
+		// Write the whole log line:
+		PrintWriter out = getOutput(level, context);
+		out.println(buf.toString());
+
 		// Print the stack trace, if any:
-		if (t != null)
-			t.printStackTrace(out);
+		if (error != null)
+			error.printStackTrace(out);
+
 		out.flush();
 	}
 
 	@Override
 	public void debug(String msg){
-		log(UWSLogType.DEBUG, msg, null);
+		log(LogLevel.DEBUG, null, msg, null);
 	}
 
 	@Override
 	public void debug(Throwable t){
-		log(UWSLogType.DEBUG, null, t);
+		log(LogLevel.DEBUG, null, null, t);
 	}
 
 	@Override
 	public void debug(String msg, Throwable t){
-		log(UWSLogType.DEBUG, msg, t);
+		log(LogLevel.DEBUG, null, msg, t);
 	}
 
 	@Override
 	public void info(String msg){
-		log(UWSLogType.INFO, msg, null);
+		log(LogLevel.INFO, null, msg, null);
 	}
 
 	@Override
 	public void warning(String msg){
-		log(UWSLogType.WARNING, msg, null);
+		log(LogLevel.WARNING, null, msg, null);
 	}
 
 	@Override
 	public void error(String msg){
-		log(UWSLogType.ERROR, msg, null);
+		log(LogLevel.ERROR, null, msg, null);
 	}
 
 	@Override
 	public void error(Throwable t){
-		log(UWSLogType.ERROR, null, t);
+		log(LogLevel.ERROR, null, null, t);
 	}
 
 	@Override
 	public void error(String msg, Throwable t){
-		log(UWSLogType.ERROR, msg, t);
+		log(LogLevel.ERROR, null, msg, t);
 	}
 
-	/* **************************** */
-	/* METHODS ABOUT THE UWS STATUS */
-	/* **************************** */
+	/* ************* */
+	/* HTTP ACTIVITY */
+	/* ************* */
 
 	/**
-	 * Gets the name of the UWS, if any.
+	 * <p>A message/error logged with this function will have the following format:</p>
+	 * <pre>&lt;TIMESTAMP&gt;	&lt;LEVEL&gt;	HTTP	REQUEST_RECEIVED	&lt;REQUEST_ID&gt;	&lt;MESSAGE&gt;	&lt;HTTP_METHOD&gt; at &lt;URL&gt; from &lt;IP_ADDR&gt; using &lt;USER_AGENT&gt; with parameters (&lt;PARAM1&gt;=&lt;VAL1&gt;&...)</pre>
 	 * 
-	 * @param uws	UWS whose the name must be returned.
-	 * 
-	 * @return		Name of the given UWS (followed by a space: " ") or an empty string ("").
+	 * @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String, java.lang.Throwable)
 	 */
-	protected final static String getUWSName(final UWS uws){
-		return ((uws != null && uws.getName() != null && !uws.getName().trim().isEmpty()) ? (uws.getName() + " ") : "");
-	}
-
-	@Override
-	public void uwsInitialized(UWS uws){
-		if (uws != null){
-			String msg = "UWS " + getUWSName(uws) + "INITIALIZED !";
-			info(msg);
-			log(UWSLogType.HTTP_ACTIVITY, msg, null);
-		}
-	}
-
 	@Override
-	public void ownerJobsSaved(JobOwner owner, int[] report){
-		if (owner != null){
-			String strReport = (report == null || report.length != 2) ? "???" : (report[0] + "/" + report[1]);
-			String ownerPseudo = (owner.getPseudo() != null && !owner.getPseudo().trim().isEmpty() && !owner.getID().equals(owner.getPseudo())) ? (" (alias " + owner.getPseudo() + ")") : "";
-			info(strReport + " saved jobs for the user " + owner.getID() + ownerPseudo + " !");
-		}
-	}
-
-	@Override
-	public void uwsRestored(UWS uws, int[] report){
-		if (uws != null){
-			String strReport = (report == null || report.length != 4) ? "[Unknown report format !]" : (report[0] + "/" + report[1] + " restored jobs and " + report[2] + "/" + report[3] + " restored users");
-			info("UWS " + getUWSName(uws) + "RESTORED => " + strReport);
-		}
-	}
-
-	@Override
-	public void uwsSaved(UWS uws, int[] report){
-		if (uws != null){
-			String strReport = (report == null || report.length != 4) ? "[Unknown report format !]" : (report[0] + "/" + report[1] + " saved jobs and " + report[2] + "/" + report[3] + " saved users");
-			info("UWS " + getUWSName(uws) + "SAVED => " + strReport);
-		}
-	}
-
-	@Override
-	public void jobCreated(UWSJob job){
-		if (job != null){
-			String jlName = (job.getJobList() != null) ? job.getJobList().getName() : null;
-			info("JOB " + job.getJobId() + " CREATED" + ((jlName != null) ? (" and added into " + jlName) : "") + " !");
-		}
-	}
-
-	@Override
-	public void jobDestroyed(UWSJob job, JobList jl){
-		if (job != null){
-			String jlName = (jl != null) ? jl.getName() : null;
-			info("JOB " + job.getJobId() + " DESTROYED" + ((jlName != null) ? (" and removed from " + jlName) : "") + " !");
-		}
-	}
-
-	@Override
-	public void jobStarted(UWSJob job){
-		if (job != null){
-			info("JOB " + job.getJobId() + " STARTED !");
-		}
-	}
-
-	@Override
-	public void jobFinished(UWSJob job){
-		if (job != null){
-			long endTime = (job.getEndTime() == null) ? -1 : job.getEndTime().getTime();
-			long startTime = (job.getStartTime() == null) ? -1 : job.getStartTime().getTime();
-			long duration = (endTime > 0 && startTime > 0) ? (endTime - startTime) : -1;
-			info("JOB " + job.getJobId() + " FINISHED with the phase " + job.getPhase() + ((duration > 0) ? " after an execution of " + duration + "ms" : "") + " !");
-		}
-	}
-
-	/* ************* */
-	/* HTTP ACTIVITY */
-	/* ************* */
-
-	@SuppressWarnings("unchecked")
-	public void httpRequest(final HttpServletRequest request, final JobOwner user, final String uwsAction, final int responseStatusCode, final String responseMsg, final Throwable responseError){
+	public void logHttp(final LogLevel level, final HttpServletRequest request, final String requestId, final String message, final Throwable error){
+		// IF A REQUEST IS PROVIDED, write its details after the message in a new column:
 		if (request != null){
 			StringBuffer str = new StringBuffer();
 
-			// Write the executed UWS action:
-			if (uwsAction == null || uwsAction.trim().isEmpty())
-				str.append("???");
-			else
-				str.append(uwsAction);
+			// Write the message (if any):
+			if (message != null)
+				str.append(message);
 			str.append('\t');
 
-			// Write the response status code:
-			if (responseStatusCode > 0)
-				str.append("HTTP-").append(responseStatusCode);
-			else
-				str.append("HTTP-???");
-			str.append('\t');
+			// Write the request type and the URL:
+			str.append(request.getMethod()).append(" at ").append(request.getRequestURL());
 
-			// Write the "response" message:
-			if (responseMsg != null)
-				str.append('[').append(responseMsg).append(']');
-			else
-				str.append("[]");
-			str.append('\t');
+			// Write the IP address:
+			str.append(" from ").append(request.getRemoteAddr());
 
-			// Write the request type and the URL:
-			str.append("[HTTP-").append(request.getMethod()).append("] ").append(request.getRequestURL()).append('\t');
+			// Write the user agent:
+			str.append(" using ").append(request.getHeader("User-Agent"));
 
 			// Write the posted parameters:
+			str.append(" with parameters (");
 			Enumeration<String> paramNames = request.getParameterNames();
 			while(paramNames.hasMoreElements()){
 				String param = paramNames.nextElement();
@@ -391,50 +302,106 @@ public class DefaultUWSLog implements UWSLog {
 				if (paramNames.hasMoreElements())
 					str.append('&');
 			}
+			str.append(')');
+
+			// Send the log message to the log file:
+			log(level, "HTTP", "REQUEST_RECEIVED", requestId, str.toString(), error);
+		}
+		// OTHERWISE, just write the given message:
+		else
+			log(level, "HTTP", "REQUEST_RECEIVED", requestId, message, error);
+	}
+
+	/**
+	 * <p>A message/error logged with this function will have the following format:</p>
+	 * <pre>&lt;TIMESTAMP&gt;	&lt;LEVEL&gt;	HTTP	RESPONSE_SENT	&lt;REQUEST_ID&gt;	&lt;MESSAGE&gt;	HTTP-&lt;STATUS_CODE&gt; to the user &lt;USER&gt; as &lt;CONTENT_TYPE&gt;</pre>
+	 * <p>,where &lt;USER&gt; may be either "(id:&lt;USER_ID&gt;;pseudo:&lt;USER_PSEUDO&gt;)" or "ANONYMOUS".</p>
+	 * 
+	 * @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletResponse, java.lang.String, uws.job.user.JobOwner, java.lang.String, java.lang.Throwable)
+	 */
+	@Override
+	public void logHttp(LogLevel level, HttpServletResponse response, String requestId, JobOwner user, String message, Throwable error){
+		if (response != null){
+			StringBuffer str = new StringBuffer();
+
+			// Write the message (if any):
+			if (message != null)
+				str.append(message);
 			str.append('\t');
 
-			// Write the IP address and the corresponding user:
-			str.append(request.getRemoteAddr()).append('[');
+			// Write the response status code:
+			str.append("HTTP-").append(response.getStatus());
+
+			// Write the user to whom the response is sent:
+			str.append(" to the user ");
 			if (user != null){
-				str.append("id:").append(user.getID());
+				str.append("(id:").append(user.getID());
 				if (user.getPseudo() != null)
 					str.append(";pseudo:").append(user.getPseudo());
+				str.append(')');
 			}else
-				str.append("???");
-			str.append("]\t");
+				str.append("ANONYMOUS");
 
-			// Write the user agent:
-			str.append(request.getHeader("User-Agent"));
+			// Write the response's MIME type:
+			if (response.getContentType() != null)
+				str.append(" as ").append(response.getContentType());
 
 			// Send the log message to the log file:
-			log(UWSLogType.HTTP_ACTIVITY, str.toString(), (responseStatusCode >= minResponseCodeForStackTrace) ? responseError : null);
+			log(level, "HTTP", "RESPONSE_SENT", requestId, str.toString(), error);
 		}
+		// OTHERWISE, just write the given message:
+		else
+			log(level, "HTTP", "RESPONSE_SENT", requestId, message, error);
 	}
 
-	/* ********************** */
-	/* THREAD STATUS MESSAGES */
-	/* ********************** */
+	/* ************ */
+	/* UWS ACTIVITY */
+	/* ************ */
 
 	@Override
-	public void threadStarted(Thread t, String task){
-		if (t != null)
-			info("THREAD " + t.getId() + " STARTED\t" + t.getName() + "\t" + t.getState() + "\t" + t.getThreadGroup().activeCount() + " active threads");
+	public void logUWS(LogLevel level, Object obj, String event, String message, Throwable error){
+		log(level, "UWS", event, null, message, error);
 	}
 
+	/* ************ */
+	/* JOB ACTIVITY */
+	/* ************ */
+
 	@Override
-	public void threadFinished(Thread t, String task){
-		if (t != null)
-			info("THREAD " + t.getId() + " ENDED\t" + t.getName() + "\t" + t.getState() + "\t" + t.getThreadGroup().activeCount() + " active threads");
+	public void logJob(LogLevel level, UWSJob job, String event, String message, Throwable error){
+		log(level, "JOB", event, (job == null) ? null : job.getJobId(), message, error);
 	}
 
+	/* ********************** */
+	/* THREAD STATUS MESSAGES */
+	/* ********************** */
+
 	@Override
-	public void threadInterrupted(Thread t, String task, Throwable error){
-		if (t != null){
-			if (error == null || error instanceof InterruptedException)
-				info("THREAD " + t.getId() + " CANCELLED\t" + t.getName() + "\t" + t.getState() + "\t" + t.getThreadGroup().activeCount() + " active threads");
-			else
-				error("THREAD " + t.getId() + " INTERRUPTED\t" + t.getName() + "\t" + t.getState() + "\t" + t.getThreadGroup().activeCount() + " active threads", error);
-		}
+	public void logThread(LogLevel level, Thread thread, String event, String message, Throwable error){
+		if (thread != null){
+			StringBuffer str = new StringBuffer();
+
+			// Write the message (if any):
+			if (message != null)
+				str.append(message);
+			str.append('\t');
+
+			// Write the thread name and ID:
+			str.append(thread.getName()).append(" (thread ID: ").append(thread.getId()).append(")");
+
+			// Write the thread state:
+			str.append(" is ").append(thread.getState());
+
+			// Write its thread group name:
+			str.append(" in the group " + thread.getThreadGroup().getName());
+
+			// Write the number of active threads:
+			str.append(" where ").append(thread.getThreadGroup().activeCount()).append(" threads are active");
+
+			log(level, "THREAD", event, thread.getName(), str.toString(), error);
+
+		}else
+			log(level, "THREAD", event, null, message, error);
 	}
 
 }
diff --git a/src/uws/service/log/UWSLog.java b/src/uws/service/log/UWSLog.java
index 350fe0244bca769ca5e4881ad4074784dcaae624..11cfa0aa412bc7671dfea24c8ff037491b88a12a 100644
--- a/src/uws/service/log/UWSLog.java
+++ b/src/uws/service/log/UWSLog.java
@@ -1,5 +1,19 @@
 package uws.service.log;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import uws.job.ErrorSummary;
+import uws.job.JobList;
+import uws.job.Result;
+import uws.job.UWSJob;
+import uws.job.user.JobOwner;
+import uws.service.UWS;
+import uws.service.UWSUrl;
+
 /*
  * This file is part of UWSLibrary.
  * 
@@ -16,174 +30,274 @@ package uws.service.log;
  * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
-import javax.servlet.http.HttpServletRequest;
-
-import uws.job.JobList;
-import uws.job.UWSJob;
-
-import uws.job.user.JobOwner;
-
-import uws.service.UWS;
-
 /**
- * Lets logging any kind of message about a UWS.
+ * Let log any kind of message about a UWS service.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 05/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.1 (09/2014)
  */
 public interface UWSLog {
 
+	/**
+	 * Indicate the level of the error: debug, info, warning or error.
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 4.1 (09/2014)
+	 * @since 4.1
+	 */
+	public static enum LogLevel{
+		DEBUG, INFO, WARNING, ERROR, FATAL;
+	}
+
 	/* *********************** */
 	/* GENERAL LOGGING METHODS */
 	/* *********************** */
 
 	/**
-	 * Logs a debug message.
+	 * <p>Generic way to log a message and/or an exception.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	The other functions of this class or extension, MAY be equivalent to a call to this function with some specific parameter values.
+	 * 	It should be especially the case for the debug(...), info(...), warning(...) and error(...) functions.
+	 * </i></p>
+	 * 
+	 * @param level		Level of the error (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param context	Context of the log item (HTTP, Thread, Job, UWS, ...). <i>MAY be NULL</i>
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 4.1
+	 */
+	public void log(final LogLevel level, final String context, final String message, final Throwable error);
+
+	/**
+	 * <p>Logs a debug message.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.WARNING, null, msg, null).
+	 * </i></p>
+	 * 
 	 * @param msg	A DEBUG message.
 	 */
 	public void debug(final String msg);
 
 	/**
-	 * Logs an exception as a debug message.
+	 * <p>Logs an exception as a debug message.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.WARNING, null, null, t).
+	 * </i></p>
+	 * 
 	 * @param t	An exception.
 	 */
 	public void debug(final Throwable t);
 
 	/**
-	 * Logs a full (message+exception) debug message.
+	 * <p>Logs a full (message+exception) debug message.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.WARNING, null, msg, t).
+	 * </i></p>
+	 * 
 	 * @param msg	A DEBUG message.
 	 * @param t		An exception.
 	 */
 	public void debug(final String msg, final Throwable t);
 
 	/**
-	 * Logs the given information.
+	 * <p>Logs the given information.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.INFO, null, msg, null).
+	 * </i></p>
+	 * 
 	 * @param msg	An INFO message.
 	 */
 	public void info(final String msg);
 
 	/**
-	 * Logs the given warning.
+	 * <p>Logs the given warning.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.WARNING, null, msg, null).
+	 * </i></p>
+	 * 
 	 * @param msg	A WARNING message.
 	 */
 	public void warning(final String msg);
 
 	/**
-	 * Logs the given error.
+	 * <p>Logs the given error.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.ERROR, null, msg, null).
+	 * </i></p>
+	 * 
 	 * @param msg	An ERROR message.
 	 */
 	public void error(final String msg);
 
 	/**
-	 * Logs the given exception as an error.
+	 * <p>Logs the given exception as an error.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.ERROR, null, null, t).
+	 * </i></p>
+	 * 
 	 * @param t	An exception.
 	 */
 	public void error(final Throwable t);
 
 	/**
-	 * Logs a full (message+exception) error message.
+	 * <p>Logs a full (message+exception) error message.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This function should be equals to: log(LogLevel.ERROR, null, msg, t).
+	 * </i></p>
+	 * 
 	 * @param msg	An ERROR message.
 	 * @param t		An exception.
 	 */
 	public void error(final String msg, final Throwable t);
 
-	/* *************************************** */
-	/* LOGGING METHODS TO WATCH THE UWS STATUS */
-	/* *************************************** */
-
-	/**
-	 * <p>Logs the fact that the given UWS has just been initialized.</p>
-	 * <p><i><u>note:</u> Theoretically, no restoration has been done when this method is called.</i></p>
-	 * @param uws	The UWS which has just been initialized.
-	 */
-	public void uwsInitialized(final UWS uws);
-
-	/**
-	 * Logs the fact that the given UWS has just been restored.
-	 * @param uws		The restored UWS.
-	 * @param report	Report of the restoration (in the order: nb restored jobs, nb jobs, nb restored users, nb users).
-	 */
-	public void uwsRestored(final UWS uws, final int[] report);
-
-	/**
-	 * Logs the fact that the given UWS has just been saved.
-	 * @param uws		The saved UWS.
-	 * @param report	Report of the save (in the order: nb saved jobs, nb jobs, nb saved users, nb users).
-	 */
-	public void uwsSaved(final UWS uws, final int[] report);
-
-	/**
-	 * Logs the fact that all the jobs of the given user have just been saved.
-	 * @param owner		The owner whose all the jobs have just been saved.
-	 * @param report	Report of the save (in the order: nb saved jobs, nb jobs).
-	 */
-	public void ownerJobsSaved(final JobOwner owner, final int[] report);
-
-	/**
-	 * Logs the fact that the given job has just been created.
-	 * @param job	The created job.
-	 */
-	public void jobCreated(final UWSJob job);
+	/* ****************** */
+	/* SPECIFIC FUNCTIONS */
+	/* ****************** */
 
 	/**
-	 * Logs the fact that the given job has just started.
-	 * @param job	The started job.
+	 * <p>Log a message and/or an error in the general context of UWS.</p>
+	 * 
+	 * <p>
+	 * 	One of the parameter is of type {@link Object}. This object can be used to provide more information to the log function
+	 * 	in order to describe as much as possible the state and/or result event.
+	 * </p>
+	 * 
+	 * <p>List of all events sent by the library (case sensitive):</p>
+	 * <ul>
+	 * 	<li>INIT (with "obj" as an instance of {@link UWS} except in case of error where "obj" is NULL)</li>
+	 * 	<li>ADD_JOB_LIST (with "obj" as an instance of {@link JobList})</li>
+	 * 	<li>DESTROY_JOB_LIST (with "obj" as an instance of {@link JobList})</li>
+	 * 	<li>DESTROY_JOB (with "obj" as an instance of {@link UWSUrl})</li>
+	 * 	<li>SERIALIZE (with "obj" as an instance of {@link UWSUrl})</li>
+	 * 	<li>SET_PARAM (with "obj" as an instance of {@link HttpServletRequest} in case of error)</li>
+	 * 	<li>GET_RESULT (with "obj" as an instance of {@link Result})</li>
+	 * 	<li>GET_ERROR (with "obj" as an instance of {@link ErrorSummary})</li>
+	 * 	<li>RESTORATION (with "obj" the raw object to de-serialize (may be {@link JSONObject} or {@link JSONArray} or NULL))</li>
+	 * 	<li>BACKUP (with "obj" the object to backup ; may be {@link JobOwner}, a {@link UWSJob}, ...)</li>
+	 * 	<li>RESTORED (with "obj" as an integer array of 4 items: nb of restored jobs, total nb of jobs, nb of restored users, total nb of users)</li>
+	 * 	<li>BACKUPED (with "obj" as an integer array of 4 items: nb of saved jobs, total nb of jobs, nb of saved users, total nb of users or with just 2 items (the two last ones))</li>
+	 * 	<li>FORMAT_ERROR (with a NULL "obj")</li>
+	 * </ul>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param obj		Object providing more information about the event/object at the origin of this log. <i>MAY be NULL</i>
+	 * @param event		Event at the origin of this log or action currently executed by UWS while this log is sent. <i>MAY be NULL</i>
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 4.1
 	 */
-	public void jobStarted(final UWSJob job);
+	public void logUWS(final LogLevel level, final Object obj, final String event, final String message, final Throwable error);
 
 	/**
-	 * Logs the fact that the given job has just finished.
-	 * @param job	The finished job.
+	 * <p>Log a message and/or an error in the HTTP context.
+	 * This log function is called when a request is received by the service. Consequently, the event is: REQUEST_RECEIVED.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	When a request is received, this function is called, and then, when the response has been written and sent to the client,
+	 * 	{@link #logHttp(LogLevel, HttpServletResponse, String, Throwable)} should be called.
+	 * 	These functions should always work together.
+	 * </i></p>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param request	HTTP request received by the service. <i>SHOULD NOT be NULL</i>
+	 * @param requestId	ID to use to identify this request until its response is sent.
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @see #logHttp(LogLevel, HttpServletResponse, String, Throwable)
+	 * 
+	 * @since 4.1
 	 */
-	public void jobFinished(final UWSJob job);
+	public void logHttp(final LogLevel level, final HttpServletRequest request, final String requestId, final String message, final Throwable error);
 
 	/**
-	 * Logs the fact that the given job has just been destroyed.
-	 * @param job	The destroyed job.
-	 * @param jl	The job list from which the given job has just been removed.
+	 * <p>Log a message and/or an error in the HTTP context.
+	 * This log function is called when a response is sent to the client by the service. Consequently, the event is: RESPONSE_SENT.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	When a request is received, {@link #logHttp(LogLevel, HttpServletRequest, String, Throwable)} is called, and then,
+	 * 	when the response has been written and sent to the client, this function should be called.
+	 * 	These functions should always work together.
+	 * </i></p>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param response	HTTP response sent by the service to the client. <i>MAY be NULL if an error occurs while writing the response</i>
+	 * @param requestId	ID to use to identify the request to which the given response is answering.
+	 * @param user		Identified user which has sent the received request.
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @see #logHttp(LogLevel, HttpServletRequest, String, Throwable)
+	 * 
+	 * @since 4.1
 	 */
-	public void jobDestroyed(final UWSJob job, final JobList jl);
-
-	/* ************* */
-	/* HTTP ACTIVITY */
-	/* ************* */
+	public void logHttp(final LogLevel level, final HttpServletResponse response, final String requestId, final JobOwner user, final String message, final Throwable error);
 
 	/**
-	 * Logs any HTTP request received by the UWS and also the send response.
-	 * @param request				The HTTP request received by the UWS.
-	 * @param user					The identified user which sends this request. (MAY BE NULL)
-	 * @param uwsAction				The identified UWS action. (MAY BE NULL)
-	 * @param responseStatusCode	The HTTP status code of the response given by the UWS.
-	 * @param responseMsg			The message (or a summary of the message) returned by the UWS. (MAY BE NULL)
-	 * @param responseError			The error sent by the UWS. (MAY BE NULL)
+	 * <p>Log a message and/or an error in the JOB context.</p>
+	 * 
+	 * <p>List of all events sent by the library (case sensitive):</p>
+	 * <ul>
+	 * 	<li>CREATED</li>
+	 * 	<li>QUEUE</li>
+	 * 	<li>START</li>
+	 * 	<li>ABORT</li>
+	 * 	<li>ERROR</li>
+	 * 	<li>EXECUTING</li>
+	 * 	<li>CHANGE_PHASE</li>
+	 * 	<li>NOTIFY</li>
+	 * 	<li>END</li>
+	 * 	<li>SERIALIZE</li>
+	 * 	<li>ADD_RESULT</li>
+	 * 	<li>SET_DESTRUCTION</li>
+	 * 	<li>SET_ERROR</li>
+	 * 	<li>CLEAR_RESOURCES</li>
+	 * 	<li>DESTROY</li>
+	 * </ul>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param job		Job from which this log comes. <i>MAY be NULL</i>
+	 * @param event		Event at the origin of this log or action executed by the given job while this log is sent. <i>MAY be NULL</i>
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 4.1
 	 */
-	public void httpRequest(final HttpServletRequest request, final JobOwner user, final String uwsAction, final int responseStatusCode, final String responseMsg, final Throwable responseError);
+	public void logJob(final LogLevel level, final UWSJob job, final String event, final String message, final Throwable error);
 
-	/* ********************** */
-	/* THREAD STATUS MESSAGES */
-	/* ********************** */
 	/**
-	 * Logs the fact that the given thread has just started.
-	 * @param t		The started thread.
-	 * @param task	Name/Description of the task that the given thread is executing.
+	 * <p>Log a message and/or an error in the THREAD context.</p>
+	 * 
+	 * <p>List of all events sent by the library (case sensitive):</p>
+	 * <ul>
+	 * 	<li>START</li>
+	 * 	<li>SET_ERROR</li>
+	 * 	<li>END</li>
+	 * </ul>
+	 * 
+	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
+	 * @param thread	Thread from which this log comes. <i>MAY be NULL</i>
+	 * @param event		Event at the origin of this log or action currently executed by the given thread while this log is sent. <i>MAY be NULL</i>
+	 * @param message	Message to log. <i>MAY be NULL</i>
+	 * @param error		Error/Exception to log. <i>MAY be NULL</i>
+	 * 
+	 * @since 4.1
 	 */
-	public void threadStarted(final Thread t, final String task);
+	public void logThread(final LogLevel level, final Thread thread, final String event, final String message, final Throwable error);
 
-	/**
-	 * Logs the fact that the given thread has just been interrupted.
-	 * @param t		The interrupted thread.
-	 * @param task	Name/Description of the task that the given thread was trying to execute.
-	 * @param error	Exception that has interrupted the given thread.
-	 */
-	public void threadInterrupted(final Thread t, final String task, final Throwable error);
-
-	/**
-	 * Logs the fact that the given thread has just finished.
-	 * @param t		The finished thread.
-	 * @param task	Name/Description of the task that the given thread was executing.
-	 */
-	public void threadFinished(final Thread t, final String task);
 }
diff --git a/src/uws/service/log/UWSLogType.java b/src/uws/service/log/UWSLogType.java
deleted file mode 100644
index 1aed19bd4c58e5a55f33989b2892db1a60f40d03..0000000000000000000000000000000000000000
--- a/src/uws/service/log/UWSLogType.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package uws.service.log;
-
-/*
- * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-/**
- * Different types of log messages.
- * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
- * 
- * @see UWSLog
- * @see DefaultUWSLog
- */
-public enum UWSLogType{
-	HTTP_ACTIVITY, DEBUG, INFO, WARNING, ERROR, CUSTOM;
-
-	protected String customType = this.name();
-
-	public final String getCustomType(){
-		return customType;
-	}
-
-	public static final UWSLogType createCustomLogType(final String customType){
-		UWSLogType type = UWSLogType.CUSTOM;
-		type.customType = customType;
-		return type;
-	}
-
-	@Override
-	public String toString(){
-		if (this == CUSTOM)
-			return customType;
-		else
-			return name();
-	}
-}
diff --git a/test/tap/formatter/JSONFormatTest.java b/test/tap/formatter/JSONFormatTest.java
index 8fa644f0bbbc602857d6134921b72b7525fa17a7..527d7e2cfda74d37bc49f4e2b247de4345083370 100644
--- a/test/tap/formatter/JSONFormatTest.java
+++ b/test/tap/formatter/JSONFormatTest.java
@@ -228,7 +228,6 @@ public class JSONFormatTest {
 			return null;
 		}
 
-		@SuppressWarnings("rawtypes")
 		@Override
 		public TAPFactory getFactory(){
 			return null;
diff --git a/test/tap/formatter/SVFormatTest.java b/test/tap/formatter/SVFormatTest.java
index eea52ccf62362d8becbb0bf486c6d864d821ea1a..d71c6accf79a64cf5343209af274af62265663ad 100644
--- a/test/tap/formatter/SVFormatTest.java
+++ b/test/tap/formatter/SVFormatTest.java
@@ -4,10 +4,8 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.sql.Connection;
 import java.sql.ResultSet;
@@ -33,6 +31,7 @@ import tap.metadata.TAPMetadata;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
 import tap.parameters.TAPParameters;
+import testtools.CommandExecute;
 import testtools.DBTools;
 import uws.service.UserIdentifier;
 
@@ -42,7 +41,7 @@ import uws.service.UserIdentifier;
  * <p>2 test ares done: 1 with an overflow and another without.</p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class SVFormatTest {
 
@@ -91,8 +90,7 @@ public class SVFormatTest {
 			formatter.writeResult(it, output, report, Thread.currentThread());
 			output.close();
 
-			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""};
-			assertTrue(executeCommand(cmd).trim().equals("11"));
+			assertTrue(CommandExecute.execute("wc -l < \"" + svFile.getAbsolutePath() + "\"").trim().equals("11"));
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -125,8 +123,7 @@ public class SVFormatTest {
 			formatter.writeResult(it, output, report, Thread.currentThread());
 			output.close();
 
-			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""};
-			assertTrue(executeCommand(cmd).trim().equals("6"));
+			assertTrue(CommandExecute.execute("wc -l < \"" + svFile.getAbsolutePath() + "\"").trim().equals("6"));
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -143,29 +140,6 @@ public class SVFormatTest {
 		}
 	}
 
-	private String executeCommand(String[] command){
-
-		StringBuffer output = new StringBuffer();
-
-		Process p;
-		try{
-			p = Runtime.getRuntime().exec(command);
-			p.waitFor();
-			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-
-			String line = "";
-			while((line = reader.readLine()) != null){
-				output.append(line + "\n");
-			}
-
-		}catch(Exception e){
-			e.printStackTrace();
-		}
-
-		return output.toString();
-
-	}
-
 	private static class ServiceConnectionTest implements ServiceConnection {
 
 		@Override
@@ -248,7 +222,6 @@ public class SVFormatTest {
 			return null;
 		}
 
-		@SuppressWarnings("rawtypes")
 		@Override
 		public TAPFactory getFactory(){
 			return null;
diff --git a/test/tap/formatter/TextFormatTest.java b/test/tap/formatter/TextFormatTest.java
index 6715bb399661fe9d935c249be79ca67398f24d77..959475b608ac2425a9297e0c6f2972723e224719 100644
--- a/test/tap/formatter/TextFormatTest.java
+++ b/test/tap/formatter/TextFormatTest.java
@@ -4,10 +4,8 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.sql.Connection;
 import java.sql.ResultSet;
@@ -33,6 +31,7 @@ import tap.metadata.TAPMetadata;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
 import tap.parameters.TAPParameters;
+import testtools.CommandExecute;
 import testtools.DBTools;
 import uws.service.UserIdentifier;
 
@@ -42,7 +41,7 @@ import uws.service.UserIdentifier;
  * <p>2 test ares done: 1 with an overflow and another without.</p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class TextFormatTest {
 
@@ -91,8 +90,7 @@ public class TextFormatTest {
 			formatter.writeResult(it, output, report, Thread.currentThread());
 			output.close();
 
-			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""};
-			assertTrue(executeCommand(cmd).trim().equals("12"));
+			assertTrue(CommandExecute.execute("wc -l < \"" + textFile.getAbsolutePath() + "\"").trim().equals("12"));
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -125,8 +123,7 @@ public class TextFormatTest {
 			formatter.writeResult(it, output, report, Thread.currentThread());
 			output.close();
 
-			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""};
-			assertTrue(executeCommand(cmd).trim().equals("7"));
+			assertTrue(CommandExecute.execute("wc -l < \"" + textFile.getAbsolutePath() + "\"").trim().equals("7"));
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -143,29 +140,6 @@ public class TextFormatTest {
 		}
 	}
 
-	private String executeCommand(String[] command){
-
-		StringBuffer output = new StringBuffer();
-
-		Process p;
-		try{
-			p = Runtime.getRuntime().exec(command);
-			p.waitFor();
-			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-
-			String line = "";
-			while((line = reader.readLine()) != null){
-				output.append(line + "\n");
-			}
-
-		}catch(Exception e){
-			e.printStackTrace();
-		}
-
-		return output.toString();
-
-	}
-
 	private static class ServiceConnectionTest implements ServiceConnection {
 
 		@Override
@@ -248,7 +222,6 @@ public class TextFormatTest {
 			return null;
 		}
 
-		@SuppressWarnings("rawtypes")
 		@Override
 		public TAPFactory getFactory(){
 			return null;
diff --git a/test/tap/formatter/VOTableFormatTest.java b/test/tap/formatter/VOTableFormatTest.java
index 5a07973d8426a9ad04ec37449c064e32e492936a..51e9dc8ab47732d008335527eba3564dd03488cc 100644
--- a/test/tap/formatter/VOTableFormatTest.java
+++ b/test/tap/formatter/VOTableFormatTest.java
@@ -4,10 +4,8 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.sql.Connection;
 import java.sql.ResultSet;
@@ -33,6 +31,7 @@ import tap.metadata.TAPMetadata;
 import tap.metadata.TAPType;
 import tap.metadata.TAPType.TAPDatatype;
 import tap.parameters.TAPParameters;
+import testtools.CommandExecute;
 import testtools.DBTools;
 import uk.ac.starlink.votable.DataFormat;
 import uws.service.UserIdentifier;
@@ -43,7 +42,7 @@ import uws.service.UserIdentifier;
  * <p>2 test ares done: 1 with an overflow and another without.</p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (09/2014)
  */
 public class VOTableFormatTest {
 
@@ -93,11 +92,8 @@ public class VOTableFormatTest {
 			output.close();
 
 			// note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l":
-			String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
-			assertEquals("10", executeCommand(cmd).trim());
-
-			cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
-			assertEquals("0", executeCommand(cmd).trim());
+			assertEquals("10", CommandExecute.execute("grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l").trim());
+			assertEquals("0", CommandExecute.execute("grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l").trim());
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -131,11 +127,8 @@ public class VOTableFormatTest {
 			output.close();
 
 			// note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l":
-			String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
-			assertEquals("5", executeCommand(cmd).trim());
-
-			cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
-			assertEquals("1", executeCommand(cmd).trim());
+			assertEquals("5", CommandExecute.execute("grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l").trim());
+			assertEquals("1", CommandExecute.execute("grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l").trim());
 
 		}catch(Exception t){
 			t.printStackTrace();
@@ -152,29 +145,6 @@ public class VOTableFormatTest {
 		}
 	}
 
-	private String executeCommand(String[] command){
-
-		StringBuffer output = new StringBuffer();
-
-		Process p;
-		try{
-			p = Runtime.getRuntime().exec(command);
-			p.waitFor();
-			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-
-			String line = "";
-			while((line = reader.readLine()) != null){
-				output.append(line + "\n");
-			}
-
-		}catch(Exception e){
-			e.printStackTrace();
-		}
-
-		return output.toString();
-
-	}
-
 	private static class ServiceConnectionTest implements ServiceConnection {
 
 		@Override
@@ -257,7 +227,6 @@ public class VOTableFormatTest {
 			return null;
 		}
 
-		@SuppressWarnings("rawtypes")
 		@Override
 		public TAPFactory getFactory(){
 			return null;
diff --git a/test/testtools/CommandExecute.java b/test/testtools/CommandExecute.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e78978ae39eaa6d0c0a67d007eb8936d8c4ea28
--- /dev/null
+++ b/test/testtools/CommandExecute.java
@@ -0,0 +1,51 @@
+package testtools;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Let's execute any shell command (even with pipes and redirections).
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (09/2014)
+ */
+public final class CommandExecute {
+
+	/**
+	 * SINGLETON CLASS.
+	 * No instance of this class can be created.
+	 */
+	private CommandExecute(){}
+
+	/**
+	 * Execute the given command (which may include pipe(s) and/or redirection(s)).
+	 * 
+	 * @param command	Command to execute in the shell.
+	 * 
+	 * @return	The string returned by the execution of the command.
+	 */
+	public final static String execute(final String command){
+
+		String[] shellCmd = new String[]{"/bin/sh","-c",command};
+
+		StringBuffer output = new StringBuffer();
+
+		Process p;
+		try{
+			p = Runtime.getRuntime().exec(shellCmd);
+			p.waitFor();
+			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+			String line = "";
+			while((line = reader.readLine()) != null){
+				output.append(line + "\n");
+			}
+
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+
+		return output.toString();
+
+	}
+}
diff --git a/test/uws/service/UWSUrlTest.java b/test/uws/service/UWSUrlTest.java
index 28249ef27d0b77730f2934433247f3832f68504c..673365076427418810a9b270f20168e44d216636 100644
--- a/test/uws/service/UWSUrlTest.java
+++ b/test/uws/service/UWSUrlTest.java
@@ -30,8 +30,6 @@ import javax.servlet.http.Part;
 import org.junit.Before;
 import org.junit.Test;
 
-import uws.UWSException;
-
 public class UWSUrlTest {
 
 	public static final class TestHttpServletRequest implements HttpServletRequest {
@@ -409,7 +407,7 @@ public class UWSUrlTest {
 		try{
 			UWSUrl uu = new UWSUrl(requestFromPath2root);
 			assertEquals(uu.getBaseURI(), "/path");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2root.getRequestURL());
 		}
 
@@ -417,7 +415,7 @@ public class UWSUrlTest {
 		try{
 			UWSUrl uu = new UWSUrl(requestFromPath2async);
 			assertEquals(uu.getBaseURI(), "/path");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2async.getRequestURL());
 		}
 
@@ -425,7 +423,7 @@ public class UWSUrlTest {
 		try{
 			UWSUrl uu = new UWSUrl(requestFromRoot2root);
 			assertEquals(uu.getBaseURI(), "");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2root.getRequestURL());
 		}
 
@@ -433,15 +431,15 @@ public class UWSUrlTest {
 		try{
 			UWSUrl uu = new UWSUrl(requestFromRoot2async);
 			assertEquals(uu.getBaseURI(), "");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2async.getRequestURL());
 		}
 
 		// CASE 5: http://localhost:8080/tapTest/path/async with url-pattern = /path/*
 		try{
 			new UWSUrl(requestWithServletPathNull);
-		}catch(UWSException e){
-			assertEquals(e.getMessage(), "The extracted base UWS URI is NULL !");
+		}catch(NullPointerException e){
+			assertEquals(e.getMessage(), "The extracted base UWS URI is NULL!");
 		}
 	}
 
@@ -453,7 +451,7 @@ public class UWSUrlTest {
 			uu.load(requestFromPath2root);
 			assertEquals(uu.getUwsURI(), "");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/path/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2root.getRequestURL());
 		}
 		// CASE 1b: Idem while loading http://localhost:8080/tapTest/path/async
@@ -462,7 +460,7 @@ public class UWSUrlTest {
 			uu.load(requestFromPath2async);
 			assertEquals(uu.getUwsURI(), "/async");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/path/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2async.getRequestURL());
 		}
 
@@ -472,7 +470,7 @@ public class UWSUrlTest {
 			uu.load(requestFromPath2async);
 			assertEquals(uu.getUwsURI(), "/async");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/path/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2async.getRequestURL());
 		}
 
@@ -482,7 +480,7 @@ public class UWSUrlTest {
 			uu.load(requestFromPath2root);
 			assertEquals(uu.getUwsURI(), "");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/path/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromPath2root.getRequestURL());
 		}
 
@@ -492,7 +490,7 @@ public class UWSUrlTest {
 			uu.load(requestFromRoot2root);
 			assertEquals(uu.getUwsURI(), "");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2root.getRequestURL());
 		}
 
@@ -502,7 +500,7 @@ public class UWSUrlTest {
 			uu.load(requestFromRoot2async);
 			assertEquals(uu.getUwsURI(), "/async");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2async.getRequestURL());
 		}
 
@@ -512,7 +510,7 @@ public class UWSUrlTest {
 			uu.load(requestFromRoot2async);
 			assertEquals(uu.getUwsURI(), "/async");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2async.getRequestURL());
 		}
 
@@ -522,7 +520,7 @@ public class UWSUrlTest {
 			uu.load(requestFromRoot2root);
 			assertEquals(uu.getUwsURI(), "");
 			assertEquals(uu.jobSummary("async", "123456A").toString(), "http://localhost:8080/tapTest/async/123456A");
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2root.getRequestURL());
 		}
 
@@ -531,7 +529,7 @@ public class UWSUrlTest {
 			UWSUrl uu = new UWSUrl(requestFromRoot2async);
 			uu.load(requestFromPath2async);
 			assertFalse(uu.getUwsURI().equals(""));
-		}catch(UWSException e){
+		}catch(NullPointerException e){
 			fail("This HTTP request is perfectly correct: " + requestFromRoot2root.getRequestURL());
 		}
 	}
diff --git a/test/uws/service/file/TestLogRotation.java b/test/uws/service/file/TestLogRotation.java
new file mode 100644
index 0000000000000000000000000000000000000000..05f937bc3ae8d3c19929c4b9ec7f7d7d9c4bdcf1
--- /dev/null
+++ b/test/uws/service/file/TestLogRotation.java
@@ -0,0 +1,240 @@
+package uws.service.file;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import uws.UWSException;
+import uws.service.log.DefaultUWSLog;
+import uws.service.log.UWSLog;
+import uws.service.log.UWSLog.LogLevel;
+
+public class TestLogRotation {
+
+	@Test
+	public void testEventFrequencyCreation(){
+		EventFrequency freq;
+
+		try{
+			String DEFAULT_FREQ = "daily at 00:00";
+
+			// FREQ = NULL => !!! ; frequency = every day
+			freq = new EventFrequency(null);
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "" => !!! ; frequency = every day
+			freq = new EventFrequency("");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "blabla" => !!!
+			freq = new EventFrequency("blabla");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			/* *********** */
+			/* DAILY EVENT */
+			/* *********** */
+			DEFAULT_FREQ = "daily at 00:00";
+
+			// FREQ = "D" => ok! ; frequency = every day at midnight
+			freq = new EventFrequency("D");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "D 06" => !!! ; frequency = every day at midnight
+			freq = new EventFrequency("D 06");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "D 06 30" => ok! ; frequency = every day at 06:30
+			freq = new EventFrequency("D 06 30");
+			assertEquals("daily at 06:30", freq.toString());
+
+			// FREQ = "D 6 30" => ok! ; frequency = every day at 06:30
+			freq = new EventFrequency("D 6 30");
+			assertEquals("daily at 06:30", freq.toString());
+
+			// FREQ = "D 24 30" => !!! ; frequency = every day at midnight
+			freq = new EventFrequency("D 24 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "D 06 60" => !!! ; frequency = every day at midnight
+			freq = new EventFrequency("D 06 60");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "D 6 30 01 blabla" => ok! ; frequency = every day at 06:30
+			freq = new EventFrequency("D 6 30");
+			assertEquals("daily at 06:30", freq.toString());
+
+			// FREQ = "d 06 30" => !!! ; frequency = every day at midnight
+			freq = new EventFrequency("d 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "D HH mm" => !!!
+			freq = new EventFrequency("D HH mm");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			/* ********** */
+			/* WEEK EVENT */
+			/* ********** */
+			DEFAULT_FREQ = "weekly on Sunday at 00:00";
+
+			// FREQ = "W" => ok! ; frequency = every week the Sunday at midnight
+			freq = new EventFrequency("W");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 06" => !!! ; frequency = every week the Sunday at midnight
+			freq = new EventFrequency("W 06");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 06 30" => !!! ; frequency = every week the Sunday at midnight
+			freq = new EventFrequency("W 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 2" => !!! ; frequency = every week the Sunday at midnight
+			freq = new EventFrequency("W 2");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 2 06" => !!! ; frequency = every week the Sunday at midnight
+			freq = new EventFrequency("W 2 06");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 2 06 30" => ok! ; frequency = every week the Monday at 06:30
+			freq = new EventFrequency("W 2 06 30");
+			assertEquals("weekly on Monday at 06:30", freq.toString());
+
+			// FREQ = "W 0 06 30" => !!! ; frequency = every week the Sunday at 06:30
+			freq = new EventFrequency("W 0 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "W 10 06 30" => !!! ; frequency = every week the Sunday at 06:30
+			freq = new EventFrequency("W 10 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "w 2 06 30" => !!! ; frequency = every day at 00:00
+			freq = new EventFrequency("w 2 06 30");
+			assertEquals("daily at 00:00", freq.toString());
+
+			// FREQ = "W 2 6 30" => ok! ; frequency = every week the Monday at 06:30
+			freq = new EventFrequency("W 2 6 30");
+			assertEquals("weekly on Monday at 06:30", freq.toString());
+
+			// FREQ = "W 2 6 30 12 blabla" => ok! ; frequency = every week the Monday at 06:30
+			freq = new EventFrequency("W 2 6 30");
+			assertEquals("weekly on Monday at 06:30", freq.toString());
+
+			/* ***************************************** */
+			/* MONTH EVENT (same code as for WEEK EVENT) */
+			/* ***************************************** */
+			DEFAULT_FREQ = "monthly on the 1st at 00:00";
+
+			// FREQ = "M 2 06 30" => ok! ; frequency = every month on the 2nd at 06:30
+			freq = new EventFrequency("M 2 06 30");
+			assertEquals("monthly on the 2nd at 06:30", freq.toString());
+
+			// FREQ = "m 2 06 30" => !!! ; frequency = every minute
+			freq = new EventFrequency("m 2 06 30");
+			assertEquals("every minute", freq.toString());
+
+			// FREQ = "M 0 06 30" => !!! ; frequency = every month on the 1st at 00:00
+			freq = new EventFrequency("M 0 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "M 32 06 30" => !!! ; frequency = every month on the 1st at 00:00
+			freq = new EventFrequency("M 32 06 30");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			/* ********** */
+			/* HOUR EVENT */
+			/* ********** */
+			DEFAULT_FREQ = "hourly at 00";
+
+			// FREQ = "h" => ok! ; frequency = every hour at 00
+			freq = new EventFrequency("h");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "h 10" => ok! ; frequency = every hour at 10
+			freq = new EventFrequency("h 10");
+			assertEquals("hourly at 10", freq.toString());
+
+			// FREQ = "H 10" => !!! ; frequency = every day at 00:00
+			freq = new EventFrequency("H 10");
+			assertEquals("daily at 00:00", freq.toString());
+
+			// FREQ = "h 5" => ok! ; frequency = every hour at 05
+			freq = new EventFrequency("h 5");
+			assertEquals("hourly at 05", freq.toString());
+
+			// FREQ = "h 60" => !!! ; frequency = every hour at 00
+			freq = new EventFrequency("h 60");
+			assertEquals("hourly at 00", freq.toString());
+
+			// FREQ = "h 10 12 blabla" => ok! ; frequency = every hour at 10
+			freq = new EventFrequency("h 10");
+			assertEquals("hourly at 10", freq.toString());
+
+			/* ********** */
+			/* HOUR EVENT */
+			/* ********** */
+			DEFAULT_FREQ = "every minute";
+
+			// FREQ = "m" => ok! ; frequency = every minute
+			freq = new EventFrequency("m");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "m 10 blabla" => ok! ; frequency = every minute
+			freq = new EventFrequency("m");
+			assertEquals(DEFAULT_FREQ, freq.toString());
+
+			// FREQ = "M" => !!! ; frequency = every month on the 1st at 00:00
+			freq = new EventFrequency("M");
+			assertEquals("monthly on the 1st at 00:00", freq.toString());
+
+		}catch(Exception e){
+			e.printStackTrace(System.err);
+			fail("UNEXPECTED EXCEPTION: \"" + e.getMessage() + "\"");
+		}
+	}
+
+	@Test
+	public void testGetLogOutput(){
+		try{
+			final LocalUWSFileManager fileManager = new LocalUWSFileManager(new File("."));
+			fileManager.logRotation = new EventFrequency("m");
+
+			final UWSLog logger = new DefaultUWSLog(fileManager);
+			for(int i = 0; i < 5; i++){
+				final int logFreq = i + 1;
+				(new Thread(new Runnable(){
+					@Override
+					public void run(){
+						try{
+							for(int cnt = 0; cnt < 3 * 60 / logFreq; cnt++){
+								logger.log(LogLevel.INFO, "TEST", "LOG MESSAGE FROM Thread-" + logFreq, null);
+								assertFalse(fileManager.getLogOutput(LogLevel.INFO, "UWS").checkError());	// if true, it means that at least one attempt to write something fails, and so, that write attempts have been done after a log rotation!
+								Thread.sleep(1000 * logFreq);
+							}
+						}catch(InterruptedException e){
+							e.printStackTrace(System.err);
+							fail("ERROR WITH THE THREAD-" + logFreq);
+						}catch(IOException e){
+							e.printStackTrace(System.err);
+							fail("IO ERROR WHEN RETRIEVING THE LOG OUTPUT IN THE THREAD-" + logFreq);
+						}
+					}
+				})).start();
+			}
+			Thread.sleep(180000);
+
+		}catch(UWSException e){
+			e.printStackTrace(System.err);
+			fail("CAN NOT CREATE THE FILE MANAGER!");
+		}catch(InterruptedException e){
+			e.printStackTrace(System.err);
+			fail("CAN NOT WAIT 3 MINUTES!");
+		}
+	}
+
+}