From 0ef180ce9050364e070fca5b2ccfec2eb5d598b7 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Wed, 17 Sep 2014 15:23:48 +0200
Subject: [PATCH] [UWS,TAP] Review the exceptions and logs management + Add log
 file rotation (with a configurable rotation frequency)

---
 src/tap/ADQLExecutor.java                     |  72 +-
 src/tap/AbstractTAPFactory.java               |  12 +-
 src/tap/AsyncThread.java                      |   8 -
 src/tap/TAPException.java                     |   8 +-
 src/tap/TAPFactory.java                       |  30 +-
 src/tap/TAPJob.java                           |   7 +-
 src/tap/TAPSyncJob.java                       | 219 +++++-
 src/tap/backup/DefaultTAPBackupManager.java   |   7 +-
 src/tap/db/DBConnection.java                  |  11 +-
 src/tap/db/JDBCConnection.java                | 173 ++---
 src/tap/error/DefaultTAPErrorWriter.java      | 126 +++-
 src/tap/file/LocalTAPFileManager.java         |  20 +-
 src/tap/formatter/JSONFormat.java             |   5 +-
 src/tap/formatter/SVFormat.java               |   7 +-
 src/tap/formatter/TextFormat.java             |   7 +-
 src/tap/formatter/VOTableFormat.java          |  86 ++-
 src/tap/log/DefaultTAPLog.java                | 174 ++---
 src/tap/log/TAPLog.java                       | 105 +--
 src/tap/metadata/TAPMetadata.java             |  62 +-
 src/tap/parameters/FormatController.java      |  47 +-
 src/tap/parameters/MaxRecController.java      |  37 +-
 .../TAPDestructionTimeController.java         |  51 +-
 .../TAPExecutionDurationController.java       |  38 +-
 src/tap/parameters/TAPParameters.java         |   3 +-
 src/tap/resource/ASync.java                   | 117 +++-
 src/tap/resource/Availability.java            |  73 +-
 src/tap/resource/Capabilities.java            |  93 ++-
 src/tap/resource/Sync.java                    |  72 +-
 src/tap/resource/TAP.java                     | 626 +++++++++++++-----
 src/tap/resource/TAPResource.java             |  66 +-
 src/tap/resource/VOSIResource.java            |  57 +-
 src/tap/upload/Uploader.java                  |  25 +-
 src/uws/UWSException.java                     | 128 +++-
 src/uws/UWSExceptionFactory.java              | 157 +----
 src/uws/job/ErrorSummary.java                 |  11 +-
 src/uws/job/JobList.java                      | 137 ++--
 src/uws/job/JobPhase.java                     |  37 +-
 src/uws/job/JobThread.java                    |  34 +-
 src/uws/job/Result.java                       |  11 +-
 src/uws/job/SerializableUWSObject.java        |  45 +-
 src/uws/job/UWSJob.java                       | 360 +++-------
 .../AbstractQueuedExecutionManager.java       |  76 ++-
 .../job/manager/DefaultExecutionManager.java  |  43 +-
 src/uws/job/manager/ExecutionManager.java     |  35 +-
 .../job/manager/QueuedExecutionManager.java   |  20 +-
 .../parameters/DestructionTimeController.java |  18 +-
 .../ExecutionDurationController.java          |  16 +-
 .../job/parameters/StringParamController.java |  18 +-
 src/uws/job/parameters/UWSParameters.java     |  21 +-
 src/uws/job/serializer/JSONSerializer.java    | 166 ++---
 src/uws/job/serializer/UWSSerializer.java     | 107 +--
 src/uws/job/serializer/XMLSerializer.java     |   3 +-
 src/uws/service/UWSService.java               | 224 ++++---
 src/uws/service/UWSServlet.java               | 318 +++++----
 src/uws/service/UWSUrl.java                   |  17 +-
 src/uws/service/actions/AddJob.java           |  23 +-
 src/uws/service/actions/DestroyJob.java       |  19 +-
 src/uws/service/actions/GetJobParam.java      |  37 +-
 src/uws/service/actions/JobSummary.java       |  23 +-
 src/uws/service/actions/ListJobs.java         |  23 +-
 src/uws/service/actions/SetJobParam.java      |  19 +-
 src/uws/service/actions/ShowHomePage.java     |  30 +-
 src/uws/service/actions/UWSAction.java        |  37 +-
 .../backup/DefaultUWSBackupManager.java       | 107 ++-
 .../error/AbstractServiceErrorWriter.java     | 291 --------
 .../service/error/DefaultUWSErrorWriter.java  | 279 +++++++-
 src/uws/service/error/ServiceErrorWriter.java |  56 +-
 src/uws/service/file/EventFrequency.java      | 498 ++++++++++++++
 src/uws/service/file/LocalUWSFileManager.java | 213 ++++--
 src/uws/service/file/UWSFileManager.java      |  30 +-
 src/uws/service/log/DefaultUWSLog.java        | 363 +++++-----
 src/uws/service/log/UWSLog.java               | 316 ++++++---
 src/uws/service/log/UWSLogType.java           |  53 --
 test/tap/formatter/JSONFormatTest.java        |   1 -
 test/tap/formatter/SVFormatTest.java          |  35 +-
 test/tap/formatter/TextFormatTest.java        |  35 +-
 test/tap/formatter/VOTableFormatTest.java     |  43 +-
 test/testtools/CommandExecute.java            |  51 ++
 test/uws/service/UWSUrlTest.java              |  32 +-
 test/uws/service/file/TestLogRotation.java    | 240 +++++++
 80 files changed, 4488 insertions(+), 2812 deletions(-)
 delete mode 100644 src/uws/service/error/AbstractServiceErrorWriter.java
 create mode 100644 src/uws/service/file/EventFrequency.java
 delete mode 100644 src/uws/service/log/UWSLogType.java
 create mode 100644 test/testtools/CommandExecute.java
 create mode 100644 test/uws/service/file/TestLogRotation.java

diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 82c105a..7acfb28 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 8825fc9..1241c92 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 65c2c37..00f1f2d 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 89a4fee..162d8ea 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 aac9b3e..22158cd 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 52510c5..eae3fe4 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 2a87027..016b0da 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 f84e066..714c817 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 f83e151..45931a6 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 0187246..f3393e3 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 28c8602..e6e7256 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 2e9beec..f7555c3 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 5437854..fbde557 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 81e25c1..88ddb09 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 35aeb21..65f8247 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 16d5e32..d5e07c8 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 ec5b35a..441a8f0 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 84604a4..40a9c71 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 3aa3898..a000ea1 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 526536f..4e8d37a 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 3dabe0e..0fc6655 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 a3c0ca6..1087aa8 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 2187bed..85922cd 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 045e5af..d24d203 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 6d8746a..5fe3c9c 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 1da467e..fc10f97 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 4cbdba0..8e54808 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 c3cf022..460b009 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 acb2d98..696d80a 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 4a830c9..37938da 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 758c8ad..216b55b 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 8286b96..4e610df 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 32aed8c..0035960 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 c281273..c88fff2 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 cc2c983..607dbff 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 9c10ee5..dfd4802 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 cf0a270..1de05bb 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 0b80bd6..1e979db 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 1361bac..aa4386c 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 3fc7c49..f0c45d8 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 24b24e8..088fbfd 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 6a14c8f..49a1d0b 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 e00bda0..333cbca 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 7232cb5..f95bd62 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 4d750e4..51402c8 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 103b64c..011e7d2 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 7c2d7ee..45cd04b 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 39b42bf..85530e3 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 767706a..f33bdcc 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 9ffb832..55fb337 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 3303987..3641a69 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 b2f14c6..c5322a4 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 b6555c8..968a099 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 bacb480..2936d8d 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 2376fa7..78e46cf 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 a726501..5dd182b 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 d6e5d33..5b7625f 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 048ceff..0df5a2b 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 adf9ed0..d645259 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 dfeeb4f..18be064 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 02026bb..5d9a92a 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 c437497..c713c92 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 3813363..7de64b9 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 2a9db4b..be9a0cf 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 a71b45c..0000000
--- 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 82529f6..50386f4 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 9c19a50..42b9a08 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 0000000..98815e7
--- /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 41f89f7..d67f013 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 bc8520b..2de7431 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 5fd9586..100f180 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 350fe02..11cfa0a 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 1aed19b..0000000
--- 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 8fa644f..527d7e2 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 eea52cc..d71c6ac 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 6715bb3..959475b 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 5a07973..51e9dc8 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 0000000..2e78978
--- /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 28249ef..6733650 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 0000000..05f937b
--- /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!");
+		}
+	}
+
+}
-- 
GitLab