Author: tmortagne
Date: 2007-12-04 12:09:32 +0100 (Tue, 04 Dec 2007)
New Revision: 6275

Added:
   xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/
   xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
Modified:
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportAction.java
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/PDFAction.java
   
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiServletURLFactory.java
   
xwiki-platform/core/trunk/xwiki-core/src/main/resources/ApplicationResources.properties
Log:
XWIKI-564: Export pages in HTML, in a zip file
- support a range a multiwiki pages in view mode without request parameters
- add skin dependencies in the package
- add attachments in the package
- modify links targeting skin, attachment and exported pages in exported pages 
(using a custom URL factory)
- package all this in a zip file
- add albatross ui button on same menu than pdf/rtf export

Modified: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java
     2007-12-04 10:19:40 UTC (rev 6274)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java
     2007-12-04 11:09:32 UTC (rev 6275)
@@ -177,7 +177,17 @@
     public static final int HAS_CLASS = 4;
 
     private int elements = HAS_OBJECTS | HAS_ATTACHMENTS;
-
+    
+    /**
+     * Separator string between database name and space name.
+     */
+    public static final String DB_SPACE_SEP = ":";
+    
+    /**
+     * Separator string between space name and page name.
+     */
+    public static final String SPACE_NAME_SEP = ".";
+    
     // Meta Data
     private BaseClass xWikiClass;
 

Added: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
                              (rev 0)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
      2007-12-04 11:09:32 UTC (rev 6275)
