From f08018cb304892cf61ad8671742a8c03eb95fb91 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Fri, 20 Oct 2017 17:29:29 +0200
Subject: [PATCH] [UWS] Add PHASE, AFTER and LAST filters on a JobList.

- PHASE: list only jobs in the specified PHASE. If this parameter is repeated
         jobs matching any of the specified phases will be returned.
- AFTER: list jobs created after the specified ISO-8601 date (included).
         If this parameter is repeated, only the most recent date is retained.
- LAST: list the N-th most recently created jobs, ordered by descending
        creation time

These filter parameters are additive: their constraints are joint as with an
AND operator (except for PHASE parameters ; see above).

If no filter is specified, all jobs EXCEPT the ARCHIVED ones are listed. The
only way to list ARCHIVED jobs is to use PHASE=ARCHIVED (with or without other
filter parameters).

The filtering API has been made in a generic manner so that it is easily
possible to create and add new filters. See the interface JobFilter and the
class JobListRefined for more details.
---
 src/org/json/Json4Uws.java                    |   51 +-
 src/uws/job/JobList.java                      |   45 +-
 src/uws/job/serializer/JSONSerializer.java    |    7 +-
 src/uws/job/serializer/UWSSerializer.java     |   48 +-
 src/uws/job/serializer/XMLSerializer.java     |   14 +-
 .../job/serializer/filter/AfterFilter.java    |   71 ++
 src/uws/job/serializer/filter/JobFilter.java  |   50 +
 .../job/serializer/filter/JobListRefiner.java |  473 +++++++
 .../serializer/filter/NoArchivedFilter.java   |   39 +
 .../job/serializer/filter/PhasesFilter.java   |   99 ++
 src/uws/service/UWSServlet.java               |    5 +-
 src/uws/service/actions/ListJobs.java         |   49 +-
 .../serializer/filter/TestAfterFilter.java    |   52 +
 .../serializer/filter/TestJobListRefiner.java | 1085 +++++++++++++++++
 .../filter/TestNoArchivedFilter.java          |   71 ++
 .../serializer/filter/TestPhasesFilter.java   |  125 ++
 16 files changed, 2245 insertions(+), 39 deletions(-)
 create mode 100644 src/uws/job/serializer/filter/AfterFilter.java
 create mode 100644 src/uws/job/serializer/filter/JobFilter.java
 create mode 100644 src/uws/job/serializer/filter/JobListRefiner.java
 create mode 100644 src/uws/job/serializer/filter/NoArchivedFilter.java
 create mode 100644 src/uws/job/serializer/filter/PhasesFilter.java
 create mode 100644 test/uws/job/serializer/filter/TestAfterFilter.java
 create mode 100644 test/uws/job/serializer/filter/TestJobListRefiner.java
 create mode 100644 test/uws/job/serializer/filter/TestNoArchivedFilter.java
 create mode 100644 test/uws/job/serializer/filter/TestPhasesFilter.java

diff --git a/src/org/json/Json4Uws.java b/src/org/json/Json4Uws.java
index 2549e0a..25a85c6 100644
--- a/src/org/json/Json4Uws.java
+++ b/src/org/json/Json4Uws.java
@@ -29,6 +29,7 @@ import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
 import uws.job.jobInfo.JobInfo;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSUrl;
