You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2012/03/09 17:23:32 UTC

svn commit: r1298906 [12/14] - in /velocity/sandbox/velosurf: ./ docs/ examples/ examples/ant-vpp/ examples/ant-vpp/lib/ examples/auth-l10n/ examples/auth-l10n/WEB-INF/ examples/auth-l10n/WEB-INF/lib/ examples/auth-l10n/WEB-INF/src/ examples/auth-l10n/...

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/ValidationFilter.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/ValidationFilter.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/ValidationFilter.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/ValidationFilter.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.validation;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.velosurf.context.DBReference;
+
+//import org.apache.velocity.velosurf.util.UserContext;
+import org.apache.velocity.velosurf.context.EntityReference;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.ToolFinder;
+import org.apache.velocity.velosurf.util.UserContext;
+import org.apache.velocity.velosurf.web.VelosurfTool;
+import org.apache.velocity.velosurf.web.l10n.Localizer;
+
+/**
+ * <p>This class is an optional filter that will validate query data according to the <code>velosurf.entity</code> query parameter
+ * (may be multivalued).
+ * If data pass all validation constraints, the filter will let the request pass towards the form action. Otherwise,
+ * it will redirect back the user to the original form (using the referer query header). In this case, the filter will
+ * populate the session with the given values (escaped) so that they can be put back in the form.</p>
+ *
+ */
+public class ValidationFilter implements Filter
+{
+    /** filter config */
+    private FilterConfig config;
+
+    /** key used to identity an entity to be validated in HTTP parameters */
+    private static final String ENTITY_KEY = "velosurf.entity";
+
+    /**
+     * initialization.
+     * @param filterConfig filter config
+     * @throws ServletException
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        config = filterConfig;
+    }
+
+    /**
+     * Filtering.
+     * @param servletRequest request
+     * @param servletResponse response
+     * @param filterChain filter chain
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest)servletRequest;
+        HttpServletResponse response = (HttpServletResponse)servletResponse;
+        HttpSession session = null;
+        Map<String, Object> map = null;
+        String[] entities = null;
+        boolean filter = (request.getParameter(ENTITY_KEY) != null);
+        boolean accept = true;
+
+        if(filter)
+        {
+            session = request.getSession(true);
+
+            /*
+             *  FIXME: the next call won't work when using a non-standard Velosurf config file
+             *  defined in toolbox.xml
+             */
+            DBReference db = VelosurfTool.getDefaultInstance(config.getServletContext());
+
+            map = new HashMap();
+
+            UserContext userContext = (UserContext)session.getAttribute(UserContext.USER_CONTEXT_KEY);
+
+            if(userContext == null)
+            {
+                userContext = db.getUserContext();
+
+                Localizer localizer = ToolFinder.findSessionTool(session, Localizer.class);
+
+                if(localizer != null)
+                {
+                    userContext.setLocalizer(localizer);
+                }
+                else
+                {
+                    userContext.setLocale(request.getLocale());
+                }
+                session.setAttribute(UserContext.USER_CONTEXT_KEY, userContext);
+            }
+            else
+            {
+                db.setUserContext(userContext);
+            }
+            if(db != null)
+            {
+                Map<String, Object> params = request.getParameterMap();
+                Object[] array;
+
+                for(Map.Entry<String, Object> entry : (Set<Map.Entry<String, Object>>)params.entrySet())
+                {
+                    array = (Object[])entry.getValue();
+                    map.put(entry.getKey(), array.length == 1 ? array[0] : array);
+                }
+                entities = (String[])params.get(ENTITY_KEY);
+                if(entities != null)
+                {
+                    for(String entity : entities)
+                    {
+                        EntityReference entityRef = (EntityReference)db.get(entity);
+
+                        if(entityRef == null)
+                        {
+                            Logger.error("validation: entity '" + entity + "' not found!");
+                            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                                               "validation: entity '" + entity + "' does not exist!");
+                            return;
+                        }
+                        else
+                        {
+                            try
+                            {
+                                accept &= entityRef.validate(map);
+                            }
+                            catch(Exception e)
+                            {
+                                Logger.error("validation: validation of entity '" + entity + "' throwed exception:");
+                                Logger.log(e);
+                                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                                                   "validation: validation of entity '" + entity
+                                                   + "' throwed exception " + e.getMessage());
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                Logger.error("validation: could not get a database connexion!");
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Velosurf tool not found in session!");
+                return;
+            }
+        }
+        if(filter &&!accept)
+        {
+            Logger.trace("validation: values did not pass checking");
+
+            String referer = request.getHeader("Referer");
+
+            if(referer == null)
+            {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Referer header needed!");
+            }
+            else
+            {
+                for(String entity : entities)
+                {
+                    session.setAttribute(entity, map);
+                }
+                Logger.trace("validation: redirecting request towards " + referer);
+                response.sendRedirect(referer);
+            }
+        }
+        else
+        {
+            if(filter /* && acept */)
+            {
+                Logger.trace("validation: values did pass checking");
+            }
+            filterChain.doFilter(servletRequest, servletResponse);
+        }
+    }
+
+    /**
+     * Destroy the filter.
+     */
+    public void destroy(){}
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/validation/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains all classes related to data validation constraints.
+
+<h2>Package Specification</h2>
+
+<!-- ANY SPECS NEEDED BY JAVA COMPATIBILITY KIT GO THERE
+<ul>
+  <li><a href="">##### REFER TO ANY FRAMEMAKER SPECIFICATION HERE #####</a>
+</ul>
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="">##### REFER TO NON-SPEC DOCUMENTATION HERE #####</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/HttpQueryTool.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/HttpQueryTool.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/HttpQueryTool.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/HttpQueryTool.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web;
+
+import java.util.*;
+import org.apache.velocity.tools.view.context.ViewContext;
+import org.apache.velocity.tools.view.tools.ParameterParser;
+import org.apache.velocity.velosurf.util.Logger;
+
+/**
+ * <p>(Deprecated - please use org.apache.velocity.tools.view.ParameterTool)</p>
+ * This class extends the tool org.apache.velocity.tools.view.tools.ParameterParser,
+ *  adding a generic setter. Values that are set manually hide any previous values that
+ *  are present in the query under the same key.
+ *
+ * It is meant for the query scope.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public @Deprecated
+class HttpQueryTool extends ParameterParser
+{
+    /** extra values map. */
+    private Map<String, Object> extraValues = new HashMap<String, Object>();
+
+    /**
+     * Constructor.
+     */
+    public HttpQueryTool(){}
+
+    /**
+     * Initialize this tool.
+     * @param viewContext view context
+     */
+    public void init(Object viewContext)
+    {
+        super.init(viewContext);
+
+        /* review all parameter keys to interpret "dots" inside keynames (only one level for now - FIXME: implement a recursive behaviour) */
+        for(Map.Entry<String, Object> entry : (Set<Map.Entry<String, Object>>)getSource().entrySet())
+        {
+            String key = entry.getKey();
+            int dot = key.indexOf('.');
+
+            if(dot > 0 && dot < key.length() - 1)
+            {
+                String parentKey = key.substring(0, dot);
+                String subKey = key.substring(dot + 1);
+                Object value = entry.getValue();
+
+                if(value.getClass().isArray() && ((String[])value).length == 1)
+                {
+                    value = ((String[])value)[0];
+                }
+
+                Map map = (Map)extraValues.get(parentKey);
+
+                if(map == null)
+                {
+                    map = new HashMap();
+                    extraValues.put(parentKey, map);
+                }
+                map.put(subKey, value);
+            }
+        }
+    }
+
+    /**
+     * Generic getter.
+     * @param key key
+     * @return value or null
+     */
+    public Object get(Object key)
+    {
+        Object ret = extraValues.get(key);
+
+        if(ret == null)
+        {
+            return super.get(key.toString());
+        }
+        else
+        {
+            return ret;
+        }
+    }
+
+    /**
+     * Generic getter with String argument (necessary to properly overload super getter).
+     * @param key key
+     * @return value or null
+     */
+    public Object get(String key)
+    {
+        return get((Object)key);
+    }
+
+    /**
+     * Generic setter.
+     * @param key key
+     * @param value value
+     * @return previous value
+     */
+    public Object put(String key, Object value)
+    {
+        return extraValues.put(key, value);
+    }
+
+    /**
+     * Get the number of parameters.
+     * @return number of parameters
+     */
+    public int size()
+    {
+        return getSource().size() + extraValues.size();
+    }
+
+    /**
+     * Check for the presence of parameters.
+     * @return true if empty
+     */
+    public boolean isEmpty()
+    {
+        return getSource().isEmpty() && extraValues.isEmpty();
+    }
+
+    /**
+     * Check for the presence of a parameter.
+     * @param key parameter name
+     * @return true if present
+     */
+    public boolean containsKey(Object key)
+    {
+        return getSource().containsKey(key) || extraValues.containsKey(key);
+    }
+
+    /**
+     * Check for the presence of a value.
+     * @param value value to find
+     * @return true if present
+     */
+    public boolean containsValue(Object value)
+    {
+        String[] array = new String[1];
+
+        array[0] = (String)value;
+        return getSource().containsValue(array) || extraValues.containsValue(value);
+    }
+
+    /**
+     * Remove a parameter (from extra values).
+     * @param key parameter name
+     * @return value or null
+     */
+    public Object remove(Object key)
+    {
+        Object inner = null, extra = null;
+
+        if(getSource().containsKey(key))
+        {
+            inner = getSource().get(key);
+        }
+        extra = extraValues.remove(key);
+        return inner != null ? inner : extra;
+    }
+
+    /**
+     * Put all key/values from a map.
+     * @param map source map
+     */
+    public void putAll(Map map)
+    {
+        extraValues.putAll(map);
+    }
+
+    /**
+     * Clear extra parameters.
+     */
+    public void clear()
+    {
+        extraValues.clear();
+    }
+
+    /**
+     * Get the set of parameter names.
+     * @return set of names
+     */
+    public Set keySet()
+    {
+        Set ret = new HashSet();
+
+        ret.addAll(getSource().keySet());
+        ret.addAll(extraValues.keySet());
+        return ret;
+    }
+
+    /**
+     * Get the collection of values
+     * @return collection of values
+     */
+    public Collection values()
+    {
+        String[] array;
+        Collection ret = new HashSet();
+        Collection coll = getSource().values();
+
+        for(Object value : coll)
+        {
+            if(value.getClass().isArray())
+            {
+                array = (String[])value;
+                ret.add(array.length == 1 ? (Object)array[0] : array);
+            }
+            else
+            {
+                ret.add(value);
+            }
+        }
+        ret.addAll(extraValues.values());
+        return ret;
+    }
+
+    public Set<Entry<String, Object>> entrySet()
+    {
+        Map map = new HashMap();
+        Set<Entry<String, Object>> coll = getSource().entrySet();
+
+        for(Entry entry : coll)
+        {
+            Object value = entry.getValue();
+
+            if(value.getClass().isArray() && ((String[])value).length == 1)
+            {
+                value = ((String[])value)[0];
+            }
+            map.put(entry.getKey(), value);
+        }
+
+        Set<Entry<String, Object>> ret = new HashSet(map.entrySet());
+
+        ret.addAll(extraValues.entrySet());
+        return ret;
+    }
+
+    public String toString()
+    {
+        StringBuilder ret = new StringBuilder("{ ");
+
+        for(Entry entry : entrySet())
+        {
+            ret.append(String.valueOf(entry.getKey()));
+            ret.append('=');
+            ret.append(String.valueOf(entry.getValue()));
+            ret.append(' ');
+        }
+        ret.append('}');
+        return ret.toString();
+    }
+
+    public Set<String> getExtraKeys()
+    {
+        return extraValues.keySet();
+    }
+
+    /**
+     * Debugging method: returns a query string corresponding to query parameters
+     * Warning: it also includes POST parameters (so strictly speaking
+     *  it's not the real query string)
+     * @return reconstitued query string
+     */
+    public String getQueryString()
+    {
+        StringBuffer result = new StringBuffer();
+
+        for(Map.Entry entry : entrySet())
+        {
+            if(result.length() > 0)
+            {
+                result.append('&');
+            }
+            result.append(String.valueOf(entry.getKey()));
+            result.append('=');
+            result.append(String.valueOf(entry.getValue()));
+        }
+        return result.toString();
+    }
+
+    public Integer getInteger(String key)
+    {
+        Integer ret = super.getInteger(key);
+
+        /* try in extraValues */
+        if(ret == null)
+        {
+            Object v = extraValues.get(key);
+
+            if(v != null)
+            {
+                try
+                {
+                    ret = Integer.parseInt(String.valueOf(v));
+                }
+                catch(NumberFormatException nfe) {}
+            }
+        }
+        return ret;
+    }
+
+    /* TODO subclass other getXXX() methods */
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/VelosurfTool.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/VelosurfTool.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/VelosurfTool.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/VelosurfTool.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.tools.view.context.ViewContext;
+import org.apache.velocity.velosurf.context.DBReference;
+import org.apache.velocity.velosurf.sql.Database;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.ServletLogWriter;
+import org.apache.velocity.velosurf.util.ToolFinder;
+import org.apache.velocity.velosurf.util.UserContext;
+import org.apache.velocity.velosurf.util.XIncludeResolver;
+import org.apache.velocity.velosurf.web.l10n.Localizer;
+
+/**
+ * <p>This class is a tool meant to be referenced in toolbox.xml (VelocityTools View 1.4) or tools.xml (VelocityTools View 2.0+)</p>
+ * <p>It can be used in any scope you want (application/session/request), depending on the behaviour you need for the refinement and ordering mechanisms (which will follow the same scope).
+ * The initialization itself is very fast once all static initialization has been done, so there is no performance bottleneck when using request scope.</p>
+ * <p>You can have several instances of VelosurfTool, each with a distinct model configuration file.
+ * This can be useful to have one instance per schema, or one instance per database if dealing with several databases.</p>
+ * <p>For this to work, you have to use velocity-tools v1.3+, and give each instance the pathname of its configuration file
+ * via the 'config' parameter in the toolbox.xml file, like this for velocity-tools prior to 2.0:</p>
+ * <pre>
+ *
+ *  &lt;!-- first instance --&gt;
+ *  &lt;tool&gt;
+ *    &lt;key&gt;db1&lt;/key&gt;
+ *    &lt;scope&gt;request&lt;/scope&gt;
+ *    &lt;class&gt;velosurf.tools.VelosurfTool&lt;/scope&gt;
+ *    &lt;parameter name="config" value="WEB-INF/db1.xml" /&gt;
+ *  &lt;/tool&gt;
+ *
+ *  &lt;!-- second instance --&gt;
+ *  &lt;tool&gt;
+ *    &lt;key&gt;db2&lt;/key&gt;
+ *    &lt;scope&gt;request&lt;/scope&gt;
+ *    &lt;class&gt;velosurf.tools.VelosurfTool&lt;/scope&gt;
+ *    &lt;parameter name="config" value="WEB-INF/db2.xml" /&gt;
+ *   &lt;/tool&gt;
+ *
+ * </pre>
+ * <p>And like this for velocity-tools v2.0+ :</p>
+ * <pre>
+ *
+ *  &lt;toolbox scope="request"&gt;
+ *    &lt;!-- first instance --&gt;
+ *    &lt;tool key="db1" class="velosurf.tools.VelosurfTool" config="WEB-INF/db1.xml"/&gt;
+ *    &lt;!-- second instance --&gt;
+ *    &lt;tool key="db2" class="velosurf.tools.VelosurfTool" config="WEB-INF/db2.xml"/&gt;
+ *  &lt;/toolbox&gt;
+ * </pre>
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public class VelosurfTool extends DBReference
+{
+    /**
+     * build a new VelosurfTool.
+     */
+    public VelosurfTool(){}
+
+    /**
+     * Initialize this instance using the given ViewContext.
+     *
+     * @param viewContext initialization data
+     */
+    public void init(Object viewContext) throws Exception
+    {
+        // getservlet context
+        ViewContext viewctx = null;
+        ServletContext ctx = null;
+
+        if(viewContext instanceof ServletContext)
+        {
+            ctx = (ServletContext)viewContext;
+        }
+        else if(viewContext instanceof ViewContext)
+        {
+            viewctx = (ViewContext)viewContext;
+            ctx = viewctx.getServletContext();
+        }
+        else
+        {
+            Logger.error("Error: Initialization: no valid initialization data found: got a "
+                         + viewContext.getClass().getName());
+            System.err.println("Error: Initialization: no valid initialization data found!");
+        }
+        if(!Logger.isInitialized() && ctx != null)
+        {
+            Logger.setWriter(new ServletLogWriter(ctx));
+        }
+
+        // get config file
+        if(configFile == null)
+        {    // if not already given by configure()
+            configFile = findConfigFile(ctx);
+        }
+
+        UserContext userContext = null;
+
+        /* user context */
+        if(viewctx != null)
+        {
+            HttpSession session = viewctx.getRequest().getSession(false);
+
+            if(session != null)
+            {
+                synchronized(session)
+                {
+                    userContext = (UserContext)session.getAttribute(UserContext.USER_CONTEXT_KEY);
+                    if(userContext == null)
+                    {
+                        /* check in the database if already connected */
+                        Database db = dbMap.get(configFile);
+
+                        if(db != null)
+                        {
+                            userContext = db.getUserContext();
+                        }
+                        else
+                        {
+                            userContext = new UserContext();
+                        }
+                        session.setAttribute(UserContext.USER_CONTEXT_KEY, userContext);
+                        if(fetchLocalizer)
+                        {
+                            Localizer localizer = ToolFinder.findSessionTool(session, Localizer.class);
+
+                            if(localizer != null)
+                            {
+                                userContext.setLocalizer(localizer);
+                            }
+                            else
+                            {
+                                // don't search for it again
+                                fetchLocalizer = false;
+                                userContext.setLocale(viewctx.getRequest().getLocale());
+                            }
+                        }
+                        else
+                        {
+                            userContext.setLocale(viewctx.getRequest().getLocale());
+                        }
+                    }
+                }
+                session.setAttribute(VelosurfTool.class.getName(), this);
+            }
+        }
+
+        /* initialize with a new or existing connection */
+        Database db = getConnection(configFile, ctx);
+
+        if(db == null)
+        {
+            throw new SQLException("Could not connect to database.");
+        }
+        else
+        {
+            db.setUserContext(userContext);
+        }
+        super.init(db);
+    }
+
+    /**
+     * Tries to find the configuration file.
+     * @param ctx servelt context
+     * @return found config file or null
+     */
+    static String findConfigFile(ServletContext ctx)
+    {
+        /* tries with a servlet context parameter */
+        String configFile = ctx.getInitParameter(WEBAPP_CONFIG_FILE_KEY);
+
+        if(configFile != null)
+        {
+//          Logger.warn("Use of the "+WEBAPP_CONFIG_FILE_KEY+" servlet context parameter is deprecated.");
+//          Logger.warn("Consider moving this parameter to toolbox.xml.");
+            return configFile;
+        }
+        configFile = ctx.getInitParameter(WEBAPP_CONFIG_FILE_KEY2);
+        if(configFile != null)
+        {
+//          Logger.warn("Use of the "+WEBAPP_CONFIG_FILE_KEY+" servlet context parameter is deprecated.");
+//          Logger.warn("Consider moving this parameter to toolbox.xml.");
+            return configFile;
+        }
+
+        // else try default
+        InputStream check = ctx.getResourceAsStream(DEFAULT_CONFIG_FILE);
+
+        if(check == null)
+        {
+            check = ctx.getResourceAsStream(OLD_DEFAULT_CONFIG_FILE);
+            if(check == null)
+            {
+                throw new RuntimeException(
+                    "Velosurf config file not found! Please specify it using the servlet context or the toolbox parameters.");
+            }
+            else
+            {
+                configFile = OLD_DEFAULT_CONFIG_FILE;
+            }
+        }
+        else
+        {
+            configFile = DEFAULT_CONFIG_FILE;
+        }
+        return configFile;
+    }
+
+    /**
+     * initialization from a servlet context
+     *
+     */
+    protected void initialize(ServletContext ctx){}
+
+    /**
+     * key used in the deployment descriptor (web.xml) to set the name of the config file.
+     */
+    private static final String WEBAPP_CONFIG_FILE_KEY = "velosurf.config";
+
+    /**
+     * alternate webapp key
+     */
+    private static final String WEBAPP_CONFIG_FILE_KEY2 = "velosurf.configuration";
+
+    /**
+     * key used in the toolbox (toolbox.xml) to set the name of the config file.
+     */
+    private static final String TOOLBOX_CONFIG_FILE_KEY = "config";
+
+    /**
+     * alternate key for the toolbox
+     */
+    private static final String TOOLBOX_CONFIG_FILE_KEY2 = "configuration";
+
+    /**
+     * default database config file.
+     */
+    private static final String DEFAULT_CONFIG_FILE = "/WEB-INF/model.xml";
+
+    /**
+     * old default database config file.
+     */
+    private static final String OLD_DEFAULT_CONFIG_FILE = "/WEB-INF/velosurf.xml";
+
+    /**
+     * path to the config file.
+     */
+    private String configFile = null;
+
+    /**
+     * database connections.
+     */
+    private static Map<String, Database> dbMap = new HashMap<String, Database>();
+
+    /**
+     * configure.
+     *
+     * @param map parameters
+     */
+    public void configure(Map<String, String> map)
+    {
+        configFile = map.get(TOOLBOX_CONFIG_FILE_KEY);
+        if(configFile == null)
+        {
+            configFile = map.get(TOOLBOX_CONFIG_FILE_KEY2);
+        }
+    }
+
+    /**
+     * return the existing Database for the specified config file, or null
+     * if it isn't already open.
+     * @param configFile
+     * @return a Database
+     */
+    protected static Database getConnection(String configFile)
+    {
+        if(!configFile.startsWith("/"))
+        {
+            configFile = "/" + configFile;
+        }
+        return dbMap.get(configFile);
+    }
+
+    /**
+     * return a db reference on the existing Database for the specified config file, or null
+     * if it isn't already open.
+     * @param configFile
+     * @return a DBReference
+     */
+    public static DBReference getInstance(String configFile)
+    {
+        if(!configFile.startsWith("/"))
+        {
+            configFile = "/" + configFile;
+        }
+
+        Database db = dbMap.get(configFile);
+
+        return db == null ? null : new DBReference(db);
+    }
+
+    /**
+     * return the existing Database for the specified config file and servlet context,
+     * or null if an error occurs.
+     * @param configFile
+     * @return a Database
+     */
+    protected static Database getConnection(String configFile, ServletContext servletContext)
+    {
+        if(!configFile.startsWith("/"))
+        {
+            configFile = "/" + configFile;
+        }
+
+        Database db = (Database)dbMap.get(configFile);
+
+        if(db == null)
+        {
+            try
+            {
+                Logger.info("Using config file '" + configFile + "'");
+
+                InputStream is = servletContext.getResourceAsStream(configFile);
+
+                if(is == null)
+                {
+                    Logger.error("Could not read config file " + configFile);
+                    return null;
+                }
+
+                /* calculate the base directory, for XInclude */
+                /* Velosurf won't like '/' in windows names or '\' in linux ones... Does Java anyway? */
+                String base = null;
+
+                configFile = configFile.replace('\\', '/');
+
+                int i = configFile.lastIndexOf('/');
+
+                if(i == -1)
+                {
+                    base = ".";
+                }
+                else
+                {
+                    base = configFile.substring(0, i);
+                }
+                db = Database.getInstance(is, new XIncludeResolver(base, servletContext));
+                dbMap.put(configFile, db);
+            }
+            catch(Throwable e)
+            {
+                Logger.error("Could not get a connection!");
+                Logger.log(e);
+            }
+        }
+        return db;
+    }
+
+    /**
+     * return a db reference on the existing Database for the specified config file and servlet context,
+     * or null if an error occurs.
+     * @param configFile
+     * @return a DBReference
+     */
+    public static DBReference getInstance(String configFile, ServletContext servletContext)
+    {
+        Database db = getConnection(configFile, servletContext);
+
+        return db == null ? null : new DBReference(db);
+    }
+
+    /**
+     * return the existing Database for the default config file, or null
+     * if it does not already exist.
+     * @return a Database
+     */
+    protected static Database getDefaultConnection()
+    {
+        return(Database)dbMap.get(DEFAULT_CONFIG_FILE);
+    }
+
+    /**
+     * return a db reference the existing Database for the default config file, or null
+     * if it does not already exist.
+     * @return a DBReference
+     */
+    public static DBReference getDefaultInstance()
+    {
+        Database db = getDefaultConnection();
+
+        return db == null ? null : new DBReference(db);
+    }
+
+    /**
+     * return the existing Database for the default config file and servlet context,
+     * or null if an error occurs.
+     * @return a Database
+     */
+    protected static Database getDefaultConnection(ServletContext servletContext)
+    {
+        return getConnection(DEFAULT_CONFIG_FILE, servletContext);
+    }
+
+    /**
+     * return a db reference on the existing Database for the default config file
+     * or null if an error occurs.
+     * @return a Database
+     */
+    public static DBReference getDefaultInstance(ServletContext servletContext)
+    {
+        String configFile = findConfigFile(servletContext);
+
+        if(configFile == null)
+        {
+            throw new RuntimeException(
+                "VelosurfTool.getDefaultInstance: Configuration file not found! Please add a 'velosurf.config' servlet context parameter.");
+        }
+
+        Database db = getConnection(configFile, servletContext);
+
+        return db == null ? null : new DBReference(db);
+    }
+
+    /**
+     * do we need to try to fetch the localizer object?
+     * True initially, false after one unsuccessful try.
+     */
+    private static boolean fetchLocalizer = true;
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/AuthenticationFilter.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/AuthenticationFilter.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/AuthenticationFilter.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/AuthenticationFilter.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,601 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web.auth;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Locale;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.velosurf.util.*;
+import org.apache.velocity.velosurf.web.l10n.Localizer;
+
+/**
+ * <p>This class is a servlet filter used to protect web pages behind an authentication mechanism. When a
+ * non-authenticated user requests a private page, (s)he is redirected towards the login page and thereafter,
+ * if (s)he logged in successfully, towards his(her) initially requested page.</p>
+ *
+ * <p>Authentication is performed via a CRAM (challenge-response authentication mechanism).
+ * Passwords are encrypted using the method given as parameter to the Authenticator tool in toolbox.xml. The provided
+ *  Javascript file /src/javascript/md5.js implements the HmacMD5 method on the client side.</p>
+ *
+ * <p>This filter works in conjunction with an Authenticator object that must be present in the session scope
+ * of the toolbox and with a javascript password encryption function.</p>
+ *
+ * <p>To use it, you just have to map private urls (and especially, the target of the login form, this is
+ * very important for the authentication to work properly!) to go through this filter, as in :</p>
+ * <xmp>
+ *   <filter>
+ *     <filter-name>authentication</filter-name>
+ *     <filter-class>auth.AuthenticationFilter</filter-class>
+ *   </filter>
+ *   <filter-mapping>
+ *     <filter-name>authentication</filter-name>
+ *     <url-pattern>/auth/*</url-pattern>
+ *   </filter-mapping>
+ * </xmp>
+ *
+ * <p>The password is encrypted in an irreversible manner into an <i>answer</i>, and to check the login,
+ * the answer that the client sends back to the server is compared to the correct awaited answer.</p>
+ *
+ * <p>The filter expect the login to be present in the HTTP 'login' form field, and the answer in
+ * the 'answer' form field (which should be all right if you use the login.vjs as is). The action of the form
+ * is never used (since the filter will redirect the user towards the page asked before the login), but <b>it must
+ * be catched by an url-pattern of this filter</b>. You can for instance define a mapping towards "/process_login".</p>
+ *
+ * <p>The logged state is materialized by the presence of a user Object in the session under
+ * the <i>user</i> key. This user object in the one returned by the abstract method Authenticator.getUser(login).</p>
+ *
+ * <p>This filter will search for an occurrence of a localizer tool in the session toolbox to resolve some values.
+ * The presence of this localizer is optional.</p>
+ *
+ * <p>Optional configuration parameters:
+ * <ul>
+ * <li><code>login-field</code>: name of the login form field (default: login).</li>
+ * <li><code>password-field</code>: name of the password field (default: password).</li>
+ * <li><code>login-key</code>: name of the session key used to store the login of the logged user.</li>
+ * <li><code>user-key</code>: name of the session key used to store a reference to the logged user object.</li>
+ * <li><code>max-inactive</code>: delay upon which an inactive user is disconnected in seconds.
+ * The default value is one hour.</li>
+ * <li><code>login-page</code>: the login page URI. The "<code>@</code>" pattern applies as well. Default is '/login.html'.</li>
+ * <li><code>authenticated-index-page</code>: the default page once authenticated. The "<code>@</code>" pattern applies as well.
+ * Default is '/logged.html'.</li>
+ * <li><code>bad-login-message</code>: the message to be displayed in case of bad login. If this parameter is not
+ * specified, the filter will try to get a reference from the localizer tool and ask it for a "badLogin"
+ * message, and if this fails, it will simply use "Bad login or password.".</li>
+ * <li><code>disconnected-message</code>: the message to be displayed when the user is disconnected after a period
+ * of inactivity on the site. Same remark if this parameter is not supplied: the filter will search
+ * for a "disconnected" message in the localizer tool if present, and otherwise display "You have been disconnected."</li>
+ * <li><code>allow-guest</code>: allow the login "guest" (false by default) - the password is not checked, the "guest" user must exist in the database.
+ * The only allowed uri is the login page, and it can be used from VTL:<br/> <code>#macro (redirect $url) $response.sendRedirect($url) #end #redirect("/login.do?login=guest&password=whatever")</code><br/>
+ * you can also include a "redirect=" parameter</li>
+ * <li><code>use-login-referer</code>: (sorry for reproducing the bugguy syntax from the HTTP RFC, it should be 'referrer' :-) ) use the HTTP request referrer after login if there is no saved request (default: no).</li>
+ * </ul>
+ * </p>
+ *
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ *
+ */
+
+public class AuthenticationFilter implements Filter
+{
+
+    /**
+     * Filter config.
+     */
+    private FilterConfig config = null;
+
+    /**
+     * Max inactive interval.
+     */
+    private int maxInactive = 3600;
+
+    /**
+     * Login field.
+     */
+    private String loginField = "login";
+
+    /**
+     * Password field.
+     */
+    private String passwordField = "password";
+
+    /**
+     * Login page.
+     */
+    private String loginPage = "/login.vhtml";
+
+    /**
+     * Index of the authenticated zone.
+     */
+    private String authenticatedIndexPage = "/index.vhtml";
+
+    /**
+     * Message in case of bad login.
+     */
+    private String badLoginMessage = null;
+
+    /**
+     * Message key in case of bad login.
+     */
+    private String badLoginMsgKey = "badLogin";
+
+    /**
+     * Default bad login message.
+     */
+    private static final String defaultBadLoginMessage = "Bad login or password.";
+
+    /**
+     * Message in case of disconnection.
+     */
+    private String disconnectedMessage = null;
+
+    /**
+     * Message key in case of disconnection.
+     */
+    private String disconnectedMsgKey = "disconnected";
+
+    /**
+     * Default message in case of disconnection.
+     */
+    private static final String defaultDisconnectedMessage = "You have been disconnected.";
+
+    /**
+     * Session key used to store logged user login
+     */
+    private String LOGIN = "velosurf.auth.login";
+
+    /**
+     * Session key used to store logged user object
+     */
+    private String USER = "velosurf.auth.user";
+
+    /**
+     * Session key used to store original pre-login request
+     */
+    public static final String REQUEST = "velosurf.auth.saved-request";
+
+    /**
+     * Should we use the referer to login.do?
+     */
+    private boolean useLoginReferer = false;
+
+    /**
+     * Should we use the referer to login.do?
+     */
+    private boolean allowGuest = false;
+
+    /**
+     * Initialization.
+     * @param config filter config
+     * @throws ServletException
+     */
+    public void init(FilterConfig config) throws ServletException
+    {
+        this.config = config;
+
+        /* logger initialization */
+        if (!Logger.isInitialized())
+        {
+            Logger.setWriter(new ServletLogWriter(config.getServletContext()));
+        }
+
+        /* max-inactive */
+        String param = this.config.getInitParameter("max-inactive");
+        if (param != null)
+        {
+            try
+            {
+                maxInactive = Integer.parseInt(param);
+            }
+            catch (NumberFormatException nfe)
+            {
+                Logger.error("AuthenticationFilter: bad format for the max-inactive parameter: "+param);
+            }
+        }
+
+        /* login field */
+        param = this.config.getInitParameter("login-field");
+        if (param != null)
+        {
+            loginField = param;
+        }
+
+        /* password field */
+        param = this.config.getInitParameter("password-field");
+        if (param != null)
+        {
+            passwordField = param;
+        }
+
+        /* login session key */
+        param = this.config.getInitParameter("login-key");
+        if (param != null)
+        {
+            LOGIN = param;
+        }
+
+        /* user session key */
+        param = this.config.getInitParameter("user-key");
+        if (param != null)
+        {
+            USER = param;
+        }
+
+        /* login page */
+        param = this.config.getInitParameter("login-page");
+        if (param != null)
+        {
+            loginPage = param;
+        }
+
+        /* authenticated index page */
+        param = this.config.getInitParameter("authenticated-index-page");
+        if (param != null)
+        {
+            authenticatedIndexPage = param;
+        }
+
+        /* bad login message */
+        badLoginMessage = this.config.getInitParameter("bad-login-message");
+
+        /* disconnected message */
+        disconnectedMessage = this.config.getInitParameter("disconnected-message");
+
+        /* use login referer */
+        param = this.config.getInitParameter("use-login-referer");
+        useLoginReferer = (param != null && Boolean.parseBoolean(param));
+
+	/* allow guests */
+        param = this.config.getInitParameter("allow-guest");
+        allowGuest = (param != null && Boolean.parseBoolean(param));
+
+    }
+
+    /**
+     * Filtering.
+     * @param servletRequest request
+     * @param servletResponse response
+     * @param chain filter chain
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest)servletRequest;
+        HttpSession session = request.getSession(false);
+        HttpServletResponse response = (HttpServletResponse)servletResponse;
+
+        String uri = request.getRequestURI();
+
+        String login = null, password = null;
+
+        if (session != null
+                && session.getId().equals(request.getRequestedSessionId()) /* not needed in theory */
+                && session.getAttribute(USER) != null)
+        {
+            /* already logged*/
+
+            /* need to refresh cached user instance in case it changed */
+            refreshUserInstance(session);
+            
+            /* if asked to logout, well, logout! */
+            if (uri.endsWith("/logout.do"))
+            {
+				doLogout(request,response,chain);
+            }
+            else
+            {
+                doProcessAuthentified(request,response,chain);
+            }
+        }
+        else
+        {
+            /* never protect the login page itself */
+            if (uri.equals(resolveLocalizedUri(request,loginPage)))
+            {
+                chain.doFilter(request,response);
+                return;
+            }
+            if (session == null)
+            {
+                session = request.getSession(true);
+            }
+            else
+            {
+                /* clear any previous loginMessage */
+                session.removeAttribute("loginMessage");
+            }
+            session.removeAttribute(USER);
+            session.removeAttribute(LOGIN);
+            if ( uri.endsWith("/login.do")
+                    && (login = request.getParameter(loginField)) != null
+                    && (password = request.getParameter(passwordField)) != null
+                    && session.getId().equals(request.getRequestedSessionId()))
+            {
+                // a user is trying to log in
+
+                if(allowGuest && login.equals("guest"))
+                { // TODO parametrize "guest" -> GUEST
+                    Logger.trace("[auth] logging in guest user");
+                    doLogin(request,response,chain);
+                }
+                else
+                {
+                    // get a reference to the authenticator tool
+                    BaseAuthenticator auth = ToolFinder.findSessionTool(session,BaseAuthenticator.class);
+                    if (auth == null)
+                    {
+                        Logger.error("[auth] cannot find any reference to the authenticator tool in the session!");
+                        /* Maybe the current user tried to validate an expired login form... well... ask him again... */
+                        response.sendRedirect(resolveLocalizedUri(request,loginPage));
+                        return;
+                    }
+                    // check answer
+                    if (auth.checkLogin(login,password))
+                    {
+                        // login ok
+                        doLogin(request,response,chain);
+                    }
+                    else
+                    {
+                        badLogin(request,response,chain);
+                    }
+                }
+
+            }
+            else
+            {
+                /* do not redirect to the logout */
+                if (uri.endsWith("/logout.do"))
+                {
+                    response.sendRedirect(resolveLocalizedUri(request,loginPage));
+                }
+                else
+                {
+                    doRedirect(request,response,chain);
+                }
+            }
+        }
+    }
+
+    protected void refreshUserInstance(HttpSession session)
+    {
+        session.setAttribute(USER, ToolFinder.findSessionTool(session,BaseAuthenticator.class).getUser((String)session.getAttribute(LOGIN)));
+    }
+
+    protected void doRedirect(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        /* save the original request */
+        String uri = request.getRequestURI();
+        Logger.trace("[auth] saving request towards "+uri);
+        HttpSession session = request.getSession();
+        session.setAttribute(REQUEST,SavedRequest.saveRequest(request));
+
+        /* check to see if the current user has been disconnected
+           note that this test will fail when the servlet container
+           reuses session ids */
+        boolean disconnected = false;
+        String reqId = request.getRequestedSessionId();
+        if (reqId != null && (session == null || !session.getId().equals(reqId)))
+        {
+            disconnected = true;
+        }
+
+        if(disconnected)
+        {
+             String message = disconnectedMessage != null ?
+                 disconnectedMessage :
+                 getMessage(ToolFinder.findSessionTool(session,Localizer.class),disconnectedMsgKey,defaultDisconnectedMessage);
+             session.setAttribute("loginMessage",message);
+        }
+        // redirect to login page
+        String loginPage = resolveLocalizedUri(request,this.loginPage);
+        Logger.trace("[auth] redirecting unauthenticated user to "+loginPage);
+        response.sendRedirect(loginPage);
+    }
+
+    protected void doLogin(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        String login = request.getParameter(loginField);
+        Logger.info("[auth] user '"+login+"' successfully logged in.");
+        HttpSession session = request.getSession();
+        session.setAttribute(USER, ToolFinder.findSessionTool(session,BaseAuthenticator.class).getUser(login));
+        session.setAttribute(LOGIN,login);
+        if (maxInactive > 0)
+        {
+            Logger.trace("[auth] setting session max inactive interval to "+maxInactive);
+             session.setMaxInactiveInterval(maxInactive);
+        }
+        session.removeAttribute("challenge");
+        session.removeAttribute("authenticator");
+        // then handle the former request if not null
+        goodLogin(request,response,chain);
+    }
+
+    protected void goodLogin(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpSession session = request.getSession();
+
+        // trying to use the "redirect=" parameter
+        String redirect = request.getParameter("redirect");
+        Logger.trace("[auth] redirect = "+redirect);
+        if(redirect != null)
+        {
+          Logger.trace("[auth] redirecting newly logged user to 'redirect' param: "+redirect);
+          response.sendRedirect(redirect);
+          return;
+        }
+
+        SavedRequest savedRequest = (SavedRequest)session.getAttribute(REQUEST);
+        if (savedRequest == null || savedRequest.getRequestURI().endsWith("/login.do"))
+        {
+            // try to redirect to the referrer url
+            if(useLoginReferer)
+            {
+                String referer = request.getHeader("Referer");
+                if(referer != null)
+                { // TODO: some referer URLs should be avoided (login.do, logout.do...)
+                    // only keep path and query
+                    URL url = new URL(referer);
+                    String path = url.getPath();
+                    String query = url.getQuery();
+                    String anchor = url.getRef();
+                    String dest = path+(query != null && query.length()>0?"?"+query:"")+(anchor != null && anchor.length()>0?"#"+anchor:"");
+                    Logger.trace("[auth] redirecting newly logged user to login.do referer: "+dest);
+                    response.sendRedirect(dest);
+                    return;
+                }
+            }
+
+            // redirect to /auth/index.html
+            String authIndex = resolveLocalizedUri(request,getAuthenticatedIndexPage(session));
+            Logger.trace("[auth] redirecting newly logged user to "+authIndex);
+            response.sendRedirect(authIndex);
+        }
+        else
+        {
+            session.removeAttribute(REQUEST);
+            String formerUrl = savedRequest.getRequestURI();
+            String query =  savedRequest.getQueryString();
+            query = (query == null ? "" : "?"+query);
+            formerUrl += query;
+            Logger.trace("[auth] redirecting newly logged user to "+formerUrl);
+            response.sendRedirect(formerUrl);
+        }
+    }
+
+    protected void badLogin(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        Logger.warn("[auth] user " + request.getParameter(loginField) + " made an unsuccessfull login attempt.");
+        HttpSession session = request.getSession();
+        String message = badLoginMessage != null ?
+            badLoginMessage :
+            getMessage(ToolFinder.findSessionTool(session,Localizer.class),badLoginMsgKey,defaultBadLoginMessage);
+        session.setAttribute("loginMessage",message);
+        // redirect to login page
+        String loginPage = resolveLocalizedUri(request,this.loginPage);
+        Logger.trace("[auth] redirecting unauthenticated user to "+loginPage);
+        response.sendRedirect(loginPage);
+    }
+
+    protected void doProcessAuthentified(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        /* if the request is still pointing on /login.html or /login.do, redirect to /auth/index.html */
+        String uri = request.getRequestURI();
+        HttpSession session = request.getSession();
+        if (uri.equals(resolveLocalizedUri(request,loginPage)) || uri.endsWith("/login.do"))
+        {
+            goodLogin(request,response,chain);
+        }
+        else
+        {
+            Logger.trace("[auth] user is authenticated.");
+            SavedRequest saved = (SavedRequest)session.getAttribute(REQUEST);
+            if (saved != null && saved.getRequestURL().equals(request.getRequestURL()))
+            {
+                session.removeAttribute(REQUEST);
+                chain.doFilter(new SavedRequestWrapper(request,saved),response);
+            }
+            else
+            {
+               chain.doFilter(request,response);
+            }
+        }
+    }
+
+    protected void doLogout(HttpServletRequest request,HttpServletResponse response,FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpSession session = request.getSession();
+        Logger.trace("[auth] user logged out");
+        session.removeAttribute(USER);
+        session.removeAttribute(LOGIN);
+        String loginPage = resolveLocalizedUri(request,this.loginPage);
+        response.sendRedirect(loginPage);
+    }
+
+
+    protected String resolveLocalizedUri(HttpServletRequest request,String uri)
+    {
+        if (uri.indexOf('@')!=-1)
+        {
+            /* means the uri need the current locale */
+            Locale locale = null;
+            HttpSession session = request.getSession();
+            if (session != null)
+            {
+                locale = (Locale)session.getAttribute("velosurf.l10n.active-locale"); /* TODO: gather 'active-locale' handling in HTTPLocalizerTool */
+            }
+
+            if (locale == null)
+            {
+                Logger.error("[auth] cannot find the active locale in the session!");
+                Logger.error("[auth] the LocalizationFilter must reside before this filter in the filters chain.");
+                //response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return uri;
+            }
+            uri = uri.replaceAll("@",locale.toString());
+        }
+        return uri;
+    }
+
+    protected String getAuthenticatedIndexPage(HttpSession session)
+    {
+        return authenticatedIndexPage;
+    }
+
+    /**
+     * Message getter.
+     * @param localizer localizer
+     * @param key key
+     * @param defaultMessage default message
+     * @return localized message or default message
+     */
+    protected String getMessage(Localizer localizer,String key,String defaultMessage)
+    {
+        String message = null;
+        if (localizer != null)
+        {
+            message = localizer.get(key);
+        }
+        return message == null || message.equals(key) ? defaultMessage : message;
+    }
+
+    /**
+     * Destroy the filter.
+     */
+    public void destroy()
+    {
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/BaseAuthenticator.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/BaseAuthenticator.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/BaseAuthenticator.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/BaseAuthenticator.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web.auth;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Random;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.tools.view.context.ViewContext;
+import sun.misc.BASE64Decoder;
+import sun.misc.BASE64Encoder;
+import org.apache.velocity.velosurf.util.Logger;
+
+/**
+ * This abstract class implements an authentication mechanism. It is meant to be declared
+ * in toolbox.xml as a session-scoped tool.
+ *
+ * The password encryption method can be specified in <code>toolbox.xml</code> using the <code>method</code> parameter
+ * (when not specified, passwords are passed in clear).
+ *
+ * You will need to implement the same password encryption on the client side using the adequate
+ * javascript files. A <code>/src/javascript/md5.js</code> file is provided to help implementing the HmacMD5 method.
+ *
+ * Still, if you really want security, use HTTPS!
+ *
+ *  @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+public abstract class BaseAuthenticator
+{
+    /**
+     * get the password corresponding to a login.
+     * @param login login
+     * @return password or null
+     */
+    public abstract String getPassword(String login);
+
+    /**
+     * Get the user object corresponding to a login
+     * @param login login
+     * @return user object
+     */
+    public abstract Object getUser(String login);
+
+    /** encryption method */
+    private String method = null;
+
+    /** challenge value */
+    private String challenge = null;
+
+    /** random number generator */
+    private static Random random = new Random(System.currentTimeMillis());
+
+    /** length of challenge */
+    private static final int CHALLENGE_LENGTH = 256;    // bits
+
+    /** keep a reference on the session */
+    private WeakReference<HttpSession> session = null;
+
+    /**
+     * initialize this tool.
+     * @param initData a view context
+     */
+    public void init(Object initData)
+    {
+        if(!(initData instanceof ViewContext))
+        {
+            Logger.error("auth: authenticator tool should be used in a session scope! (received init data of class: "
+                         + (initData == null ? "null" : initData.getClass().getName()) + ")");
+        }
+
+        HttpSession s = ((ViewContext)initData).getRequest().getSession(true);
+
+        session = new WeakReference<HttpSession>(s);
+        s.setAttribute(BaseAuthenticator.class.getName(), this);
+    }
+
+    /**
+     * configure this tool.
+     * @param config map containing an optional "method" parameter
+     */
+    public void configure(Map config)
+    {
+        method = (String)config.get("method");
+    }
+
+    /**
+     * This method generates a new challenge each time it is called.
+     *
+     * @return a new 1024-bit challenge in base64
+     */
+    public String getChallenge()
+    {
+        BigInteger bigint = new BigInteger(CHALLENGE_LENGTH, random);
+
+        challenge = new sun.misc.BASE64Encoder().encode(bigint.toByteArray());
+        challenge = challenge.replace("\n", "");
+        Logger.trace("auth: generated new challenge: " + challenge);
+        return challenge;
+    }
+
+    /**
+     * Check received answer.
+     *
+     * @param login  login
+     * @param answer received answer
+     * @return true if received answer is valid
+     */
+    public boolean checkLogin(String login, String answer)
+    {
+        String password = getPassword(login);
+
+        if(password == null)
+        {
+            /* password not found */
+            Logger.trace("auth: login " + login + " does not exist");
+            return false;
+        }
+        if(password.length() == 0 && answer.length() == 0)
+        {
+            return true;
+        }
+
+        String correctAnswer = generateAnswer(password);
+
+        Logger.trace("auth: received=" + answer);
+        Logger.trace("auth: correct =" + correctAnswer);
+        return(correctAnswer != null && correctAnswer.equals(answer));
+    }
+
+    /**
+     * Generate the correct answer.
+     * @param password
+     * @return encrypted answer
+     */
+    private String generateAnswer(String password)
+    {
+        if(method == null)
+        {
+            return password;
+        }
+        else if(challenge == null)
+        {
+            /* return something that will never match any password */
+            return getChallenge();
+        }
+        else
+        {
+            Logger.debug("auth: using method " + method);
+            try
+            {
+                /*
+                 *  TODO: use utf8 (and find a way to convert an utf8 string into
+                 *  an array of bytes on the javascript counterpart)
+                 */
+                Mac mac = Mac.getInstance(method);
+
+                mac.init(new SecretKeySpec(password.getBytes("ISO-8859-1"), method));
+
+                byte[] hash = mac.doFinal(challenge.getBytes("ISO-8859-1"));
+                String encoded = new BASE64Encoder().encode(hash);
+
+                /* strips the last(s) '=' */
+                int i;
+
+                while((i = encoded.lastIndexOf('=')) != -1)
+                {
+                    encoded = encoded.substring(0, i);
+                }
+                return encoded;
+            }
+            catch(NoSuchAlgorithmException nsae)
+            {
+                Logger.error("auth: could not find algorithm '" + method + "'");
+                Logger.log(nsae);
+            }
+            catch(Exception e)
+            {
+                Logger.error("auth: an unknown error occurred...");
+                Logger.log(e);
+            }
+        }
+        return null;
+    }
+
+    public Object getLoggedUser()
+    {
+        if(session == null)
+        {
+            return null;
+        }
+
+        HttpSession sess = session.get();
+
+        if(sess == null)
+        {
+            return null;
+        }
+        return sess.getAttribute("velosurf.auth.user");
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/SimpleDBAuthenticator.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/SimpleDBAuthenticator.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/SimpleDBAuthenticator.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/SimpleDBAuthenticator.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web.auth;
+
+import java.util.Map;
+import javax.servlet.ServletContext;
+import org.apache.velocity.tools.view.context.ViewContext;
+import org.apache.velocity.velosurf.context.DBReference;
+import org.apache.velocity.velosurf.context.Instance;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.web.VelosurfTool;
+import org.apache.velocity.velosurf.web.auth.BaseAuthenticator;
+
+/**
+ * <p>Authenticator basic implementation.</p>
+ * <p>It accepts the four following parameters in <code>toolbox.xml</code>:</p>
+ * <ul>
+ * <li><code>method</code> (inherited from <code>BaseAuthenticator</code>) the encryption method to use (default to none,
+ * an example client-side javascript encryption is provided for the method HmacMD5).</li>
+ * <li><code>user-by-login</code> name of the Velosurf root attribute that returns a user given its login.</li>
+ * <li><code>login-parameter</code> name of the external parameter 'login' in the previous attribute.</li>
+ * <li><code>password-field</code> name of the password field.</li>
+ * </ul>
+ *
+ *  @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+public class SimpleDBAuthenticator extends BaseAuthenticator
+{
+    /** database. */
+    protected DBReference db = null;
+
+    /** key used in toolbox.xml to indicate the "user by login" root attribute. */
+    private static final String USER_BY_LOGIN_KEY = "user-by-login";
+
+    /** key used in toolbox.xml to indicate the name of the login parameter in the "user by login" attribute. */
+    private static final String LOGIN_PARAMETER_KEY = "login-parameter";
+
+    /** key used in toolbox.xml to indicate the name of the password field in the "user by login" attribute. */
+    private static final String PASSWORD_FIELD_KEY = "password-field";
+
+    /** default name of the "user by login" root attribute. */
+    private static final String USER_BY_LOGIN_DEFAULT = "user_by_login";
+
+    /** default name for the "login" parameter. */
+    private static final String LOGIN_PARAMETER_DEFAULT = "login";
+
+    /** default name of the "password" field. */
+    private static final String PASSWORD_FIELD_DEFAULT = "password";
+
+    /** configuration. */
+    private Map config = null;
+
+    /** "user by login" root attribute name. */
+    private String userByLogin = USER_BY_LOGIN_DEFAULT;
+
+    /** login parameter name */
+    private String loginParameter = LOGIN_PARAMETER_DEFAULT;
+
+    /** password field name */
+    private String passwordField = PASSWORD_FIELD_DEFAULT;
+
+    /**
+     * initialize this tool.
+     * @param initData a view context
+     */
+    public void init(Object initData)
+    {
+        super.init(initData);
+
+        // init only if there was no error in super class
+        if(initData instanceof ViewContext)
+        {
+            if(db == null)
+            {
+                initDB(((ViewContext)initData).getServletContext());
+            }
+        }
+        if(config != null)
+        {
+            String value;
+
+            value = (String)config.get(USER_BY_LOGIN_KEY);
+            if(value != null)
+            {
+                userByLogin = value;
+            }
+            value = (String)config.get(PASSWORD_FIELD_KEY);
+            if(value != null)
+            {
+                passwordField = value;
+            }
+            value = (String)config.get(LOGIN_PARAMETER_KEY);
+            if(value != null)
+            {
+                loginParameter = value;
+            }
+        }
+    }
+
+    protected void initDB(ServletContext ctx)
+    {
+        db = VelosurfTool.getDefaultInstance(ctx);
+    }
+
+    /**
+     * externally set the db reference
+     * @param db DBReference
+     */
+    public void setDBReference(DBReference db)
+    {
+        this.db = db;
+    }
+
+    /**
+     * get the password for this login.
+     * @param login login
+     * @return password or null
+     */
+    public String getPassword(String login)
+    {
+        Map user = null;
+
+        synchronized(db)
+        {
+            db.put(loginParameter, login);
+            user = (Map)db.get(userByLogin);
+        }
+        if(user != null)
+        {
+            return(String)user.get(passwordField);
+        }
+        return null;
+    }
+
+    /**
+     * get the user object for this login.
+     * @param login login
+     * @return user object
+     */
+    public Object getUser(String login)
+    {
+        synchronized(db)
+        {
+            db.put(loginParameter, login);
+            return db.get(userByLogin);
+        }
+    }
+
+    /**
+     * configure this tool.
+     * @param map
+     */
+    public void configure(Map map)
+    {
+        super.configure(map);
+        config = map;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/auth/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains classes related to authentication.
+
+<h2>Package Specification</h2>
+
+<!-- ANY SPECS NEEDED BY JAVA COMPATIBILITY KIT GO THERE
+<ul>
+  <li><a href="">##### REFER TO ANY FRAMEMAKER SPECIFICATION HERE #####</a>
+</ul>
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="">##### REFER TO NON-SPEC DOCUMENTATION HERE #####</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/HTTPLocalizerTool.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/HTTPLocalizerTool.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/HTTPLocalizerTool.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/HTTPLocalizerTool.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.apache.velocity.velosurf.web.l10n;
+
+import java.lang.ref.WeakReference;
+import java.text.MessageFormat;
+import java.util.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.tools.view.context.ViewContext;
+import org.apache.velocity.velosurf.util.Logger;
+
+/**
+ * <p>This class rely on the "Accepted-Language" HTTP header to detect
+ *  the appropriate locale to be used.</p>
+ *
+ * <p>This tool accepts a "default-locale" configuration parameter in toolbox.xml.</p>
+ * <p>It is meant for the session scope.</p>
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public abstract class HTTPLocalizerTool implements Localizer
+{
+    /**
+     * Initialize this tool.
+     * @param initData a view context
+     */
+    public void init(Object initData)
+    {
+        if(initData instanceof ViewContext)
+        {
+            HttpSession session = ((ViewContext)initData).getRequest().getSession(true);
+
+            if(session != null)
+            {
+                this.session = new WeakReference<HttpSession>(session);
+
+                Locale locale = (Locale)session.getAttribute("velosurf.l10n.active-locale");
+
+                if(locale == null)
+                {
+                    /* means the localization filter did not intercept this query */
+                    locale = getBestLocale(listFromEnum(((ViewContext)initData).getRequest().getLocales()));
+                    Logger.trace("l10n: unlocalized page - using locale " + locale);
+                }
+                setLocale(locale);
+            }
+        }
+        else
+        {
+            Logger.error("l10n: Localizer tool should be used in a session scope!");
+            return;
+        }
+    }
+
+    /**
+     * Transform an enumeration into a list of locales.
+     * @param e enumeration
+     * @return a list of locales
+     */
+    private static List<Locale> listFromEnum(Enumeration e)
+    {
+        List<Locale> list = new ArrayList<Locale>();
+
+        while(e.hasMoreElements())
+        {
+            list.add((Locale)e.nextElement());
+        }
+        return list;
+    }
+
+    /**
+     * Get the locale best matching available localized data among a list.
+     * @param locales list of input locales
+     * @return best matching locale
+     */
+    public Locale getBestLocale(List<Locale> locales)
+    {
+        for(Locale locale : locales)
+        {
+            if(hasLocale(locale))
+            {
+                return locale;
+            }
+        }
+
+        /* second pass without the country code */
+        for(Locale locale : locales)
+        {
+            String country = locale.getCountry();
+
+            if(country != null && country.length() > 0)
+            {
+                Locale l = new Locale(locale.getLanguage());
+
+                if(hasLocale(l))
+                {
+                    return l;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check for the presence of a locale.
+     * @param locale locale to check
+     * @return true if present
+     */
+    public abstract boolean hasLocale(Locale locale);
+
+    /**
+     * Current locale setter.
+     * @param locale locale
+     */
+    public void setLocale(Locale locale)
+    {
+        this.locale = locale;
+    }
+
+    /**
+     * Current lcoale getter.
+     *
+     * @return current locale
+     */
+    public Locale getLocale()
+    {
+        checkLocaleChange();
+        return locale;
+    }
+
+    /**
+     *  Check that the locale has not changed in the session.
+     */
+    public void checkLocaleChange()
+    {
+        if(session == null) return;
+        HttpSession s = session.get();
+
+        if(s != null)
+        {
+            Locale l = (Locale)s.getAttribute("velosurf.l10n.active-locale");
+
+            if(l != null &&!l.equals(locale))
+            {
+                setLocale(l);
+            }
+        }
+    }
+
+    /**
+     * Get the localized message for this key.
+     * @param id message key
+     * @return localized message (or id if not found).
+     */
+    public abstract String get(Object id);
+
+    /**
+     * Get the localized parameterized message for this key.
+     * @param id message key
+     * @param params message parameters
+     * @return localized message (or id if not found).
+     */
+    public String get(Object id, Object... params)
+    {
+        String message = get(id).replaceAll("'", "''");
+
+        return MessageFormat.format(message, params);
+    }
+
+    /** keep a reference on the session */
+    private WeakReference<HttpSession> session = null;
+
+    /** current locale */
+    protected Locale locale = null;
+}