@@ -0,0 +1,240 @@
+package com.xpn.xwiki.export.html;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.web.ExportURLFactory;
+
+/**
+ * Create a zip package containing a range of HTML pages with skin and 
attachment dependencies.
+ * 
+ * @version $Id: $
+ */
+public class HtmlPackager
+{
+    /**
+     * The name of the package for which packager append ".zip".
+     */
+    private String name = "html.export";
+
+    /**
+     * A description of the package.
+     */
+    private String description = "";
+
+    /**
+     * The pages to export. A [EMAIL PROTECTED] Set} of page name.
+     */
+    private Set pages = new HashSet();
+
+    /**
+     * Modify the name of the package for which packager append ".zip".
+     * 
+     * @param name the name of the page.
+     */
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    /**
+     * @return the name of the package for which packager append ".zip".
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Modify the description of the package.
+     * 
+     * @param description the description of the package.
+     */
+    public void setDescription(String description)
+    {
+        this.description = description;
+    }
+
+    /**
+     * @return the description of the package.
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * Add a page to export.
+     * 
+     * @param page the name of the page to export.
+     */
+    public void addPage(String page)
+    {
+        this.pages.add(page);
+    }
+
+    /**
+     * Add a range of pages to export.
+     * 
+     * @param pages a range od pages to export.
+     */
+    public void addPages(Collection pages)
+    {
+        for (Iterator it = pages.iterator(); it.hasNext();) {
+            this.pages.add(it.next());
+        }
+    }
+
+    /**
+     * Apply export and create the ZIP package.
+     * 
+     * @param context the XWiki context used to render pages.
+     * @throws IOException error when creating the package.
+     * @throws XWikiException error when render the pages.
+     */
+    public void export(XWikiContext context) throws IOException, XWikiException
+    {
+        // ////////////////////////////////////////////
+        // Create custom URL factory
+        // ////////////////////////////////////////////
+
+        ExportURLFactory urlf = new ExportURLFactory();
+        File dir =
+            (File) 
context.getEngineContext().getAttribute("javax.servlet.context.tempdir");
+        File tempdir = new File(dir, RandomStringUtils.randomAlphanumeric(8));
+        tempdir.mkdirs();
+        File attachmentDir = new File(tempdir, "attachment");
+        attachmentDir.mkdirs();
+        urlf.init(this.pages, tempdir, context);
+        context.setURLFactory(urlf);
+
+        // ////////////////////////////////////////////
+        // Configure response
+        // ////////////////////////////////////////////
+
+        context.getResponse().setContentType("application/zip");
+        context.getResponse().addHeader("Content-disposition",
+            "attachment; filename=" + context.getWiki().getURLEncoded(name) + 
".zip");
+        context.setFinished(true);
+
+        // ////////////////////////////////////////////
+        // Render pages to export
+        // ////////////////////////////////////////////
+
+        ZipOutputStream zos = new 
ZipOutputStream(context.getResponse().getOutputStream());
+
+        XWikiContext renderContext = (XWikiContext) context.clone();
+
+        for (Iterator it = this.pages.iterator(); it.hasNext();) {
+            String pageName = (String) it.next();
+
+            XWikiDocument doc = context.getWiki().getDocument(pageName, 
context);
+
+            String zipname =
+                doc.getDatabase() + "." + doc.getSpace() + "." + doc.getName() 
+ ".html";
+            String language = doc.getLanguage();
+            if ((language != null) && (!language.equals(""))) {
+                zipname += "." + language;
+            }
+            ZipEntry zipentry = new ZipEntry(zipname);
+            zos.putNextEntry(zipentry);
+
+            renderContext.setDoc(doc);
+            String content = context.getWiki().parseTemplate("view.vm", 
renderContext);
+
+            zos.write(content.getBytes(context.getWiki().getEncoding()));
+            zos.closeEntry();
+        }
+
+        // ////////////////////////////////////////////
+        // Add needed skins to zip file
+        // ////////////////////////////////////////////
+        for (Iterator it = urlf.getNeededSkins().iterator(); it.hasNext();) {
+            String skinName = (String) it.next();
+            addSkinToZip(skinName, zos, context);
+        }
+
+        // ////////////////////////////////////////////
+        // Add resources files to zip file
+        // ////////////////////////////////////////////
+        addDirToZip(tempdir, zos, "");
+
+        zos.setComment(description);
+
+        // Finish zip file
+        zos.finish();
+        zos.flush();
+    }
+
+    /**
+     * Add skin to the package in sub-directory "skins".
+     * 
+     * @param skinName the name of the skin.
+     * @param out the ZIP output stream where to put the skin.
+     * @param context the XWiki context.
+     * @throws IOException error when adding the skin to package.
+     */
+    private static void addSkinToZip(String skinName, ZipOutputStream out, 
XWikiContext context)
+        throws IOException
+    {
+        File file =
+            new 
File(context.getWiki().getEngineContext().getRealPath("/skins/" + skinName));
+        addDirToZip(file, out, "skins/" + skinName + "/");
+    }
+
+    /**
+     * Add a directory and all its sub-directories to the package.
+     * 
+     * @param directory the directory to add.
+     * @param out the ZIP output stream where to put the skin.
+     * @param basePath the path where to put the directory in the package.
+     * @throws IOException error when adding the directory to package.
+     */
+    private static void addDirToZip(File directory, ZipOutputStream out, 
String basePath)
+        throws IOException
+    {
+        if (!directory.isDirectory()) {
+            return;
+        }
+
+        File[] files = directory.listFiles();
+
+        if (files == null) {
+            return;
+        }
+
+        byte[] tmpBuf = new byte[1024];
+
+        for (int i = 0; i < files.length; ++i) {
+            File file = files[i];
+            if (file.isDirectory()) {
+                addDirToZip(file, out, basePath + file.getName() + "/");
+                continue;
+            }
+
+            FileInputStream in = new FileInputStream(file);
+
+            out.putNextEntry(new ZipEntry(basePath + file.getName()));
+
+            int len;
+            while ((len = in.read(tmpBuf)) > 0) {
+                out.write(tmpBuf, 0, len);
+            }
+
+            out.closeEntry();
+            in.close();
+        }
+    }
+}


Property changes on: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportAction.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportAction.java
      2007-12-04 10:19:40 UTC (rev 6274)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportAction.java
      2007-12-04 11:09:32 UTC (rev 6275)
@@ -22,18 +22,25 @@
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
 import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.export.html.HtmlPackager;
 import com.xpn.xwiki.pdf.impl.PdfExportImpl;
 import com.xpn.xwiki.plugin.packaging.PackageAPI;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
