Index: src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPlugin.java
===================================================================
--- src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPlugin.java	(revision 27)
+++ src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPlugin.java	(working copy)
@@ -29,6 +29,7 @@
 import com.xpn.xwiki.plugin.XWikiPluginInterface;
 import com.xpn.xwiki.plugin.lucene.LucenePlugin;
 import com.xpn.xwiki.plugin.lucene.SearchResult;
+import com.xpn.xwiki.plugin.lucene.SearchResults;
 import com.xpn.xwiki.plugin.scheduler.SchedulerPlugin;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -57,6 +58,8 @@
 
     public static String WATCHLIST_CLASS = "XWiki.WatchListClass";
 
+    public static String WATCHLIST_JOB_EXTENSION = "XWiki.WatchListJobExtension";
+
     public static final int WATCHLIST_INTERVAL_NEVER = 0;
 
     public static final int WATCHLIST_INTERVAL_HOUR = 1;
@@ -99,6 +102,11 @@
     public void virtualInit(XWikiContext context)
     {
         super.virtualInit(context);
+        try {
+            initWatchListClass(context);
+        } catch (XWikiException e) {
+            log.error("virtualInit", e);
+        }
     }
 
     /**
@@ -115,8 +123,7 @@
             initWatchlistJobs(context);
             sanitizeWatchlists(context);
         } catch (XWikiException e) {
-            log.error("virtualInit", e);
-            e.printStackTrace();
+            log.error("Init", e);
         }
     }
 
@@ -170,8 +177,12 @@
                 WATCHLIST_INTERVAL_WEEK + "=weekly|" +
                 WATCHLIST_INTERVAL_MONTH + "=monthly");
         needsUpdate |=
-            bclass.addTextAreaField("spaces", "Space list, separated by commas", 40, 5);
+                bclass.addTextAreaField("wikis", "Wiki list, separated by commas", 40, 5);
         needsUpdate |=
+                bclass.addTextAreaField("tags", "Tags list, separated by commas", 40, 5);
+        needsUpdate |=
+                bclass.addTextAreaField("spaces", "Space list, separated by commas", 40, 5);
+        needsUpdate |=
             bclass.addTextAreaField("documents", "Document list, separated by commas", 40, 5);
         needsUpdate |= bclass.addTextAreaField("query", "Query (HQL)", 40, 5);
 
@@ -475,7 +486,94 @@
         return true;
     }
 
+
     /**
+     * Transforms a list of wiki:space in a lucene query
+     **/
+    private String getLuceneQuery(String[] docs, String replaceString) {
+        List query = new ArrayList();
+
+        for (int i=0;i<docs.length;i++) {
+           if ((docs[i]!=null) &&(!docs[i].trim().equals("")))
+              query.add(docs[i].replaceAll("(.*):(.*)", replaceString));
+        }
+        return StringUtils.join(query, " OR ");
+    }
+
+     /**
+     * Transforms a list of wikis in a lucene query
+     **/
+    private String getWikisLuceneQuery(String[] wikis) {
+        List query = new ArrayList();
+
+        for (int i=0;i<wikis.length;i++) {
+           if ((wikis[i]!=null) &&(!wikis[i].trim().equals("")))
+              query.add("wiki:" + wikis[i]);
+        }
+        return StringUtils.join(query, " OR ");
+    }
+    /**
+     * Transforms a list of wiki:space in a lucene query
+     **/
+    private String getLuceneQuery(String documents, String spaces, String wikis) {
+        String watchedDocuments = getLuceneQuery(documents.split(","), "(wiki:$1 AND fullname:$2)");
+        String watchedSpaces = getLuceneQuery(spaces.trim().split(","), "(wiki:$1 AND web:$2)");
+        String watchedWikis = getWikisLuceneQuery(wikis.trim().split(","));
+        String watchedWikisQuery = (watchedWikis.equals("")) ? "" : " OR " + watchedWikis;
+
+        String request = null;
+        if (!StringUtils.isBlank(watchedSpaces) && !StringUtils.isBlank(watchedDocuments)) {
+            request = "(" + watchedSpaces + " OR " + watchedDocuments + watchedWikisQuery + ") AND type:wikipage";
+        } else if (StringUtils.isBlank(watchedSpaces) && !StringUtils.isBlank(watchedDocuments)) {
+            request = "(" + watchedDocuments + watchedWikisQuery + ") AND type:wikipage";
+        } else if (!StringUtils.isBlank(watchedSpaces) && StringUtils.isBlank(watchedDocuments)) {
+            request = "(" + watchedSpaces + watchedWikisQuery + ") AND type:wikipage";
+        }
+        return request;
+    }
+
+    /**
+     * Transforms a list of wiki:space in a lucene query
+     **/
+    private String getTagsLuceneQuery(String tags) {
+        String watchedTags = getLuceneQuery(tags.trim().split(","), "(wiki:$1 AND (XWiki.FeedEntryClass.tags:$2 OR XWiki.TagClass.tags:$2))");
+        String request = null;
+        if (!StringUtils.isBlank(watchedTags)) {
+            request = "(" + watchedTags + ")";
+        } else {
+            request = "";
+        }
+        return request;
+    }
+
+    /**
+     * This function filters elements not for the wiki named currentWikiName
+     * It includes those that start with currentWikiName: or have no wiki name specified
+     * @param docspaceList
+     * @param currentWikiName
+     * @return
+     */
+    private String filterList(String docspaceList, String currentWikiName) {
+        if ((docspaceList==null)||(docspaceList.equals("")))
+            return "";
+
+        ArrayList list = new ArrayList();
+        String[] docspaces = docspaceList.split(",");
+        for (int i=0;i<docspaces.length;i++) {
+            String docspace = docspaces[i];
+            int i1 = docspace.indexOf(":");
+            if (i1==-1)
+             list.add(docspace);
+            else {
+                if (docspace.startsWith(currentWikiName + ":"))
+                 list.add(docspace.substring(i1+1));
+            }
+        }
+        return StringUtils.join(list, ",");
+    }
+
+
+    /**
      * Get the list of the elements watched by user ordered by last modification date, descending
      *
      * @param user XWiki User
@@ -485,48 +583,108 @@
      */
     public List getWatchListWhatsNew(String user, XWikiContext context) throws Exception
     {
+        return getWatchListWhatsNew(user, true, false, 20, context);
+    }
+
+    /**
+     * Get the list of the elements watched by user ordered by last modification date, descending
+     *
+     * @param user XWiki User
+     * @param docsOrTags Include documents matching docs and spaces (true) OR documents matching tags (false)
+     * @param forceMonoWiki force mono wiki mode running and hql query
+     * @param context Context of the request
+     * @return The list of the watched elements ordered by last modification date, descending
+     * @throws XWikiException If the search request fails
+     */
+    public List getWatchListWhatsNew(String user, boolean docsOrTags, boolean forceMonoWiki, int nbElements, XWikiContext context) throws Exception
+    {
         List<String> results = new ArrayList<String>();
         BaseObject watchListObject = this.getWatchListObject(user, context);
 
-        if (context.getWiki().isVirtualMode()) {
-            // In virtual mode RSS feeds are generated from a Lucene query
-            String watchedDocuments = watchListObject.getLargeStringValue("documents").trim()
-                .replaceFirst("^,", "").replaceFirst("^(.*):(.*)", "(wiki:$1 AND fullname:$2)")
-                .replaceAll(",(.*):(.*)", " OR (wiki:$1 AND fullname:$2)");
-            String watchedSpaces = watchListObject.getLargeStringValue("spaces").trim()
-                .replaceFirst("^,", "").replaceFirst("^(.*):(.*)", "(wiki:$1 AND web:$2)")
-                .replaceAll(",(.*):(.*)", " OR (wiki:$1 AND web:$2)");
-            String request = null;
-            if (!StringUtils.isBlank(watchedSpaces) && !StringUtils.isBlank(watchedDocuments)) {
-                request = "(" + watchedSpaces + " OR " + watchedDocuments + ") AND type:wikipage";
-            } else if (StringUtils.isBlank(watchedSpaces) && !StringUtils.isBlank(watchedDocuments)) {
-                request = watchedDocuments + " AND type:wikipage";
-            } else if (!StringUtils.isBlank(watchedSpaces) && StringUtils.isBlank(watchedDocuments)) {
-                request = watchedSpaces + " AND type:wikipage";
-            }
+        if (watchListObject!=null) {
+            if ((!forceMonoWiki)&&context.getWiki().isVirtualMode()) {
+                String request;
+                // In virtual mode RSS feeds are generated from a Lucene query
+                if (docsOrTags) {
+                    // We should build the query for documents matching documents and spaces
+                    String documents = watchListObject.getLargeStringValue("documents");
+                    String spaces = watchListObject.getLargeStringValue("spaces");
+                    String wikis = watchListObject.getLargeStringValue("wikis");
+                    request = getLuceneQuery(documents, spaces, wikis);
+                } else {
+                    // We should build the query for documents matching tags
+                    String tags = watchListObject.getLargeStringValue("tags").trim();
+                    request = getTagsLuceneQuery(tags);
+                }
 
-            if (!StringUtils.isBlank(request)) {
-                String language = StringUtils.isBlank(context.getLanguage()) ? "default" : context.getLanguage();
-                LucenePlugin lucene = (LucenePlugin) context.getWiki().getPlugin("lucene", context);
-                List<SearchResult> luceneResults =
-                    lucene.getSearchResults(request, "-date", null, language, context).getResults(0, 20);
+                if (!StringUtils.isBlank(request)) {
+                    String language = StringUtils.isBlank(context.getLanguage()) ? "default" : context.getLanguage();
+                    LucenePlugin lucene = (LucenePlugin) context.getWiki().getPlugin("lucene", context);
+                    if (log.isInfoEnabled()) {
+                        log.info("Running lucene query " + request);
+                    }
 
-                for (SearchResult result : luceneResults) {
-                    results.add(result.getWiki() + ":" + result.getFullName());
+                    // Reunning the lucene query
+                    SearchResults luceneSearchResults = lucene.getSearchResults(request, "-date", null, language, context);
+                    if (log.isInfoEnabled()) {
+                        log.info("Lucene query return " + luceneSearchResults.getTotalHitcount() + " results before security checks");
+                    }
+                    List<SearchResult> luceneResults = luceneSearchResults.getResults(1, nbElements);
+                    for (SearchResult result : luceneResults) {
+                        results.add(result.getWiki() + ":" + result.getFullName());
+                    }
+                    if (log.isInfoEnabled()) {
+                        log.info("Lucene query return " + results.size() + " results after security checks and max items");
+                    }
                 }
+            } else {
+                // In simple mode RSS feeds are generated from a HSQL query
+                String request;
+                if (docsOrTags) {
+                    // We should get the documents matching documents and spaces
+                    String currentWikiName = context.getDatabase();
+
+                    String wWikis = watchListObject.getLargeStringValue("wikis");
+                    boolean hasWiki = false;
+                    if (wWikis!=null) {
+                        String[] watchedWikis = wWikis.trim().split(",");
+                        for (int i=0;i<watchedWikis.length;i++) {
+                            if (watchedWikis[i].trim().equals(currentWikiName)) {
+                                hasWiki = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (hasWiki) {
+                        request = "select doc.fullName from XWikiDocument as doc order by doc.date desc";
+
+                    } else {
+                        String watchedDocuments = filterList(watchListObject.getLargeStringValue("documents"), currentWikiName).replaceAll(",", "','");
+                        String watchedSpaces = filterList(watchListObject.getLargeStringValue("spaces"), currentWikiName).replaceAll(",", "','");
+                        request = "select doc.fullName from XWikiDocument as doc where doc.web in ('" +
+                                watchedSpaces + "') or doc.fullName in ('" + watchedDocuments + "') " +
+                                "order by doc.date desc";
+                    }
+                } else {
+
+                    // We need to get documents matching tags
+                    String watchedTags =
+                            watchListObject.getLargeStringValue("tags").trim().replaceFirst("^,", "")
+                                    .replaceAll("[^\\.,:]+:", "").replaceAll(",", "','");
+
+                    request = "select doc.fullName from XWikiDocument as doc where doc.fullName in (select obj1.name from BaseObject as obj1, DBStringListProperty as prop join prop.list list"
+                            + " where obj1.className='XWiki.TagClass' and obj1.id=prop.id.id and prop.id.name='tags' and list in ('" + watchedTags + "')) "
+                            + " or doc.fullName in (select obj2.name from BaseObject as obj2, XWiki.FeedEntryClass as feed join feed.tags tags where obj2.id=feed.id and tags in ('" + watchedTags + "'))"
+                            + " order by doc.date desc";
+                }
+
+                if (log.isInfoEnabled()) {
+                    log.info("Running hql query " + request);
+                }
+                // running the HQL query
+                results.addAll(context.getWiki().getStore().search(request, nbElements, 0, context));
             }
-        } else {
-            // In simple mode RSS feeds are generated from a HSQL query
-            String watchedDocuments =
-                watchListObject.getLargeStringValue("documents").trim().replaceFirst("^,", "")
-                    .replaceAll("[^\\.,:]+:", "").replaceAll(",", "','");
-            String watchedSpaces =
-                watchListObject.getLargeStringValue("spaces").trim().replaceFirst("^,", "")
-                    .replaceAll("[^\\.,:]+:", "").replaceAll(",", "','");
-            String request = "select doc.fullName from XWikiDocument as doc where doc.space in ('" +
-                watchedSpaces + "') or doc.fullName in ('" + watchedDocuments + "') " +
-                "order by doc.date desc";
-            results.addAll(context.getWiki().getStore().search(request, 20, 0, context));
         }
         return results;
     }
Index: src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJobExtension.java
===================================================================
--- src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJobExtension.java	(revision 0)
+++ src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJobExtension.java	(revision 31)
@@ -0,0 +1,32 @@
+package com.xpn.xwiki.plugin.watchlist;
+
+import com.xpn.xwiki.api.*;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.XWikiContext;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: ludovic
+ * Date: 10 aožt 2008
+ * Time: 19:37:35
+ * To change this template use File | Settings | File Templates.
+ */
+public interface WatchListJobExtension {
+
+    /**
+     * WatchList Job Extension written in Groovy
+     * @param job
+     * @param updatedDocuments
+     * @param notificationCriteria
+     * @param subscriber
+     * @param context
+     * @return
+     * @throws XWikiException
+     */
+    public List filter(WatchListJob job, List updatedDocuments,
+          com.xpn.xwiki.api.Object notificationCriteria, String subscriber, XWikiContext context) throws XWikiException;
+
+    
+}
Index: src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPluginApi.java
===================================================================
--- src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPluginApi.java	(revision 27)
+++ src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListPluginApi.java	(working copy)
@@ -246,6 +246,35 @@
     }
 
     /**
+     * Get the list of the elements watched by user ordered by last modification date, descending
+     *
+     * @param user XWiki User
+     * @param docsOrTags true for docs matching docs and spaces, false for docs matching tags
+     * @param nbElements Number of elements to return
+     * @return the list of the elements watched by user ordered by last modification date, descending
+     * @throws XWikiException If the search request fails
+     */
+    public List getWatchListWhatsNew(String user, boolean docsOrTags, int nbElements) throws Exception
+    {
+        return getWatchListPlugin().getWatchListWhatsNew(user, docsOrTags, false, nbElements, context);
+    }
+
+    /**
+     * Get the list of the elements watched by user ordered by last modification date, descending
+     *
+     * @param user XWiki User
+     * @param docsOrTags true for docs matching docs and spaces, false for docs matching tags
+     * @param forceMonoWiki force running an HQL query
+     * @param nbElements number of returned elements
+     * @return the list of the elements watched by user ordered by last modification date, descending
+     * @throws XWikiException If the search request fails
+     */
+    public List getWatchListWhatsNew(String user, boolean docsOrTags, boolean forceMonoWiki, int nbElements) throws Exception
+    {
+        return getWatchListPlugin().getWatchListWhatsNew(user, docsOrTags, forceMonoWiki, nbElements, context);
+    }
+
+    /**
      * Get the WatchList plugin
      *
      * @return the WatchList plugin
Index: src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJob.java
===================================================================
--- src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJob.java	(revision 27)
+++ src/main/java/com/xpn/xwiki/plugin/watchlist/WatchListJob.java	(working copy)
@@ -170,8 +170,8 @@
                         }
                         // Filter documents according to lists in the WatchList Object
                         List matchingDocuments =
-                            filter(updatedDocuments, notificationCriteria,
-                                subscriber.getFullName());
+                            filterExtension(updatedDocuments, notificationCriteria,
+                                subscriber.getFullName(), context);
 
                         // If there are matching documents, sends the notification
                         if (matchingDocuments.size() > 0) {
@@ -213,18 +213,51 @@
 
     /**
      * Filters updated documents against users' criteria
+     * This method tries to load the filter extension. If it fails it runs the standard filter.
      *
      * @param updatedDocuments the list of updated documents
      * @param notificationCriteria the BaseObject representing the user notification criteria
      * @param subscriber the user to be notified
      * @return a filtered list of documents to be sent to the user
      */
+    private List filterExtension(List updatedDocuments,
+                                 Object notificationCriteria, String subscriber, XWikiContext context) throws XWikiException
+    {
+        if (context.getWiki().exists(WatchListPlugin.WATCHLIST_JOB_EXTENSION, context)) {
+            try {
+                WatchListJobExtension wlext = (WatchListJobExtension) context.getWiki().parseGroovyFromPage(WatchListPlugin.WATCHLIST_JOB_EXTENSION, context);
+                return wlext.filter(this, updatedDocuments, notificationCriteria, subscriber, context);
+            } catch (Throwable e) {
+                if (LOG.isErrorEnabled()) {
+                    LOG.error("Exception while loading watch list job extension", e);
+                }
+            }
+        }
+
+        return filter(updatedDocuments, notificationCriteria, subscriber);
+    }
+
+
+    /**
+     * Filters updated documents against users' criteria
+     *
+     * @param updatedDocuments the list of updated documents
+     * @param notificationCriteria the BaseObject representing the user notification criteria
+     * @param subscriber the user to be notified
+     * @return a filtered list of documents to be sent to the user
+     */
     private List filter(List updatedDocuments,
         Object notificationCriteria, String subscriber) throws XWikiException
     {
-        String spaceCriterion = (String) notificationCriteria.getProperty("spaces").getValue();
-        String documentCriterion = (String) notificationCriteria.getProperty("documents").getValue();
-        String query = (String) notificationCriteria.getProperty("query").getValue();
+        if (notificationCriteria==null)
+        {
+            return new ArrayList();
+        }
+                 
+        String tagsCriterion = (notificationCriteria.getProperty("tags")==null) ? "" : (String) notificationCriteria.getProperty("tags").getValue();
+        String spaceCriterion = (notificationCriteria.getProperty("spaces")==null) ? "" : (String) notificationCriteria.getProperty("spaces").getValue();
+        String documentCriterion = (notificationCriteria.getProperty("documents")==null) ? "" : (String) notificationCriteria.getProperty("documents").getValue();
+        String query = (notificationCriteria.getProperty("query")==null) ? "" :  (String) notificationCriteria.getProperty("query").getValue();
 
         List watchedDocuments = new ArrayList();
         if (spaceCriterion.length() == 0 && documentCriterion.length() == 0
@@ -234,11 +267,14 @@
         }
 
         List filteredDocumentList = new ArrayList();
-        String[] watchedSpaces = spaceCriterion.split(",");
-        String[] docArray = documentCriterion.split(",");
+        String[] watchedSpaces = spaceCriterion.trim().equals("") ? null : spaceCriterion.split(",");
+        String[] watchedDocs = documentCriterion.trim().equals("") ? null : documentCriterion.split(",");
+        String[] watchedTags = tagsCriterion.trim().equals("") ? null : tagsCriterion.split(",");
 
-        for (int i = 0; i < docArray.length; i++) {
-            watchedDocuments.add(docArray[i]);
+        if (watchedDocs!=null) {
+            for (int i = 0; i < watchedDocs.length; i++) {
+                watchedDocuments.add(watchedDocs[i]);
+            }
         }
 
         if (query.length() > 0) {
@@ -251,37 +287,64 @@
         while (updatedDocumentsIt.hasNext()) {
             String updatedDocumentName = (String) updatedDocumentsIt.next();
             Document updatedDocument =
-                new Document(context.getWiki().getDocument(updatedDocumentName, context), context);
+                    new Document(context.getWiki().getDocument(updatedDocumentName, context), context);
             String updatedDocumentSpace =
-                updatedDocument.getWiki() + ":" + updatedDocument.getSpace();
+                    updatedDocument.getWiki() + ":" + updatedDocument.getSpace();
             boolean documentAdded = false;
 
-            for (int i = 0; i < watchedSpaces.length; i++) {
-                if (updatedDocumentSpace.equals(watchedSpaces[i])
-                    && context.getWiki().getRightService()
-                    .hasAccessLevel("view", subscriber, updatedDocumentName, context))
-                {
-                    filteredDocumentList.add(updatedDocumentName);
-                    documentAdded = true;
-                    break;
+            if (watchedSpaces!=null) {
+                for (int i = 0; i < watchedSpaces.length; i++) {
+                    if (updatedDocumentSpace.equals(watchedSpaces[i])
+                            && context.getWiki().getRightService()
+                            .hasAccessLevel("view", subscriber, updatedDocumentName, context))
+                    {
+                        filteredDocumentList.add(updatedDocumentName);
+                        documentAdded = true;
+                        break;
+                    }
                 }
             }
 
             // loop over the watched documents if the current document has not
             // been included in the filteredList already
-            if (!documentAdded) {
+            if ((!documentAdded)&&(watchedDocs!=null)) {
                 Iterator watchedDocumentIt = watchedDocuments.iterator();
                 while (watchedDocumentIt.hasNext()) {
                     String watchedDocumentName = (String) watchedDocumentIt.next();
                     if (updatedDocumentName.equals(watchedDocumentName)
-                        && context.getWiki().getRightService()
-                        .hasAccessLevel("view", subscriber, updatedDocumentName, context))
+                            && context.getWiki().getRightService()
+                            .hasAccessLevel("view", subscriber, updatedDocumentName, context))
                     {
                         filteredDocumentList.add(updatedDocumentName);
+                        documentAdded = true;
                         break;
                     }
                 }
             }
+
+            // loop of the watched document to see if the tags are matching
+            if ((!documentAdded)&&(watchedTags!=null)) {
+                List tagList1 = new ArrayList(), tagList2 = new ArrayList();
+                try {
+                    // we need to get the XWiki tags
+                    tagList1 = (List) updatedDocument.getObject("XWiki.TagClass").getProperty("tags").getValue();
+                } catch (Exception e) {}
+                try {
+                    // we need to get the Feed Entry tags
+                    tagList2 = (List) updatedDocument.getObject("XWiki.FeedEntryClass").getProperty("tags").getValue();
+                } catch (Exception e) {}
+
+                if ((tagList1.size()>=0)&&(tagList2.size()>0)) {
+                    for (int i=0;i<watchedTags.length;i++) {
+                        String tag = watchedTags[i];
+                        if (tagList1.contains(tag)||tagList2.contains(tag)) {
+                            filteredDocumentList.add(updatedDocumentName);
+                            documentAdded = true;
+                            break;
+                        }
+                    }
+                }
+            }
         }
 
         return filteredDocumentList;