@@ -37,7 +38,7 @@ import uws.service.UWSUrl;
  * Useful conversion functions from UWS to JSON.
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  */
 public final class Json4Uws {
 
@@ -75,25 +76,65 @@ public final class Json4Uws {
 
 	/**
 	 * Gets the JSON representation of the given jobs list.
-	 * @param jobsList			The jobs list to represent in JSON.
-	 * @param owner				The user who asks to serialize the given jobs list. (MAY BE NULL)
-	 * @return					Its JSON representation.
-	 * @throws JSONException	If there is an error while building the JSON object.
+	 *
+	 * @param jobsList	The jobs list to represent in JSON.
+	 * @param owner		The user who asks to serialize the given jobs list.
+	 *             		(MAY BE NULL)
+	 *
+	 * @return	Its JSON representation.
+	 *
+	 * @throws JSONException	If there is an error while building the JSON
+	 *                      	object.
+	 *
+	 * @see #getJson(JobList, JobOwner, JobListRefiner)
 	 */
 	public final static JSONObject getJson(final JobList jobsList, final JobOwner owner) throws JSONException{
+		return getJson(jobsList, owner, null);
+	}
+
+	/**
+	 * Gets the JSON representation of the given jobs list by filtering by owner
+	 * and some user-filters (e.g. on phase, creation time).
+	 *
+	 * @param jobsList		The jobs list to represent in JSON.
+	 * @param owner			The user who asks to serialize the given jobs list.
+	 *             			(MAY BE NULL)
+	 * @param listRefiner	Represent all the specified job filters to apply ;
+	 *                   	only the job that pass through this filter should be
+	 *                   	displayed. If NULL, all jobs are displayed.
+	 *
+	 * @return	Its JSON representation.
+	 *
+	 * @throws JSONException	If there is an error while building the JSON
+	 *                      	object.
+	 *
+	 * @since 4.3
+	 */
+	public final static JSONObject getJson(final JobList jobsList, final JobOwner owner, final JobListRefiner listRefiner) throws JSONException{
 		JSONObject json = new JSONObject();
 		if (jobsList != null){
 			json.put("name", jobsList.getName());
 			json.put("version", UWS.VERSION);
 			JSONArray jsonJobs = new JSONArray();
 			UWSUrl jobsListUrl = jobsList.getUrl();
+
+			// Security filter: retrieve only the jobs of the specified owner:
 			Iterator<UWSJob> it = jobsList.getJobs(owner);
+
+			/* User filter: filter the jobs in function of filters specified by
+			 * the user:  */
+			if (listRefiner != null)
+				it = listRefiner.refine(it);
+
+			// Append the JSON serialization of all filtered jobs:
 			JSONObject jsonObj = null;
 			while(it.hasNext()){
 				jsonObj = getJson(it.next(), jobsListUrl, true);
 				if (jsonObj != null)
 					jsonJobs.put(jsonObj);
+
 			}
+
 			json.put("jobs", jsonJobs);
 		}
 		return json;
diff --git a/src/uws/job/JobList.java b/src/uws/job/JobList.java
index 3923c4a..a19c135 100644
--- a/src/uws/job/JobList.java
+++ b/src/uws/job/JobList.java
@@ -20,6 +20,7 @@ package uws.job;
  *                       Astronomisches Rechen Institut (ARI)
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Iterator;
@@ -27,6 +28,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.servlet.ServletOutputStream;
+
 import uws.UWSException;
 import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
@@ -35,6 +38,7 @@ import uws.job.manager.DefaultExecutionManager;
 import uws.job.manager.DestructionManager;
 import uws.job.manager.ExecutionManager;
 import uws.job.serializer.UWSSerializer;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSService;
@@ -171,7 +175,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </i></p>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  *
  * @see UWSJob
  */
@@ -1000,6 +1004,45 @@ public class JobList extends SerializableUWSObject implements Iterable<UWSJob> {
 		}
 	}
 
+	/**
+	 * Serializes the while object in the given output stream,
+	 * considering the given owner ID, the given job filters and thanks to the
+	 * given serializer.
+	 *
+	 * @param output		The ouput stream in which this object must be
+	 *              		serialized.
+	 * @param serializer	The serializer to use.
+	 * @param ownerId		The ID of the current ID.
+	 * @param listRefiner	Special filter able to refine the list of jobs with
+	 *                   	job filters specified by the user
+	 *                   	(i.e. filter, sort and limit).
+	 *
+	 * @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 UWSSerializer#getJobList(JobList, JobOwner, JobListRefiner, boolean)
+	 *
+	 * @since 4.3
+	 */
+	public void serialize(ServletOutputStream output, UWSSerializer serializer, JobOwner owner, JobListRefiner listRefiner) throws UWSException, IOException, Exception{
+		if (output == null)
+			throw new NullPointerException("Missing serialization output stream!");
+
+		if (owner != null && !owner.hasReadPermission(this))
+			throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(owner, true, getName()));
+
+		String serialization = serializer.getJobList(this, owner, listRefiner, true);
+		if (serialization != null){
+			output.print(serialization);
+			output.flush();
+		}else
+			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect serialization value (=NULL) ! => impossible to serialize " + toString() + ".");
+	}
+
 	/* ***************** */
 	/* INHERITED METHODS */
 	/* ***************** */
diff --git a/src/uws/job/serializer/JSONSerializer.java b/src/uws/job/serializer/JSONSerializer.java
index 7a5089a..fbc30e9 100644
--- a/src/uws/job/serializer/JSONSerializer.java
+++ b/src/uws/job/serializer/JSONSerializer.java
@@ -28,6 +28,7 @@ import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSUrl;
@@ -36,7 +37,7 @@ import uws.service.UWSUrl;
  * Lets serializing any UWS resource in JSON.
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  *
  * @see Json4Uws
  */
@@ -54,8 +55,8 @@ public class JSONSerializer extends UWSSerializer {
 	}
 
 	@Override
-	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root) throws JSONException{
-		return Json4Uws.getJson(jobsList, owner).toString();
+	public String getJobList(final JobList jobsList, final JobOwner owner, final JobListRefiner listRefiner, final boolean root) throws JSONException{
+		return Json4Uws.getJson(jobsList, owner, listRefiner).toString();
 	}
 
 	@Override
diff --git a/src/uws/job/serializer/UWSSerializer.java b/src/uws/job/serializer/UWSSerializer.java
index 64f0a73..f7cf1a8 100644
--- a/src/uws/job/serializer/UWSSerializer.java
+++ b/src/uws/job/serializer/UWSSerializer.java
@@ -28,6 +28,7 @@ import uws.job.ErrorSummary;
 import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSUrl;
@@ -41,7 +42,7 @@ import uws.service.UWSUrl;
  * </ul>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  *
  * @see XMLSerializer
  * @see JSONSerializer
@@ -207,23 +208,58 @@ public abstract class UWSSerializer implements Serializable {
 	 * @return				The serialization of the given jobs list.
 	 *
 	 * @throws Exception	If there is an error during the serialization.
+	 *
+	 * @see #getJobList(JobList, JobOwner, boolean)
 	 */
 	public String getJobList(final JobList jobsList, final boolean root) throws Exception{
 		return getJobList(jobsList, null, root);
 	}
 
 	/**
-	 * Serializes the given jobs list.
+	 * Serializes the given jobs list, by filtering by user.
 	 *
 	 * @param jobsList		The jobs list to serialize.
-	 * @param owner			The user which has asked the serialization of the given jobs list.
-	 * @param root			<i>false</i> if the jobs list to serialize will be included
-	 * 						in a top level serialization (for a jobs list: uws), <i>true</i> otherwise.
+	 * @param owner			The user which has asked the serialization of the
+	 *             			given jobs list. If NULL, all anonymous jobs are
+	 *             			displayed.
+	 * @param root			<code>false</code> if the jobs list to serialize
+	 *            			will be included in a top level serialization (for a
+	 *            			jobs list: uws),
+	 *            			<code>true</code> otherwise.
+	 *
+	 * @return				The serialization of the given jobs list.
+	 *
+	 * @throws Exception	If there is an error during the serialization.
+	 *
+	 * @see #getJobList(JobList, JobOwner, JobListRefiner, boolean)
+	 */
+	public String getJobList(final JobList jobsList, JobOwner owner, final boolean root) throws Exception{
+		return getJobList(jobsList, null, null, root);
+	}
+
+	/**
+	 * Serializes the given jobs list, by filtering using user-specified
+	 * filters.
+	 *
+	 * @param jobsList		The jobs list to serialize.
+	 * @param owner			The user which has asked the serialization of the
+	 *             			given jobs list. If NULL, all anonymous jobs are
+	 *             			displayed.
+	 * @param phaseFilters	Represent all the specified job filters to apply ;
+	 *                    	only the job that pass through this filter should be
+	 *                    	displayed. If NULL, all jobs are displayed.
+	 * @param root			<code>false</code> if the jobs list to serialize
+	 *            			will be included in a top level serialization (for a
+	 *            			jobs list: uws),
+	 *            			<code>true</code> otherwise.
+	 *
 	 * @return				The serialization of the given jobs list.
 	 *
 	 * @throws Exception	If there is an error during the serialization.
+	 *
+	 * @since 4.3
 	 */
-	public abstract String getJobList(final JobList jobsList, JobOwner owner, final boolean root) throws Exception;
+	public abstract String getJobList(final JobList jobsList, JobOwner owner, final JobListRefiner listRefiner, final boolean root) throws Exception;
 
 	/**
 	 * Serializes the whole given job.
diff --git a/src/uws/job/serializer/XMLSerializer.java b/src/uws/job/serializer/XMLSerializer.java
index 44cb254..b44bdf3 100644
--- a/src/uws/job/serializer/XMLSerializer.java
+++ b/src/uws/job/serializer/XMLSerializer.java
@@ -31,6 +31,7 @@ import uws.job.JobList;
 import uws.job.Result;
 import uws.job.UWSJob;
 import uws.job.jobInfo.JobInfo;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSUrl;
@@ -40,7 +41,7 @@ import uws.service.request.UploadFile;
  * Lets serializing any UWS resource in XML.
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  */
 public class XMLSerializer extends UWSSerializer {
 	private static final long serialVersionUID = 1L;
@@ -178,7 +179,7 @@ public class XMLSerializer extends UWSSerializer {
 	}
 
 	@Override
-	public String getJobList(final JobList jobsList, final JobOwner owner, final boolean root){
+	public String getJobList(final JobList jobsList, final JobOwner owner, final JobListRefiner listRefiner, final boolean root) throws Exception{
 		StringBuffer xml = new StringBuffer(getHeader());
 
 		xml.append("<jobs version=\"").append(UWS.VERSION).append('"').append(getUWSNamespace(true));
@@ -190,7 +191,16 @@ public class XMLSerializer extends UWSSerializer {
 		xml.append('>');
 
 		UWSUrl jobsListUrl = jobsList.getUrl();
+
+		// Security filter: retrieve only the jobs of the specified owner:
 		Iterator<UWSJob> it = jobsList.getJobs(owner);
+
+		/* User filter: filter the jobs in function of filters specified by the
+		 * user: */
+		if (listRefiner != null)
+			it = listRefiner.refine(it);
+
+		// Append the jobs' description:
 		while(it.hasNext())
 			xml.append("\n\t").append(getJobRef(it.next(), jobsListUrl));
 
diff --git a/src/uws/job/serializer/filter/AfterFilter.java b/src/uws/job/serializer/filter/AfterFilter.java
new file mode 100644
index 0000000..bcf3094
--- /dev/null
+++ b/src/uws/job/serializer/filter/AfterFilter.java
@@ -0,0 +1,71 @@
+package uws.job.serializer.filter;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.Date;
+
+import uws.job.UWSJob;
+
+/**
+ * Job filter based on the creation time.
+ *
+ * <p>
+ * 	Only jobs that have been created after a given date (included) are kept.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (10/2017)
+ * @since 4.3
+ */
+public final class AfterFilter implements JobFilter {
+
+	/** The date after which jobs must be kept. */
+	private final Date limit;
+
+	/**
+	 * Build the {@link AfterFilter} with the given limit date.
+	 *
+	 * @param date	The date (non-included) after which jobs must be kept.
+	 *
+	 * @throws NullPointerException	If the given date is NULL.
+	 */
+	public AfterFilter(final Date date) throws NullPointerException{
+		if (date == null)
+			throw new NullPointerException("Missing limit date! Can not create an AfterFilter.");
+
+		limit = date;
+	}
+
+	/**
+	 * Get the date which filters jobs on their creationTime.
+	 * Only jobs created after this date will be retained.
+	 *
+	 * @return	The limit date.
+	 */
+	public final Date getDate(){
+		return limit;
+	}
+
+	@Override
+	public boolean match(final UWSJob job){
+		return (job != null) && (job.getCreationTime() != null) && (job.getCreationTime().after(limit));
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/job/serializer/filter/JobFilter.java b/src/uws/job/serializer/filter/JobFilter.java
new file mode 100644
index 0000000..ec2ef9c
--- /dev/null
+++ b/src/uws/job/serializer/filter/JobFilter.java
@@ -0,0 +1,50 @@
+package uws.job.serializer.filter;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import uws.job.UWSJob;
+
+/**
+ * Definition of a filter aiming to reduce a list of jobs.
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (10/2017)
+ * @since 4.3
+ *
+ * @see JobFilters
+ */
+public interface JobFilter {
+
+	/**
+	 * Tell whether the given job match this filter.
+	 *
+	 * <p><i>Note:
+	 * 	In case of error while evaluating this filter on the given job,
+	 * 	<code>false</code> will be returned.
+	 * </i></p>
+	 *
+	 * @param job	A job to filter.
+	 *
+	 * @return	<code>true</code> if the job matches this filter,
+	 *        	<code>false</code> otherwise.
+	 */
+	public boolean match(final UWSJob job);
+
+}
\ No newline at end of file
diff --git a/src/uws/job/serializer/filter/JobListRefiner.java b/src/uws/job/serializer/filter/JobListRefiner.java
new file mode 100644
index 0000000..d36c200
--- /dev/null
+++ b/src/uws/job/serializer/filter/JobListRefiner.java
@@ -0,0 +1,473 @@
+package uws.job.serializer.filter;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.text.ParseException;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.servlet.http.HttpServletRequest;
+
+import uws.ISO8601Format;
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+
+/**
+ * Let filter (and optionally order) a list of jobs according to the filter
+ * parameters given in an {@link HttpServletRequest}.
+ *
+ * <p>Only the following HTTP-GET parameters will generate a job filter:</p>
+ * <ul>
+ * 	<li><b>PHASE</b>: a single legal phase is expected. Only jobs having this
+ * 	                  phase will pass through this filter.
+ *
+ * 	                  <p><i><b>Note:</b>Several <code>PHASE</code> parameters
+ * 	                  with different execution phases may be provided. Their
+ * 	                  effect will be joint with a logical OR (so, all jobs of
+ * 	                  one of the given phases will pass the filter).</i></p></li>
+ *
+ * 	<li><b>AFTER</b>: an ISO-8601 date is expected. Only jobs started
+ * 	                  after this date will pass through the filter.
+ *
+ * 	                  <p><i><b>Note:</b> If several <code>AFTER</code>
+ * 	                  parameters are provided, only the one with the most recent
+ * 	                  date will be taken into account. For instance:
+ * 	                  <code>?AFTER=2015-01-01T12:00:00Z&AFTER=2014-01-01T12:00:00Z</code>
+ * 	                  will be interpreted as just 1 AFTER filter with the date
+ * 	                  <code>2015-01-01T12:00:00Z</code>.</i></p></li>
+ *
+ * 	<li><b>LAST</b>: a positive integer number is expected. Only the LAST most
+ * 	                 recently created jobs will pass through the filter.
+ * 	                 The jobs will be sorted by descending creationTime.
+ *
+ * 	                 <p><i><b>Note:</b> If several <code>LAST</code>
+ * 	                 parameters are provided, only the smallest positive (and
+ * 	                 not null) value will be taken into account.</i></p>
+ * </ul>
+ *
+ * <p><i><b>IMPORTANT Note:</b>
+ * 	If no PHASE filter is specified, a default one rejecting ARCHIVED filter is
+ * 	set by default. This is specified in UWS 1.1 standard so that being backward
+ * 	compatible with the version 1.0 of the standard in which no ARCHIVED phase
+ * 	existed.
+ * </i></p>
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (10/2017)
+ * @since 4.3
+ */
+public class JobListRefiner {
+
+	/** List of all job filters to apply. All of these filters must match in
+	 * order to keep a job. */
+	protected final List<JobFilter> filters = new ArrayList<JobFilter>();
+
+	/** List of retained jobs after filtering. */
+	protected List<UWSJob> jobList = new ArrayList<UWSJob>();
+
+	/** Indicate how jobs must be sorted in the {@link #jobList} list.
+	 *
+	 * <p><i>Note:
+	 * 	If NULL, jobs are merely added at the end of the list: no order.
+	 * </i></p> */
+	protected Comparator<UWSJob> sortComp = null;
+
+	/** Indicate how many of the first jobs of the list {@link #jobList} must be
+	 * really retained.
+	 *
+	 * <p><i>Note:
+	 * 	If negative or null, all jobs are of the filtered list are returned.
+	 * </i></p> */
+	protected int topSize = -1;
+
+	/** Indicate if the jobs of the {@link #jobList} list must be returned from
+	 * the beginning to the end (<code>false</code>) or from the end to the
+	 * beginning (<code>true</code>).
+	 *
+	 * <p><i>Note:
+	 * 	If {@link #topSize} have a positive and not null value, this attribute
+	 * 	will then apply on the {@link #topSize} first jobs: if <code>false</code>
+	 * 	jobs from 0 to {@link #topSize} are returned, otherwise they are
+	 * 	returned from {@link #topSize} to 0.
+	 * </i></p> */
+	protected boolean reverseOrder = false;
+
+	/**
+	 * Empty constructor. No filter and no sorting is done here.
+	 * All jobs given to the {@link #filter(Iterator)} function will then be
+	 * allowed.
+	 */
+	protected JobListRefiner(){}
+
+	/**
+	 * Build a filter for a whole list of jobs.
+	 *
+	 * <p>
+	 * 	This filter will actually be composed of several Job filters,
+	 * 	in function of the HTTP-GET parameters specified in the given HTTP
+	 * 	request.
+	 * </p>
+	 *
+	 * <p>Only the following HTTP-GET parameters will generate a job filter:</p>
+	 * <ul>
+	 * 	<li><b>PHASE</b>: a single legal phase is expected. Only jobs having
+	 * 	                  this phase will pass through this filter.
+	 *
+	 * 	                  <p><i><b>Note:</b>Several <code>PHASE</code>
+	 * 	                  parameters with different execution phases may be
+	 * 	                  provided. Their effect will be joint with a logical OR
+	 * 	                  (so, all jobs of one of the given phases will pass the
+	 * 	                  filter).</i></p></li>
+	 *
+	 * 	<li><b>AFTER</b>: an ISO-8601 date is expected. Only jobs started
+	 * 	                  after this date will pass through the filter.
+	 *
+	 * 	                  <p><i><b>Note:</b> If several <code>AFTER</code>
+	 * 	                  parameters are provided, only the one with the most
+	 * 	                  recent date will be taken into account. For instance:
+	 * 	                  <code>?AFTER=2015-01-01T12:00:00Z&AFTER=2014-01-01T12:00:00Z</code>
+	 * 	                  will be interpreted as just 1 AFTER filter with the
+	 * 	                  date <code>2015-01-01T12:00:00Z</code>.</i></p></li>
+	 *
+	 * 	<li><b>LAST</b>: a positive integer number is expected. Only the LAST
+	 * 	                 most recently created jobs will pass through the
+	 * 	                 filter. The jobs will be sorted by descending
+	 * 	                 creationTime.
+	 *
+	 * 	                 <p><i><b>Note:</b> If several <code>LAST</code>
+	 * 	                 parameters are provided, only the smallest positive
+	 * 	                 (and not null) value will be taken into account.</i></p>
+	 * </ul>
+	 *
+	 * <p><i><b>IMPORTANT Note:</b>
+	 * 	If no PHASE filter is specified, a default one rejecting ARCHIVED filter
+	 * 	is set by default. This is specified in UWS 1.1 standard so that being
+	 * 	backward compatible with the version 1.0 of the standard in which no
+	 * 	ARCHIVED phase existed.
+	 * </i></p>
+	 *
+	 * @param request	An HTTP request in which HTTP-GET parameters correspond
+	 *               	to Job filters to create.
+	 *
+	 * @throws UWSException	If the value of at least one AFTER, PHASE or LAST
+	 *                     	parameter is incorrect.
+	 */
+	public JobListRefiner(final HttpServletRequest request) throws UWSException{
+		String pName;
+		String[] values;
+
+		ExecutionPhase phase = null;
+		Date afterDate = null;
+		int last = -1;
+
+		AfterFilter afterFilter = null;
+		PhasesFilter phasesFilter = null;
+
+		/* *************************************************** */
+		/* Identify all filters inside the HTTP-GET parameters */
+
+		Enumeration<String> paramNames = request.getParameterNames();
+		while(paramNames.hasMoreElements()){
+			pName = paramNames.nextElement();
+			values = request.getParameterValues(pName);
+			// Case: PHASE (case INsensitively):
+			if (pName.toUpperCase().equals("PHASE")){
+				for(String p : values){
+					if (p != null){
+						try{
+							// resolve the execution phase:
+							phase = ExecutionPhase.valueOf(p.toUpperCase());
+							// add the phase into the PhasesFilter:
+							if (phasesFilter == null)
+								phasesFilter = new PhasesFilter(phase);
+							else
+								phasesFilter.add(phase);
+						}catch(IllegalArgumentException iae){
+							throw new UWSException(UWSException.BAD_REQUEST, "Incorrect PHASE value: \"" + p + "\"! No such execution phase is known by this service.");
+						}
+					}
+				}
+			}else if (pName.toUpperCase().equals("AFTER")){
+				for(String p : values){
+					if (p != null){
+						try{
+							// resolve date:
+							afterDate = ISO8601Format.parseToDate(p);
+							/* create/replace the AfterFilter if the date is
+							 * more recent: */
+							if (afterFilter == null || afterDate.after(afterFilter.getDate()))
+								afterFilter = new AfterFilter(afterDate);
+						}catch(ParseException pe){
+							throw new UWSException(UWSException.BAD_REQUEST, "Incorrect AFTER value: \"" + p + "\"! The date must be formatted in ISO-8601.");
+						}
+					}
+				}
+			}else if (pName.toUpperCase().equals("LAST")){
+				for(String p : values){
+					if (p != null){
+						try{
+							// resolve the number of jobs to fetch:
+							last = Integer.parseInt(p);
+							/* update the last counter (the value is updated
+							 * only if the new value is positive and smaller): */
+							if (last >= 0 && (topSize < 0 || last < topSize))
+								topSize = last;
+							else if (last < 0)
+								throw new UWSException(UWSException.BAD_REQUEST, "Incorrect LAST value: \"" + p + "\"! A positive integer was expected.");
+						}catch(NumberFormatException nfe){
+							throw new UWSException(UWSException.BAD_REQUEST, "Incorrect LAST value: \"" + p + "\"! A positive integer was expected.");
+						}
+					}
+				}
+			}
+		}
+
+		/* ************************ */
+		/* Append all found filters */
+
+		/* Set the PHASES filter (if no filter is specified, a default one
+		 * forbidding ARCHIVED jobs is set): */
+		if (phasesFilter != null)
+			filters.add(phasesFilter);
+		else
+			filters.add(new NoArchivedFilter());
+
+		// Set the AFTER filter:
+		if (afterFilter != null)
+			filters.add(afterFilter);
+
+		// Set the LAST filter:
+		if (topSize >= 0){
+			/* jobs are sorted by descending creation-time (so that only the
+			 * topSize first can be easily read): */
+			sortComp = new JobComparator();
+			/* the topSize most recently jobs must be returned in descending creation-time,
+			 * so the order of jobs set by the sortComp comparator must NOT be reversed: */
+			reverseOrder = false;
+		}
+
+	}
+
+	/**
+	 * Add the given job in the temporary internal list of filtered jobs by
+	 * preserving the specified sorting.
+	 *
+	 * <p>
+	 * 	This function added the job at the end of the list IF no sort is
+	 * 	required. Otherwise, a binary search is performed and the job is added
+	 * 	at the right place in the list so that keeping the list sorted.
+	 * </p>
+	 *
+	 * @param job	The job to keep in the display-able job list.
+	 */
+	protected final void addJob(final UWSJob job){
+		if (job == null)
+			return;
+
+		if (sortComp == null)
+			jobList.add(job);
+		else{
+			int index = Collections.binarySearch(jobList, job, sortComp);
+			if (index < 0)
+				index = -(index + 1);
+			jobList.add(index, job);
+		}
+	}
+
+	/**
+	 * Filter (and eventually sort and/or limit in size) the given list of jobs.
+	 *
+	 * @param jobList	Job list to filter.
+	 *
+	 * @return	The filtered (and eventually sorted/limited) job list.
+	 */
+	@SuppressWarnings("rawtypes")
+	public Iterator<UWSJob> refine(final Iterator<UWSJob> jobList){
+		// Remove all items of the last filtering result:
+		if (this.jobList instanceof AbstractList)
+			((AbstractList)this.jobList).clear();
+		else{
+			while(!this.jobList.isEmpty())
+				this.jobList.remove(0);
+		}
+
+		// Filters the given jobs with the simple job filters:
+		UWSJob job;
+		while(jobList.hasNext()){
+			job = jobList.next();
+
+			// Apply the job filters on this job and retain it if it passes them:
+			if (match(job))
+				addJob(job);	// if a sort must be done, it is performed here by #addJob(UWSJob)
+		}
+
+		// Return an iterator on this whole filtered job list:
+		if (topSize < 0)
+			return this.jobList.iterator();
+
+		// OR Return an iterator on the topSize first jobs (in the current order or reverse):
+		else
+			return new TopIterator(this.jobList, topSize, reverseOrder);
+	}
+
+	/**
+	 * Tell whether the given job matches all the job filters.
+	 *
+	 * <p>
+	 * 	In other words, this function operates a logical AND between all listed
+	 * 	filters.
+	 * </p>
+	 *
+	 * <p><i>Note:
+	 * 	If the given job is NULL, <code>false</code> will be returned.
+	 * 	In case of error while evaluating one of the filters on the given job,
+	 * 	<code>false</code> will be returned as well.
+	 * </i></p>
+	 *
+	 * @param job	A job to filter.
+	 *
+	 * @return	<code>true</code> if the job matches all the filters,
+	 *        	<code>false</code> otherwise.
+	 */
+	protected final boolean match(final UWSJob job){
+		if (job == null)
+			return false;
+
+		for(JobFilter filter : filters){
+			if (!filter.match(job))
+				return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Compare the 2 given {@link UWSJob} instances by using only their creation
+	 * date/time. The most recently created job is considered as inferior. So,
+	 * this comparator aims to sort jobs by descending creation date/time.
+	 *
+	 * <p><i><b>WARNING!</b>
+	 * 	It must be ensured that all compared jobs have always a NOT-NULL
+	 * 	creationTime attribute. Otherwise this comparator may fail or return an
+	 * 	incorrect value.
+	 * </i></p>
+	 *
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 4.3 (10/2017)
+	 * @since 4.3
+	 */
+	public final static class JobComparator implements Comparator<UWSJob> {
+		@Override
+		public int compare(UWSJob o1, UWSJob o2){
+			return -(o1.getCreationTime().compareTo(o2.getCreationTime()));
+		}
+	}
+
+	/**
+	 * This iterator is designed to return just the N first items of the given
+	 * list.
+	 *
+	 * <p>
+	 * 	It is also possible to inverse the order of these N first items by setting
+	 * 	the last constructor parameter to <code>true</code>.
+	 * </p>
+	 *
+	 * <p><i>Note:
+	 * 	This iterator does not support the remove operation ;
+	 * 	the function {@link #remove()} will then return an
+	 * 	{@link UnsupportedOperationException}.
+	 * </i></p>
+	 *
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 4.3 (10/2017)
+	 * @since 4.3
+	 */
+	protected final static class TopIterator implements Iterator<UWSJob> {
+
+		/** Jobs list on which this iterator must iterate. */
+		private final List<UWSJob> list;
+		/** The number of items that must be read by this iterator. */
+		private final int topSize;
+		/** Indicate whether the topSize items must be read in the given order
+		 * or in the reverse one. */
+		private final boolean reverseOrder;
+
+		/** Index of the last read item. */
+		private int currentIndex = -1;
+		/** Number of read items. */
+		private int count = 0;
+
+		/**
+		 * Create an iterator which will read the <code>topSize</code> first
+		 * item of the given list.
+		 *
+		 * @param joblist	List of jobs to return.
+		 * @param topSize	Number of items to read from the beginning of the
+		 *               	list.
+		 * @param reverse	<code>true</code> if the <code>topSize</code> first
+		 *               	items must be read in the given order,
+		 *               	<code>false</code> if they must be read in the
+		 *               	reverse order.
+		 */
+		public TopIterator(final List<UWSJob> joblist, final int topSize, final boolean reverse){
+			this.list = joblist;
+			this.topSize = topSize;
+			this.reverseOrder = reverse;
+
+			if (reverseOrder && topSize >= 0)
+				currentIndex = (list.size() <= topSize) ? list.size() : topSize;
+			else
+				currentIndex = reverseOrder ? list.size() : -1;
+
+			count = 0;
+		}
+
+		@Override
+		public boolean hasNext(){
+			return (topSize < 0 || count + 1 <= topSize) && (reverseOrder ? currentIndex - 1 >= 0 : currentIndex + 1 < list.size());
+		}
+
+		@Override
+		public UWSJob next(){
+			if (!hasNext())
+				throw new NoSuchElementException("No more jobs in this filtered job list!");
+
+			count++;
+			currentIndex = reverseOrder ? currentIndex - 1 : currentIndex + 1;
+
+			return list.get(currentIndex);
+		}
+
+		@Override
+		public void remove(){
+			throw new UnsupportedOperationException("No remove operation possible on this iterator of filtered job list!");
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/job/serializer/filter/NoArchivedFilter.java b/src/uws/job/serializer/filter/NoArchivedFilter.java
new file mode 100644
index 0000000..688f9e1
--- /dev/null
+++ b/src/uws/job/serializer/filter/NoArchivedFilter.java
@@ -0,0 +1,39 @@
+package uws.job.serializer.filter;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+
+/**
+ * Job filter which allows all jobs except those in ARCHIVED phase.
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (10/2017)
+ * @since 4.3
+ */
+public class NoArchivedFilter implements JobFilter {
+
+	@Override
+	public boolean match(final UWSJob job){
+		return (job != null) && (job.getPhase() != ExecutionPhase.ARCHIVED);
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/job/serializer/filter/PhasesFilter.java b/src/uws/job/serializer/filter/PhasesFilter.java
new file mode 100644
index 0000000..dd2aa76
--- /dev/null
+++ b/src/uws/job/serializer/filter/PhasesFilter.java
@@ -0,0 +1,99 @@
+package uws.job.serializer.filter;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+
+/**
+ * Job filter based on the execution phase.
+ *
+ * <p>
+ * 	Only jobs that are in one of the execution phases listed in this filter are
+ * 	kept.
+ * </p>
+ *
+ * <p>
+ * 	The constructor of this filter requires exactly one execution phase.
+ * 	But obviously more phases can be added to the list of accepted execution
+ * 	phases by using the function {@link #add(ExecutionPhase)}.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.3 (10/2017)
+ * @since 4.3
+ */
+public final class PhasesFilter implements JobFilter {
+
+	/** List of execution phases in which jobs to keep must be. */
+	protected final List<ExecutionPhase> phases = new ArrayList<ExecutionPhase>();
+
+	/**
+	 * Build a {@link PhasesFilter} which will retain only jobs in the given
+	 * execution phase.
+	 *
+	 * <p>
+	 * 	More phases can be added into the list of accepted phases thanks to the
+	 * 	function {@link #add(ExecutionPhase)}.
+	 * </p>
+	 *
+	 * @param phase	An execution phase on which jobs to retain must be.
+	 *
+	 * @throws NullPointerException	If the given phase is NULL.
+	 */
+	public PhasesFilter(final ExecutionPhase phase) throws NullPointerException{
+		if (phase == null)
+			throw new NullPointerException("Missing execution phase! Can not ceate a PhasesFilter without at least one execution phase.");
+
+		phases.add(phase);
+	}
+
+	/**
+	 * Add the given phase in the list of accepted phases.
+	 *
+	 * <p><i>Note:
+	 * 	The given phase is not added into the list if it is already inside.
+	 * </i></p>
+	 *
+	 * @param phase	An execution phase to add inside the list of accepted
+	 *             	phases.
+	 */
+	public void add(final ExecutionPhase phase){
+		if (phase != null && !phases.contains(phase))
+			phases.add(phase);
+	}
+
+	@Override
+	public boolean match(final UWSJob job){
+		if (job == null)
+			return false;
+
+		for(ExecutionPhase p : phases){
+			if (job.getPhase() == p)
+				return true;
+		}
+
+		return false;
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/service/UWSServlet.java b/src/uws/service/UWSServlet.java
index 60355f6..e2015fb 100644
--- a/src/uws/service/UWSServlet.java
+++ b/src/uws/service/UWSServlet.java
@@ -57,6 +57,7 @@ import uws.job.parameters.UWSParameters;
 import uws.job.serializer.JSONSerializer;
 import uws.job.serializer.UWSSerializer;
 import uws.job.serializer.XMLSerializer;
+import uws.job.serializer.filter.JobListRefiner;
 import uws.job.user.JobOwner;
 import uws.service.actions.UWSAction;
 import uws.service.backup.UWSBackupManager;
@@ -150,7 +151,7 @@ import uws.service.request.UploadFile;
  * </p>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (10/2017)
  */
 public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory {
 	private static final long serialVersionUID = 1L;
@@ -536,7 +537,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		resp.setContentType(serializer.getMimeType());
 		resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING);
 		try{
-			jobsList.serialize(resp.getOutputStream(), serializer, user);
+			jobsList.serialize(resp.getOutputStream(), serializer, user, new JobListRefiner(req));
 		}catch(Exception e){
 			if (!(e instanceof UWSException)){
 				getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize the jobs list \"" + jobsList.getName() + "\"!", e);
diff --git a/src/uws/service/actions/ListJobs.java b/src/uws/service/actions/ListJobs.java
index 2ce5c85..57f7947 100644
--- a/src/uws/service/actions/ListJobs.java
+++ b/src/uws/service/actions/ListJobs.java
@@ -2,21 +2,21 @@ package uws.service.actions;
 
 /*
  * This file is part of UWSLibrary.
- * 
+ *
  * UWSLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * UWSLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *
+ * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -30,21 +30,27 @@ import uws.UWSException;
 import uws.UWSToolBox;
 import uws.job.JobList;
 import uws.job.serializer.UWSSerializer;
+import uws.job.serializer.filter.JobListRefiner;
 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>
- * 
- * <p><i><u>Note:</u> The corresponding name is {@link UWSAction#LIST_JOBS}.</i></p>
- * 
- * <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>
- * 
+ * The "List Jobs" action of a UWS.
+ *
+ * <p><i>Note:
+ * 	The corresponding name is {@link UWSAction#LIST_JOBS}.
+ * </i></p>
+ *
+ * <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}
+ * 	chosen in function of the HTTP Accept header.
+ * </p>
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (04/2015)
+ * @version 4.3 (10/2017)
  */
 public class ListJobs extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -70,11 +76,12 @@ public class ListJobs extends UWSAction {
 	/**
 	 * Checks whether:
 	 * <ul>
-	 * 	<li>a job list name is specified in the given UWS URL <i>(<u>note:</u> the existence of the jobs list is not checked)</i>,</li>
+	 * 	<li>a job list name is specified in the given UWS URL
+	 * 	    <i>(<u>note:</u> the existence of the jobs list is not checked)</i>,</li>
 	 * 	<li>the UWS URL does not make a reference to a job (so: no job ID),</li>
 	 * 	<li>the HTTP method is HTTP-GET.</li>
 	 * </ul>
-	 * 
+	 *
 	 * @see uws.service.actions.UWSAction#match(UWSUrl, JobOwner, HttpServletRequest)
 	 */
 	@Override
@@ -84,12 +91,14 @@ public class ListJobs extends UWSAction {
 
 	/**
 	 * Gets the specified jobs list <i>(and throw an error if not found)</i>,
-	 * chooses the serializer and write the serialization of the jobs list in the given response.
-	 * 
+	 * chooses the serializer and write the serialization of the jobs list in
+	 * the given response.
+	 *
 	 * @see #getJobsList(UWSUrl)
+	 * @see JobListRefiner#JobListRefiner(HttpServletRequest)
 	 * @see UWSService#getSerializer(String)
-	 * @see JobList#serialize(ServletOutputStream, UWSSerializer, JobOwner)
-	 * 
+	 * @see JobList#serialize(ServletOutputStream, UWSSerializer, JobOwner, JobListRefiner)
+	 *
 	 * @see uws.service.actions.UWSAction#apply(UWSUrl, JobOwner, HttpServletRequest, HttpServletResponse)
 	 */
 	@Override
@@ -102,7 +111,7 @@ public class ListJobs extends UWSAction {
 		response.setContentType(serializer.getMimeType());
 		response.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING);
 		try{
-			jobsList.serialize(response.getOutputStream(), serializer, user);
+			jobsList.serialize(response.getOutputStream(), serializer, user, new JobListRefiner(request));
 		}catch(Exception e){
 			if (!(e instanceof UWSException)){
 				getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "SERIALIZE", "Can not serialize the jobs list \"" + jobsList.getName() + "\"!", e);
diff --git a/test/uws/job/serializer/filter/TestAfterFilter.java b/test/uws/job/serializer/filter/TestAfterFilter.java
new file mode 100644
index 0000000..fe6cc9c
--- /dev/null
+++ b/test/uws/job/serializer/filter/TestAfterFilter.java
@@ -0,0 +1,52 @@
+package uws.job.serializer.filter;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.junit.Test;
+
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+
+public class TestAfterFilter {
+
+	@Test
+	public void testMatch(){
+		GregorianCalendar cal = new GregorianCalendar(2010, 3, 1, 1, 0, 0);
+
+		JobFilter filter = new AfterFilter(cal.getTime());
+
+		// 1 second after => OK!
+		cal.set(2010, 3, 1, 1, 0, 1);
+		UWSJob testJob = new UWSJob("123456", cal.getTimeInMillis(), null, new UWSParameters(), -1, -1, -1, null, null);
+		assertTrue(filter.match(testJob));
+
+		// Now => OK!
+		testJob = new UWSJob("123456", (new Date()).getTime(), null, new UWSParameters(), -1, (new Date()).getTime(), -1, null, null);
+		assertTrue(filter.match(testJob));
+
+		// Exactly same date => Nop!
+		cal.set(2010, 3, 1, 1, 0, 0);
+		testJob = new UWSJob("123456", cal.getTimeInMillis(), null, new UWSParameters(), -1, cal.getTimeInMillis(), -1, null, null);
+		assertFalse(filter.match(testJob));
+
+		// 1 second before => Nop!
+		cal.set(2010, 3, 1, 0, 59, 59);
+		testJob = new UWSJob("123456", cal.getTimeInMillis(), null, new UWSParameters(), -1, cal.getTimeInMillis(), -1, null, null);
+		assertFalse(filter.match(testJob));
+
+		// No start time => does not matter...we are working on the creationTime now
+		/* Note: this test is here just to ensure we are effectively working on the creationTime! */
+		testJob = new UWSJob("123456", cal.getTimeInMillis(), null, new UWSParameters(), -1, -1, -1, null, null);
+		assertFalse(filter.match(testJob));
+		testJob = new UWSJob("123456", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null);
+		assertTrue(filter.match(testJob));
+
+		// No job => Nop!
+		assertFalse(filter.match(null));
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/job/serializer/filter/TestJobListRefiner.java b/test/uws/job/serializer/filter/TestJobListRefiner.java
new file mode 100644
index 0000000..f9bbad3
--- /dev/null
+++ b/test/uws/job/serializer/filter/TestJobListRefiner.java
@@ -0,0 +1,1085 @@
+package uws.job.serializer.filter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.junit.Test;
+
+import uws.ISO8601Format;
+import uws.UWSException;
+import uws.job.ErrorSummary;
+import uws.job.ErrorType;
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+import uws.job.serializer.filter.JobListRefiner.TopIterator;
+
+public class TestJobListRefiner {
+
+	@Test
+	public void testJobListRefiner(){
+		TestHttpServletRequest request = new TestHttpServletRequest();
+		JobListRefiner filter;
+		try{
+			// NO PARAMS => Nothing set!
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+
+			// A NON FILTER PARAMETER => Nothing set!
+			request.addParams("Nothing", "Blabla");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+		}catch(UWSException ue){
+			ue.printStackTrace(System.err);
+			fail("No error should happen if no LAST, PHASE or AFTER parameter is provided.");
+		}
+
+		/* ************* */
+		/* FILTER: AFTER */
+
+		// INCORRECT AFTER PARAMETER => Nothing set and no error!
+		try{
+			request.clearParams();
+			request.addParams("AFTER", "foo");
+			filter = new JobListRefiner(request);
+			fail("The provided parameter AFTER is NOT AT ALL an ISO-8601 date. An error should have occurred.");
+		}catch(UWSException ue){
+			assertEquals("Incorrect AFTER value: \"foo\"! The date must be formatted in ISO-8601.", ue.getMessage());
+			assertEquals(UWSException.BAD_REQUEST, ue.getHttpErrorCode());
+		}
+
+		try{
+			// CORRECT AFTER PARAMETER => an AfterFilter should be set!
+			// With just a full date:
+			request.clearParams();
+			request.addParams("AFTER", "2015-01-01");
+			filter = new JobListRefiner(request);
+			assertEquals(2, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(AfterFilter.class, filter.filters.get(1).getClass());
+			assertEquals("2015-01-01T00:00:00Z", ISO8601Format.format(((AfterFilter)filter.filters.get(1)).getDate()));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+			request.clearParams();
+			// With a full date and time:
+			request.addParams("AFTER", "2015-01-01T12:00:00");
+			filter = new JobListRefiner(request);
+			assertEquals(2, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(AfterFilter.class, filter.filters.get(1).getClass());
+			assertEquals("2015-01-01T12:00:00Z", ISO8601Format.format(((AfterFilter)filter.filters.get(1)).getDate()));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+
+			// CORRECT 3 AFTER PARAMETERS => a single AfterFilter with the most recent date should be set!
+			request.clearParams();
+			request.addParams("AFTER", "2014-01-01T12:00:00");
+			request.addParams("after", "2015-01-30T12:00:00");
+			request.addParams("AFTER", "2015-01-01T12:00:00");
+			filter = new JobListRefiner(request);
+			assertEquals(2, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(AfterFilter.class, filter.filters.get(1).getClass());
+			assertEquals("2015-01-30T12:00:00Z", ISO8601Format.format(((AfterFilter)filter.filters.get(1)).getDate()));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+		}catch(UWSException ue){
+			ue.printStackTrace(System.err);
+			fail("No error should happen since all provided AFTER parameters are correct.");
+		}
+
+		/* ************ */
+		/* FILTER: LAST */
+
+		// 1 INCORRECT LAST PARAMETER => nothing should be set!
+		try{
+			request.clearParams();
+			request.addParams("LAST", "-10");
+			filter = new JobListRefiner(request);
+			fail("The provided parameter LAST is NOT positive. An error should have occurred.");
+		}catch(UWSException ue){
+			assertEquals("Incorrect LAST value: \"-10\"! A positive integer was expected.", ue.getMessage());
+			assertEquals(UWSException.BAD_REQUEST, ue.getHttpErrorCode());
+		}
+		try{
+			request.clearParams();
+			request.addParams("LAST", "foo");
+			filter = new JobListRefiner(request);
+			fail("The provided parameter LAST is NOT an integer. An error should have occurred.");
+		}catch(UWSException ue){
+			assertEquals("Incorrect LAST value: \"foo\"! A positive integer was expected.", ue.getMessage());
+			assertEquals(UWSException.BAD_REQUEST, ue.getHttpErrorCode());
+		}
+
+		try{
+			// CORRECT 1 LAST PARAMETER => topSize, sortComp and reverseOrder should be set!
+			request.clearParams();
+			request.addParams("LAST", "10");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(10, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNotNull(filter.sortComp);
+			assertEquals(JobListRefiner.JobComparator.class, filter.sortComp.getClass());
+
+			// SPECIAL CASE OF LAST=0 => same behavior as for a positive value
+			request.clearParams();
+			request.addParams("LAST", "0");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(0, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNotNull(filter.sortComp);
+			assertEquals(JobListRefiner.JobComparator.class, filter.sortComp.getClass());
+
+			// CORRECT 3 LAST PARAMETERS => Only the smallest value should be kept ; a StartedFilter should be set, as well as topSize, sortComp and reverseOrder!
+			request.clearParams();
+			request.addParams("LAST", "10");
+			request.addParams("last", "5");
+			request.addParams("LAST", "7");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(NoArchivedFilter.class, filter.filters.get(0).getClass());
+			assertEquals(5, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNotNull(filter.sortComp);
+			assertEquals(JobListRefiner.JobComparator.class, filter.sortComp.getClass());
+		}catch(UWSException ue){
+			ue.printStackTrace(System.err);
+			fail("No error should happen since all provided LAST parameters are correct.");
+		}
+
+		/* ************* */
+		/* FILTER: PHASE */
+
+		// 1 INCORRECT PHASE PARAMETER => a NoArchivedFilter should be set!
+		try{
+			request.clearParams();
+			request.addParams("PHASE", "foo");
+			filter = new JobListRefiner(request);
+			fail("The provided parameter PHASE is NOT a valid execution phase. An error should have occurred.");
+		}catch(UWSException ue){
+			assertEquals("Incorrect PHASE value: \"foo\"! No such execution phase is known by this service.", ue.getMessage());
+			assertEquals(UWSException.BAD_REQUEST, ue.getHttpErrorCode());
+		}
+
+		// CORRECT 1 CORRECT + 1 INCORRECT PHASE PARAMETER => a PhasesFilter with only the correct phase should be set!
+		try{
+			request.clearParams();
+			request.addParams("PHASE", "foo");
+			request.addParams("phase", "EXECUTING");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(PhasesFilter.class, filter.filters.get(0).getClass());
+			assertEquals(1, ((PhasesFilter)filter.filters.get(0)).phases.size());
+			assertEquals(ExecutionPhase.EXECUTING, ((PhasesFilter)filter.filters.get(0)).phases.get(0));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+		}catch(UWSException ue){
+			assertEquals("Incorrect PHASE value: \"foo\"! No such execution phase is known by this service.", ue.getMessage());
+			assertEquals(UWSException.BAD_REQUEST, ue.getHttpErrorCode());
+		}
+
+		try{
+			// CORRECT PHASE PARAMETER => a PhasesFilter should be set!
+			request.clearParams();
+			request.addParams("PHASE", "EXECUTING");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(PhasesFilter.class, filter.filters.get(0).getClass());
+			assertEquals(1, ((PhasesFilter)filter.filters.get(0)).phases.size());
+			assertEquals(ExecutionPhase.EXECUTING, ((PhasesFilter)filter.filters.get(0)).phases.get(0));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+
+			// CORRECT 2 CORRECT PHASE PARAMETERS => a PhasesFilter with the two correct phases should be set!
+			request.clearParams();
+			request.addParams("PHASE", "QUEUED");
+			request.addParams("phase", "EXECUTING");
+			filter = new JobListRefiner(request);
+			assertEquals(1, filter.filters.size());
+			assertEquals(PhasesFilter.class, filter.filters.get(0).getClass());
+			assertEquals(2, ((PhasesFilter)filter.filters.get(0)).phases.size());
+			assertEquals(ExecutionPhase.QUEUED, ((PhasesFilter)filter.filters.get(0)).phases.get(0));
+			assertEquals(ExecutionPhase.EXECUTING, ((PhasesFilter)filter.filters.get(0)).phases.get(1));
+			assertEquals(-1, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNull(filter.sortComp);
+		}catch(UWSException ue){
+			ue.printStackTrace(System.err);
+			fail("No error should happen since all provided PHASE parameters are correct.");
+		}
+
+		/* *********** */
+		/* FILTER: ALL */
+
+		// ALL MIXED PARAMETERS
+		try{
+			request.clearParams();
+			request.addParams("last", "5");
+			request.addParams("phase", "EXECUTING");
+			request.addParams("AFTER", "2015-02-10T12:00:00");
+			request.addParams("AFTER", "2013-01-10T12:00:00");
+			filter = new JobListRefiner(request);
+			assertEquals(2, filter.filters.size());
+			assertEquals(PhasesFilter.class, filter.filters.get(0).getClass());
+			assertEquals(1, ((PhasesFilter)filter.filters.get(0)).phases.size());
+			assertEquals(ExecutionPhase.EXECUTING, ((PhasesFilter)filter.filters.get(0)).phases.get(0));
+			assertEquals(AfterFilter.class, filter.filters.get(1).getClass());
+			assertEquals("2015-02-10T12:00:00Z", ISO8601Format.format(((AfterFilter)filter.filters.get(1)).getDate()));
+			assertEquals(5, filter.topSize);
+			assertFalse(filter.reverseOrder);
+			assertNotNull(filter.sortComp);
+		}catch(UWSException ue){
+			ue.printStackTrace(System.err);
+			fail("No error should happen since all provided PHASE, LAST and AFTER parameters are correct.");
+		}
+	}
+
+	@Test
+	public void testFilter(){
+		ArrayList<UWSJob> jobs = new ArrayList<UWSJob>(10);
+		try{
+			// 0 -> PENDING
+			UWSJob test = new UWSJob("0", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null);
+			jobs.add(test);
+			// 1 -> QUEUED
+			test = new UWSJob("1", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null);
+			test.setPhase(ExecutionPhase.QUEUED, true);
+			jobs.add(test);
+			// 2 -> ABORTED
+			test = new UWSJob("2", (new GregorianCalendar(2010, 2, 1)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2010, 2, 1)).getTimeInMillis(), -1, null, null);
+			test.setPhase(ExecutionPhase.ABORTED, true);
+			jobs.add(test);
+			// 3 -> ARCHIVED
+			test = new UWSJob("3", (new GregorianCalendar(2010, 1, 12)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2010, 1, 12)).getTimeInMillis(), (new GregorianCalendar(2010, 1, 13)).getTimeInMillis(), null, null);
+			test.setPhase(ExecutionPhase.ARCHIVED, true);
+			jobs.add(test);
+			// 4 -> EXECUTING
+			test = new UWSJob("4", (new GregorianCalendar(2015, 2, 2)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2015, 2, 2)).getTimeInMillis(), -1, null, null);
+			test.setPhase(ExecutionPhase.EXECUTING, true);
+			jobs.add(test);
+			// 5 -> EXECUTING
+			test = new UWSJob("5", (new GregorianCalendar(2014, 8, 2)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2014, 8, 2)).getTimeInMillis(), -1, null, null);
+			test.setPhase(ExecutionPhase.EXECUTING, true);
+			jobs.add(test);
+			// 6 -> ERROR
+			test = new UWSJob("6", (new GregorianCalendar(2015, 3, 3)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2015, 3, 3)).getTimeInMillis(), (new GregorianCalendar(2015, 9, 4)).getTimeInMillis(), null, new ErrorSummary("", ErrorType.FATAL));
+			test.setPhase(ExecutionPhase.ERROR, true);
+			jobs.add(test);
+			// 7 -> ERROR
+			test = new UWSJob("7", (new GregorianCalendar(2015, 8, 3)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2015, 8, 3)).getTimeInMillis(), (new GregorianCalendar(2015, 9, 4)).getTimeInMillis(), null, new ErrorSummary("", ErrorType.FATAL));
+			test.setPhase(ExecutionPhase.EXECUTING, true);
+			jobs.add(test);
+			// 8 -> ERROR
+			test = new UWSJob("8", (new GregorianCalendar(2015, 2, 3)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2015, 2, 3)).getTimeInMillis(), (new GregorianCalendar(2015, 9, 4)).getTimeInMillis(), null, new ErrorSummary("", ErrorType.FATAL));
+			test.setPhase(ExecutionPhase.EXECUTING, true);
+			jobs.add(test);
+			// 9 -> ERROR
+			test = new UWSJob("9", (new GregorianCalendar(2015, 1, 3)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2015, 1, 3)).getTimeInMillis(), (new GregorianCalendar(2015, 9, 4)).getTimeInMillis(), null, new ErrorSummary("", ErrorType.FATAL));
+			test.setPhase(ExecutionPhase.EXECUTING, true);
+			jobs.add(test);
+		}catch(UWSException ex){
+			ex.printStackTrace();
+			fail("Can not force the execution phase of the job! (see console for more details)");
+		}
+
+		/* ****************************** */
+		/* No filter, no sort, no reverse */
+		JobListRefiner filter = new JobListRefiner();
+
+		Iterator<UWSJob> it = filter.refine(jobs.iterator());
+		for(int i = 0; i < 10; i++){
+			assertTrue(it.hasNext());
+			assertEquals("" + i, it.next().getJobId());
+		}
+		assertFalse(it.hasNext());
+
+		/* ************************************** */
+		/* 1 PHASE filter, no sort, no reverse */
+		PhasesFilter pFilter = new PhasesFilter(ExecutionPhase.EXECUTING);
+		pFilter.add(ExecutionPhase.ARCHIVED);
+		filter.filters.add(pFilter);
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("3", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("5", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("9", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* **************************************************** */
+		/* 1 PHASE filter + 1 AFTER filter, no sort, no reverse */
+		filter.filters.add(new AfterFilter((new GregorianCalendar(2015, 1, 1)).getTime()));
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("9", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* ***************************************************************** */
+		/* 1 PHASE filter + 1 AFTER filter, 3 first jobs, no sort, no reverse */
+		filter.jobList.clear();
+		filter.topSize = 3;
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* ************************************************************************ */
+		/* 1 PHASE filter + 1 AFTER filter, all jobs, sort by startTime, no reverse */
+		filter.topSize = -1;
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return o1.getStartTime().compareTo(o2.getStartTime());
+			}
+		};
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("9", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* ********************************************************************************** */
+		/* 1 PHASE filter + 1 AFTER filter, the 4 first jobs, sort by startTime, WITH reverse */
+		filter.topSize = 4;
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return o1.getStartTime().compareTo(o2.getStartTime());
+			}
+		};
+		filter.reverseOrder = true;
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("9", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* ************************************************************************************************************ */
+		/* 1 PHASE filter + 1 AFTER filter, 3 last jobs, sort by descending creationTime => simulation of LAST "filter" */
+		filter.topSize = 3;
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return -(o1.getCreationTime().compareTo(o2.getCreationTime()));
+			}
+		};
+		filter.reverseOrder = false;
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("8", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* ***************************************************************************************************************** */
+		/* No filter (except to avoid ARCHIVED), 3 last jobs, sort by descending creationTime => simulation of LAST "filter" */
+		filter.filters.clear();
+		filter.filters.add(new NoArchivedFilter());
+		filter.topSize = 3;
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return -(o1.getCreationTime().compareTo(o2.getCreationTime()));
+			}
+		};
+		filter.reverseOrder = false;
+
+		it = filter.refine(jobs.iterator());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("7", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		/* *********************************************************************************************************************************************** */
+		/* No filter (except to avoid ARCHIVED), 0 last jobs, sort by descending creationTime => simulation of LAST "filter" WITH THE SPECIAL VALUE LAST=0 */
+		filter.filters.clear();
+		filter.filters.add(new NoArchivedFilter());
+		filter.topSize = 0;
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return -(o1.getCreationTime().compareTo(o2.getCreationTime()));
+			}
+		};
+		filter.reverseOrder = false;
+
+		it = filter.refine(jobs.iterator());
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void testAddJob(){
+		JobListRefiner filter = new JobListRefiner();
+		filter.sortComp = new Comparator<UWSJob>(){
+			@Override
+			public int compare(UWSJob o1, UWSJob o2){
+				return o1.getCreationTime().compareTo(o2.getCreationTime());
+			}
+		};
+
+		filter.addJob(new UWSJob("123456", (new GregorianCalendar(2013, 3, 10)).getTimeInMillis(), null, new UWSParameters(), -1, -1, -1, null, null));
+		filter.addJob(new UWSJob("654321", (new GregorianCalendar(2010, 3, 10)).getTimeInMillis(), null, new UWSParameters(), -1, -1, -1, null, null));
+
+		assertEquals("654321", filter.jobList.get(0).getJobId());
+		assertEquals("123456", filter.jobList.get(1).getJobId());
+	}
+
+	@Test
+	public void testMatch(){
+		JobListRefiner filter = new JobListRefiner();
+
+		/* *********** */
+		/* No filter */
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+		// A job => Yes!
+		assertTrue(filter.match(new UWSJob(new UWSParameters())));
+
+		/* ********************************************** */
+		/* Only jobs in EXECUTING phase */
+		filter.filters.add(new PhasesFilter(ExecutionPhase.EXECUTING));
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+		// Not an EXECUTING job => Nope!
+		assertFalse(filter.match(new UWSJob(new UWSParameters())));
+
+		UWSJob testJob = new UWSJob("123456", (new Date()).getTime(), null, new UWSParameters(), -1, (new Date()).getTime(), -1, null, null);
+		try{
+			// EXECUTING job => OK!
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+
+			// ARCHIVED job => Nope!
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertFalse(filter.match(testJob));
+
+			// ERROR job => Nope!
+			testJob.setPhase(ExecutionPhase.ERROR, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException ex){
+			ex.printStackTrace();
+			fail("Can not force the execution phase of the job! (see console for more details)");
+		}
+
+		/* ********************************************************** */
+		/* Only jobs in EXECUTING or ARCHIVED phase */
+		((PhasesFilter)filter.filters.get(filter.filters.size() - 1)).add(ExecutionPhase.ARCHIVED);
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+		// Not started job => Nope!
+		assertFalse(filter.match(new UWSJob(new UWSParameters())));
+
+		testJob = new UWSJob("123456", (new Date()).getTime(), null, new UWSParameters(), -1, (new Date()).getTime(), -1, null, null);
+		try{
+			// EXECUTING job => OK!
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+
+			// ARCHIVED job => OK!
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertTrue(filter.match(testJob));
+
+			// ERROR job => Nope!
+			testJob.setPhase(ExecutionPhase.ERROR, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException ex){
+			ex.printStackTrace();
+			fail("Can not force the execution phase of the job! (see console for more details)");
+		}
+
+		/* ***************************************************************************** */
+		/* Only jobs in EXECUTING or ARCHIVED phase AND after 1/3/2010 */
+		filter.filters.add(new AfterFilter((new GregorianCalendar(2010, 2, 1)).getTime()));
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+		// Not started job => Nope!
+		assertFalse(filter.match(new UWSJob(new UWSParameters())));
+
+		testJob = new UWSJob("123456", (new GregorianCalendar(2010, 2, 2)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2010, 2, 2)).getTimeInMillis(), -1, null, null);
+		try{
+			// EXECUTING job, and after 1/3/2010 => OK!
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+
+			// ARCHIVED job, and after 1/3/2010 => OK!
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertTrue(filter.match(testJob));
+
+			// ERROR job, and after 1/3/2010 => Nope!
+			testJob.setPhase(ExecutionPhase.ERROR, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException ex){
+			ex.printStackTrace();
+			fail("Can not force the execution phase of the job! (see console for more details)");
+		}
+
+		testJob = new UWSJob("123456", (new GregorianCalendar(2010, 1, 1)).getTimeInMillis(), null, new UWSParameters(), -1, (new GregorianCalendar(2010, 1, 1)).getTimeInMillis(), -1, null, null);
+		try{
+			// EXECUTING job, and before 1/3/2010 => Nope!
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertFalse(filter.match(testJob));
+
+			// ARCHIVED job, and before 1/3/2010 => Nope!
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertFalse(filter.match(testJob));
+
+			// ERROR job, and before 1/3/2010 => Nope!
+			testJob.setPhase(ExecutionPhase.ERROR, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException ex){
+			ex.printStackTrace();
+			fail("Can not force the execution phase of the job! (see console for more details)");
+		}
+	}
+
+	@Test
+	public void testTopIterator(){
+		ArrayList<UWSJob> jobs = new ArrayList<UWSJob>(5);
+		jobs.add(new UWSJob("0", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null));
+		jobs.add(new UWSJob("1", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null));
+		jobs.add(new UWSJob("2", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null));
+		jobs.add(new UWSJob("3", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null));
+		jobs.add(new UWSJob("4", (new Date()).getTime(), null, new UWSParameters(), -1, -1, -1, null, null));
+
+		// Just the 3 first items in same order:
+		TopIterator it = new TopIterator(jobs, 3, false);
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		// Just the 3 first items in reverse order:
+		it = new TopIterator(jobs, 3, true);
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		// All items in same order:
+		it = new TopIterator(jobs, -1, false);
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("3", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		// All items in reverse order:
+		it = new TopIterator(jobs, -1, true);
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("3", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		// More items than in the list in same order:
+		it = new TopIterator(jobs, 10, false);
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("3", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertFalse(it.hasNext());
+
+		// More items than in the list in reverse order:
+		it = new TopIterator(jobs, 10, true);
+		assertTrue(it.hasNext());
+		assertEquals("4", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("3", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("2", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("1", it.next().getJobId());
+		assertTrue(it.hasNext());
+		assertEquals("0", it.next().getJobId());
+		assertFalse(it.hasNext());
+	}
+
+	protected final static class TestHttpServletRequest implements HttpServletRequest {
+
+		private HashMap<String,String[]> parameters = new HashMap<String,String[]>();
+
+		private static class NamesEnumeration implements Enumeration<String> {
+
+			private final Iterator<String> it;
+
+			public NamesEnumeration(final Set<String> names){
+				this.it = names.iterator();
+			}
+
+			@Override
+			public boolean hasMoreElements(){
+				return it.hasNext();
+			}
+
+			@Override
+			public String nextElement(){
+				return it.next();
+			}
+
+		}
+
+		public void addParams(final String name, final String value){
+			if (parameters.containsKey(name)){
+				String[] values = parameters.get(name);
+				values = Arrays.copyOf(values, values.length + 1);
+				values[values.length - 1] = value;
+				parameters.put(name, values);
+			}else
+				parameters.put(name, new String[]{value});
+		}
+
+		public void clearParams(){
+			parameters.clear();
+		}
+
+		@Override
+		public Enumeration<String> getParameterNames(){
+			return new NamesEnumeration(parameters.keySet());
+		}
+
+		@Override
+		public String[] getParameterValues(String name){
+			return parameters.get(name);
+		}
+
+		@Override
+		public Map<String,String[]> getParameterMap(){
+			return parameters;
+		}
+
+		@Override
+		public String getParameter(String name){
+			String[] values = parameters.get(name);
+			if (values == null || values.length == 0)
+				return null;
+			else
+				return values[0];
+		}
+
+		@Override
+		public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1){
+			return null;
+		}
+
+		@Override
+		public AsyncContext startAsync(){
+			return null;
+		}
+
+		@Override
+		public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException{
+
+		}
+
+		@Override
+		public void setAttribute(String arg0, Object arg1){
+
+		}
+
+		@Override
+		public void removeAttribute(String arg0){
+
+		}
+
+		@Override
+		public boolean isSecure(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncSupported(){
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncStarted(){
+			return false;
+		}
+
+		@Override
+		public ServletContext getServletContext(){
+			return null;
+		}
+
+		@Override
+		public int getServerPort(){
+			return 0;
+		}
+
+		@Override
+		public String getServerName(){
+			return null;
+		}
+
+		@Override
+		public String getScheme(){
+			return null;
+		}
+
+		@Override
+		public RequestDispatcher getRequestDispatcher(String arg0){
+			return null;
+		}
+
+		@Override
+		public int getRemotePort(){
+			return 0;
+		}
+
+		@Override
+		public String getRemoteHost(){
+			return null;
+		}
+
+		@Override
+		public String getRemoteAddr(){
+			return null;
+		}
+
+		@Override
+		public String getRealPath(String arg0){
+			return null;
+		}
+
+		@Override
+		public BufferedReader getReader() throws IOException{
+			return null;
+		}
+
+		@Override
+		public String getProtocol(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<Locale> getLocales(){
+			return null;
+		}
+
+		@Override
+		public Locale getLocale(){
+			return null;
+		}
+
+		@Override
+		public int getLocalPort(){
+			return 0;
+		}
+
+		@Override
+		public String getLocalName(){
+			return null;
+		}
+
+		@Override
+		public String getLocalAddr(){
+			return null;
+		}
+
+		@Override
+		public ServletInputStream getInputStream() throws IOException{
+			return null;
+		}
+
+		@Override
+		public DispatcherType getDispatcherType(){
+			return null;
+		}
+
+		@Override
+		public String getContentType(){
+			return null;
+		}
+
+		@Override
+		public int getContentLength(){
+			return 0;
+		}
+
+		@Override
+		public String getCharacterEncoding(){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getAttributeNames(){
+			return null;
+		}
+
+		@Override
+		public Object getAttribute(String arg0){
+			return null;
+		}
+
+		@Override
+		public AsyncContext getAsyncContext(){
+			return null;
+		}
+
+		@Override
+		public void logout() throws ServletException{}
+
+		@Override
+		public void login(String arg0, String arg1) throws ServletException{}
+
+		@Override
+		public boolean isUserInRole(String arg0){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdValid(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromUrl(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromURL(){
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromCookie(){
+			return false;
+		}
+
+		@Override
+		public Principal getUserPrincipal(){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(boolean arg0){
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(){
+			return null;
+		}
+
+		@Override
+		public String getServletPath(){
+			return null;
+		}
+
+		@Override
+		public String getRequestedSessionId(){
+			return null;
+		}
+
+		@Override
+		public StringBuffer getRequestURL(){
+			return null;
+		}
+
+		@Override
+		public String getRequestURI(){
+			return null;
+		}
+
+		@Override
+		public String getRemoteUser(){
+			return null;
+		}
+
+		@Override
+		public String getQueryString(){
+			return null;
+		}
+
+		@Override
+		public String getPathTranslated(){
+			return null;
+		}
+
+		@Override
+		public String getPathInfo(){
+			return null;
+		}
+
+		@Override
+		public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public Part getPart(String arg0) throws IOException, IllegalStateException, ServletException{
+			return null;
+		}
+
+		@Override
+		public String getMethod(){
+			return "GET";
+		}
+
+		@Override
+		public int getIntHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Enumeration<String> getHeaders(String arg0){
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getHeaderNames(){
+			return null;
+		}
+
+		@Override
+		public String getHeader(String arg0){
+			return null;
+		}
+
+		@Override
+		public long getDateHeader(String arg0){
+			return 0;
+		}
+
+		@Override
+		public Cookie[] getCookies(){
+			return null;
+		}
+
+		@Override
+		public String getContextPath(){
+			return null;
+		}
+
+		@Override
+		public String getAuthType(){
+			return null;
+		}
+
+		@Override
+		public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException{
+			return false;
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/job/serializer/filter/TestNoArchivedFilter.java b/test/uws/job/serializer/filter/TestNoArchivedFilter.java
new file mode 100644
index 0000000..a4d89cf
--- /dev/null
+++ b/test/uws/job/serializer/filter/TestNoArchivedFilter.java
@@ -0,0 +1,71 @@
+package uws.job.serializer.filter;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+
+public class TestNoArchivedFilter {
+
+	@Test
+	public void testMatch(){
+		NoArchivedFilter filter = new NoArchivedFilter();
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+
+		UWSJob testJob = new UWSJob(new UWSParameters());
+
+		// Job PENDING => OK!
+		assertTrue(filter.match(testJob));
+
+		try{
+			// Job QUEUED => OK!
+			testJob.setPhase(ExecutionPhase.QUEUED, true);
+			assertTrue(filter.match(testJob));
+
+			// Job HELD => OK!
+			testJob.setPhase(ExecutionPhase.HELD, true);
+			assertTrue(filter.match(testJob));
+
+			// Job SUSPENDED => OK!
+			testJob.setPhase(ExecutionPhase.SUSPENDED, true);
+			assertTrue(filter.match(testJob));
+
+			// Job EXECUTING => OK!
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+
+			// Job ERROR => OK!
+			testJob.setPhase(ExecutionPhase.ERROR, true);
+			assertTrue(filter.match(testJob));
+
+			// Job ABORTED => OK!
+			testJob.setPhase(ExecutionPhase.ABORTED, true);
+			assertTrue(filter.match(testJob));
+
+			// Job COMPLETED => OK!
+			testJob.setPhase(ExecutionPhase.COMPLETED, true);
+			assertTrue(filter.match(testJob));
+
+			// Job UNKNOWN => OK!
+			testJob.setPhase(ExecutionPhase.UNKNOWN, true);
+			assertTrue(filter.match(testJob));
+
+			// Job ARCHIVED => Nope!
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertFalse(filter.match(testJob));
+
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/test/uws/job/serializer/filter/TestPhasesFilter.java b/test/uws/job/serializer/filter/TestPhasesFilter.java
new file mode 100644
index 0000000..cec6f77
--- /dev/null
+++ b/test/uws/job/serializer/filter/TestPhasesFilter.java
@@ -0,0 +1,125 @@
+package uws.job.serializer.filter;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import uws.UWSException;
+import uws.job.ExecutionPhase;
+import uws.job.UWSJob;
+import uws.job.parameters.UWSParameters;
+
+public class TestPhasesFilter {
+
+	@Test
+	public void testMatch(){
+		PhasesFilter filter = new PhasesFilter(ExecutionPhase.EXECUTING);
+
+		/* FILTER WITH ONLY ONE PHASE: EXECUTING */
+
+		// No job => Nope!
+		assertFalse(filter.match(null));
+
+		// Job PENDING => Nope!
+		UWSJob testJob = new UWSJob(new UWSParameters());
+		assertFalse(filter.match(testJob));
+
+		// Job EXECUTING => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		/* FILTER WITH TWO PHASES: QUEUED & EXECUTING */
+		filter.add(ExecutionPhase.QUEUED);
+
+		// Job PENDING => Nope!
+		testJob = new UWSJob(new UWSParameters());
+		assertFalse(filter.match(testJob));
+
+		// Job QUEUED => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.QUEUED, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job EXECUTING => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job ARCHIVED => Nope!
+		try{
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job ABORTED => Nope!
+		try{
+			testJob.setPhase(ExecutionPhase.ABORTED, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		/* FILTER WITH THREE PHASES: QUEUED & EXECUTING & ARCHIVED */
+		filter.add(ExecutionPhase.ARCHIVED);
+
+		// Job PENDING => Nope!
+		testJob = new UWSJob(new UWSParameters());
+		assertFalse(filter.match(testJob));
+
+		// Job QUEUED => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.QUEUED, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job EXECUTING => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.EXECUTING, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job ARCHIVED => OK!
+		try{
+			testJob.setPhase(ExecutionPhase.ARCHIVED, true);
+			assertTrue(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+
+		// Job ABORTED => Nope!
+		try{
+			testJob.setPhase(ExecutionPhase.ABORTED, true);
+			assertFalse(filter.match(testJob));
+		}catch(UWSException e){
+			e.printStackTrace();
+			fail("Impossible to change the phase! (see console for more details)");
+		}
+	}
+
+}
\ No newline at end of file
-- 
GitLab