- * Exports in XAR, PDF or RTF formats
+ * Exports in XAR, PDF, RTF or HTML formats.
+ * 
+ * @version $Id: $
  */
 public class ExportAction extends XWikiAction
 {
     /**
      * [EMAIL PROTECTED]
+     * 
      * @see XWikiAction#render(XWikiContext)
      */
     public String render(XWikiContext context) throws XWikiException
@@ -46,23 +53,64 @@
 
             if ((format == null) || (format.equals("xar"))) {
                 defaultPage = exportXAR(context);
+            } else if (format.equals("html")) {
+                defaultPage = exportHTML(context);
             } else {
                 defaultPage = exportPDFOrRTF(format, context);
             }
         } catch (Exception e) {
             throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
                 XWikiException.ERROR_XWIKI_APP_EXPORT,
-                "Exception while exporting", e);
+                "Exception while exporting",
+                e);
         }
 
         return defaultPage;
     }
 
-    private String exportPDFOrRTF(String format, XWikiContext context)
-        throws XWikiException, IOException
+    private String exportHTML(XWikiContext context) throws XWikiException, 
IOException
     {
-        XWikiURLFactory urlf = context.getWiki().getURLFactoryService()
-            .createURLFactory(XWikiContext.MODE_PDF, context);
+        XWikiRequest request = context.getRequest();
+
+        String description = request.get("description");
+        String name = request.get("name");
+        String[] pages = request.getParameterValues("pages");
+
+        List pageList;
+        if (pages == null || pages.length == 0) {
+            pageList = new ArrayList();
+            pageList.add(context.getDoc().getFullName());
+
+            if (name == null || name.trim().length() == 0) {
+                name = context.getDoc().getFullName();
+            }
+        } else {
+            pageList = Arrays.asList(pages);
+        }
+
+        HtmlPackager packager = new HtmlPackager();
+
+        if (name != null && name.trim().length() > 0) {
+            packager.setName(name);
+        }
+
+        if (description != null) {
+            packager.setDescription(description);
+        }
+        
+        packager.addPages(pageList);
+        
+        packager.export(context);
+        
+        return null;
+    }
+
+    private String exportPDFOrRTF(String format, XWikiContext context) throws 
XWikiException,
+        IOException
+    {
+        XWikiURLFactory urlf =
+            
context.getWiki().getURLFactoryService().createURLFactory(XWikiContext.MODE_PDF,
+                context);
         context.setURLFactory(urlf);
         PdfExportImpl pdfexport = new PdfExportImpl();
         XWikiDocument doc = context.getDoc();
@@ -76,9 +124,10 @@
         }
 
         context.getResponse().setContentType("application/" + format);
-        context.getResponse().addHeader("Content-disposition", "inline; 
filename=" +
-            Utils.encode(doc.getSpace(), context) + "_" +
-            Utils.encode(doc.getName(), context) + "." + format);
+        context.getResponse().addHeader(
+            "Content-disposition",
+            "inline; filename=" + Utils.encode(doc.getSpace(), context) + "_"
+                + Utils.encode(doc.getName(), context) + "." + format);
         pdfexport.export(doc, context.getResponse().getOutputStream(), type, 
context);
 
         return null;
@@ -107,8 +156,7 @@
             return "export";
         }
 
