=== core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWikiConstant.java
==================================================================
--- core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWikiConstant.java	(revision 1397)
+++ core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWikiConstant.java	(revision 1399)
@@ -3,4 +3,7 @@
 public class XWikiConstant {
     public static final String TAG_CLASS = "XWiki.TagClass";
     public static final String TAG_CLASS_PROP_TAGS = "tags";
+    public static final String REDIRECT_CLASS = "XWiki.XWikiRedirect";
+    public static final String REDIRECT_CLASS_PROP_PATTERN = "pattern";
+    public static final String REDIRECT_CLASS_PROP_DESTINATION = "destination";
 }
=== core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java
==================================================================
--- core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java	(revision 1397)
+++ core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java	(revision 1399)
@@ -431,6 +431,7 @@
                     getSkinClass(context);
                     getGlobalRightsClass(context);
                     getTagClass(context);
+                    getRedirectClass(context);
                     getPluginManager().virtualInit(context);
                 }
 
@@ -812,6 +813,7 @@
             getCommentsClass(context);
             getSkinClass(context);
             getGlobalRightsClass(context);
+            getRedirectClass(context);
             getStatsService(context);
         }
 
@@ -6201,4 +6203,64 @@
             context.getMessageTool().get("core.comment.rollback", params), context);
         return rolledbackDoc;
     }
+
+    public BaseClass getRedirectClass(XWikiContext context) throws XWikiException
+    {
+        XWikiDocument doc;
+        boolean needsUpdate = false;
+
+        doc = getDocument(XWikiConstant.REDIRECT_CLASS, context);
+
+        BaseClass bclass = doc.getxWikiClass();
+        if (context.get("initdone") != null)
+            return bclass;
+
+        bclass.setName(XWikiConstant.REDIRECT_CLASS);
+
+        needsUpdate |= bclass.addTextField(XWikiConstant.REDIRECT_CLASS_PROP_PATTERN, "Pattern", 30);
+        needsUpdate |= bclass.addTextField(XWikiConstant.REDIRECT_CLASS_PROP_DESTINATION, "Destination", 30);
+        String content = doc.getContent();
+        if ((content == null) || (content.equals(""))) {
+            needsUpdate = true;
+            doc.setContent("1 XWiki RedirectClass");
+        }
+
+        if (needsUpdate)
+            saveDocument(doc, context);
+        return bclass;
+    }
+
+    /**
+     * Get redirection URL if base URL matches patterns in XWikiRedirect objects attached to
+     * the XWiki.XWikiPreferences page.
+     *
+     * @param context
+     * @param url Base url
+     * @return Redirection url, or null if no pattern matched
+     * @throws XWikiException
+     */
+    public String getRedirection(String url, XWikiContext context) throws XWikiException
+    {
+        String database = context.getDatabase();
+        try {
+            context.setDatabase(context.getMainXWiki());
+            XWikiDocument preferences = getDocument("XWiki.XWikiPreferences", context);
+            Vector redirects = preferences.getObjects(XWikiConstant.REDIRECT_CLASS);
+            if (redirects == null)
+                return null;
+            Iterator it = redirects.iterator();
+            while (it.hasNext()) {
+                BaseObject redir = (BaseObject)it.next();
+                String p = redir.getStringValue(XWikiConstant.REDIRECT_CLASS_PROP_PATTERN);
+                if (url.matches(p)) {
+                    String dest = redir.getStringValue(XWikiConstant.REDIRECT_CLASS_PROP_DESTINATION);
+                    return url.replaceAll(p, dest);
+                }
+            }
+        }
+        finally {
+            context.setDatabase(database);
+        }
+        return null;
+    }
 }
=== core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiAction.java
==================================================================
--- core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiAction.java	(revision 1397)
+++ core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiAction.java	(revision 1399)
@@ -22,7 +22,7 @@
 package com.xpn.xwiki.web;
 
 import java.io.IOException;
-
+import java.net.URL;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -114,15 +114,26 @@
                 Utils.prepareContext(mapping.getName(), request, response,
                     new XWikiServletContext(servlet.getServletContext()));
 
+            URL url = context.getURL();
+
             // Add the form to the context
             context.setForm((XWikiForm) form);
             XWiki xwiki = null;
             try {
                 xwiki = XWiki.getXWiki(context);
+                // Check user-defined redirections
+                String redirection = context.getWiki().getRedirection(url.toString(), context);
+                if (redirection != null) {
+                    response.sendRedirect(redirection);
+                    return null;
+                }
             } catch (XWikiException e) {
                 if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) {
-                    // redirect
-                    String redirect = context.getWiki().Param("xwiki.virtual.redirect");
+                    // Check user-defined redirections first
+                    String redirect = context.getWiki().getRedirection(url.toString(), context);
+                    // redirect to the "No wiki page" if no user-specified redirection
+                    if (redirect == null)
+                        redirect = context.getWiki().Param("xwiki.virtual.redirect");
                     response.sendRedirect(redirect);
                     return null;
                 } else {