-        PackageAPI export =
-            ((PackageAPI) context.getWiki().getPluginApi("package", context));
+        PackageAPI export = ((PackageAPI) 
context.getWiki().getPluginApi("package", context));
         if ("true".equals(history)) {
             export.setWithVersions(true);
         } else {

Added: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
                          (rev 0)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
  2007-12-04 11:09:32 UTC (rev 6275)
@@ -0,0 +1,233 @@
+package com.xpn.xwiki.web;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.doc.XWikiAttachment;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.util.Util;
+
+public class ExportURLFactory extends XWikiServletURLFactory
+{
+    /**
+     * Pages for which to convert URL to local.
+     */
+    protected Set exportedPages = new HashSet();
+
+    /**
+     * Directory where to export attachment.
+     */
+    protected File exportDir;
+
+    /**
+     * Names of skins needed by rendered page(s).
+     */
+    private Set neededSkins = new HashSet();
+
+    public ExportURLFactory()
+    {
+    }
+
+    public void init(Collection exportedPages, File exportDir, XWikiContext 
context)
+    {
+        super.init(context);
+
+        if (exportDir != null) {
+            this.exportDir = exportDir;
+        }
+
+        if (exportedPages != null) {
+            XWikiDocument doc = new XWikiDocument();
+
+            for (Iterator it = exportedPages.iterator(); it.hasNext();) {
+                String pageName = (String) it.next();
+
+                doc.setDatabase(null);
+                doc.setSpace(null);
+                doc.setName(null);
+
+                doc.setFullName(pageName);
+
+                String absolutePageName = "";
+
+                if (doc.getDatabase() != null) {
+                    absolutePageName += doc.getDatabase().toLowerCase();
+                } else {
+                    absolutePageName += context.getDatabase().toLowerCase();
+                }
+
+                absolutePageName += XWikiDocument.DB_SPACE_SEP;
+
+                absolutePageName += doc.getFullName();
+
+                this.exportedPages.add(absolutePageName);
+            }
+        }
+    }
+
+    public URL createSkinURL(String filename, String skin, XWikiContext 
context)
+    {
+        try {
+            getNeededSkins().add(skin);
+
+            StringBuffer newpath = new StringBuffer();
+
+            newpath.append("file://");
+
+            newpath.append("skins/");
+            newpath.append(skin);
+
+            addFileName(newpath, filename, false, context);
+
+            return new URL(newpath.toString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return super.createSkinURL(filename, skin, context);
+    }
+
+    public URL createSkinURL(String filename, String web, String name, String 
xwikidb,
+        XWikiContext context)
+    {
+        if (!"skins".equals(web)) {
+            return createSkinURL(filename, web, name, xwikidb, context);
+        }
+        
+        try {
+            getNeededSkins().add(name);
+
+            StringBuffer newpath = new StringBuffer();
+
+            newpath.append("file://");
+
+            newpath.append("skins/");
+            newpath.append(name);
+
+            addFileName(newpath, filename, false, context);
+
+            return new URL(newpath.toString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return super.createSkinURL(filename, web, name, xwikidb, context);
+    }
+
+    public URL createURL(String web, String name, String action, String 
querystring,
+        String anchor, String xwikidb, XWikiContext context)
+    {
+        try {
+            if (this.exportedPages.contains((xwikidb == null ? 
context.getDatabase()
+                .toLowerCase() : xwikidb.toLowerCase())
+                + XWikiDocument.DB_SPACE_SEP + web + 
XWikiDocument.SPACE_NAME_SEP)
+                && !"view".equals(action) && context.getLinksAction() == null) 
{
+                StringBuffer newpath = new StringBuffer(servletPath);
+
+                newpath.append("file://");
+
+                newpath.append(xwikidb.toLowerCase());
+                newpath.append(".");
+                newpath.append(web);
+                newpath.append(".");
+                newpath.append(name);
+
+                if ((anchor != null) && (!anchor.equals(""))) {
+                    newpath.append("#");
+                    newpath.append(anchor);
+                }
+
+                newpath.append(".html");
+
+                return new URL(newpath.toString());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return super.createURL(web, name, action, querystring, anchor, 
xwikidb, context);
+    }
+
+    public URL createAttachmentURL(String filename, String web, String name, 
String action,
+        String querystring, String xwikidb, XWikiContext context)
+    {
+        try {
+            String path =
+                "attachment/" + context.getDatabase() + "." + web + "." + name 
+ "." + filename;
+
+            File tempdir = exportDir;
+            File file = new File(tempdir, path);
+            if (!file.exists()) {
+                XWikiDocument doc =
+                    context.getWiki().getDocument(web + 
XWikiDocument.SPACE_NAME_SEP + name,
+                        context);
+                XWikiAttachment attachment = doc.getAttachment(filename);
+                byte[] data = attachment.getContent(context);
+                FileOutputStream fos = new FileOutputStream(file);
+                fos.write(data);
+                fos.close();
+            }
+
+            return new URI("file://" + path).toURL();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return super.createAttachmentURL(filename, web, name, action, 
null, xwikidb, context);
+        }
+    }
+
+    public URL createAttachmentRevisionURL(String filename, String web, String 
name,
+        String revision, String xwikidb, XWikiContext context)
+    {
+        try {
+            String path =
+                "attachment/" + context.getDatabase() + "." + web + "." + name 
+ "." + filename;
+
+            File tempdir = exportDir;
+            File file = new File(tempdir, path);
+            if (!file.exists()) {
+                XWikiDocument doc =
+                    context.getWiki().getDocument(web + 
XWikiDocument.SPACE_NAME_SEP + name,
+                        context);
+                XWikiAttachment attachment =
+                    
doc.getAttachment(filename).getAttachmentRevision(revision, context);
+                byte[] data = attachment.getContent(context);
+                FileOutputStream fos = new FileOutputStream(file);
+                fos.write(data);
+                fos.close();
+            }
+
+            return new URI("file://" + path).toURL();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return super.createAttachmentRevisionURL(filename, web, name, 
revision, xwikidb,
+                context);
+        }
+    }
+
+    public String getURL(URL url, XWikiContext context)
+    {
+        if (url == null) {
+            return "";
+        }
+
+        String path = Util.escapeURL(url.toString());
+
+        if (url.getProtocol().equals("file")) {
+            path = path.substring("file://".length());
+        }
+
+        return path;
+    }
+
+    public Collection getNeededSkins()
+    {
+        return neededSkins;
+    }
+}


Property changes on: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/PDFAction.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/PDFAction.java
 2007-12-04 10:19:40 UTC (rev 6274)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/PDFAction.java
 2007-12-04 11:09:32 UTC (rev 6275)
@@ -27,6 +27,10 @@
 
 import java.io.IOException;
 
+/**
+ *
+ * @deprecated Use [EMAIL PROTECTED] ExportAction}.
+ */
 public class PDFAction extends XWikiAction {
        public String render(XWikiContext context) throws XWikiException {
         XWikiURLFactory urlf = 
context.getWiki().getURLFactoryService().createURLFactory(XWikiContext.MODE_PDF,
 context);

Modified: 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiServletURLFactory.java
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiServletURLFactory.java
    2007-12-04 10:19:40 UTC (rev 6274)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiServletURLFactory.java
    2007-12-04 11:09:32 UTC (rev 6275)
@@ -217,12 +217,12 @@
         }
     }
 
-    private void addFileName(StringBuffer newpath, String filename, 
XWikiContext context)
+    protected void addFileName(StringBuffer newpath, String filename, 
XWikiContext context)
     {
         addFileName(newpath, filename, true, context);
     }
 
-    private void addFileName(StringBuffer newpath, String filename, boolean 
encode,
+    protected void addFileName(StringBuffer newpath, String filename, boolean 
encode,
         XWikiContext context)
     {
         newpath.append("/");

Modified: 
xwiki-platform/core/trunk/xwiki-core/src/main/resources/ApplicationResources.properties
===================================================================
--- 
xwiki-platform/core/trunk/xwiki-core/src/main/resources/ApplicationResources.properties
     2007-12-04 10:19:40 UTC (rev 6274)
+++ 
xwiki-platform/core/trunk/xwiki-core/src/main/resources/ApplicationResources.properties
     2007-12-04 11:09:32 UTC (rev 6275)
@@ -742,6 +742,7 @@
 core.menu.preview=Print preview
 core.menu.export.pdf=Export as PDF
 core.menu.export.rtf=Export as RTF
+core.menu.export.html=Export as HTML
 core.menu.rights=Page access rights
 core.menu.watch=Watch
 core.menu.unwatch=Unwatch

_______________________________________________
notifications mailing list
[email protected]
http://lists.xwiki.org/mailman/listinfo/notifications

Reply via email to