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 [13/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/web/l10n/LocalizationFilter.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/LocalizationFilter.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/LocalizationFilter.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/LocalizationFilter.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,560 @@
+/*
+ * 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.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.*;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.ServletLogWriter;
+import org.apache.velocity.velosurf.util.StringLists;
+import org.apache.velocity.velosurf.util.ToolFinder;
+
+/**
+ * <p>Localization filter. It's goal is to redirect or forward incoming unlocalized http requests (depending on the
+ * choosen method, <code>FORWARD</code> or <code>REDIRECT</code>) towards an address taking into account the best match
+ * between requested locales and supported locales, and also to deduce the locale from URLS (when <code>REDIRECT</code>
+ * is used).</p>
+ *
+ * <p>Optional init parameters:
+ * <ul>
+ * <li><code>supported-locales</code>: comma separated list of supported locales ; if not provided, there is an attempt to programatically determine it<sup>(1)</sup>.
+ * No default value provided.</li>
+ * <li><code>default-locale</code>: the locale to be used by default (after four checks: the incoming URI, the session, cookies, and the request headers).
+ * No default value provided.</li>
+ * <li><code>localization-method</code>: <code>forward</code> or <code>redirect</code>, default is <code>redirect</code>.
+ * <li><code>match-host</code> & <code>rewrite-host</code>: not yet implemented.<sup>(2)</sup></li>
+ * <li><code>match-uri</code> & <code>rewrite-uri</code>: the regular expression against which an unlocalized uri is matched, and the replacement uri, where
+ * <code>@</code> represents the locale and $1, $2, ... the matched sub-patterns. Defaults are <code>^/(.*)$</code>
+ * for <code>match-uri</code> and <code>/@/$1</code> for rewrite-uri.(2)</li>
+ * <li><code>match-query-string</code> & <code>rewrite-query-string</code>: not yet implemented.(2)</li>
+ * <li><code>match-url</code> & <code>rewrite-url</code>: not yet implemented.(2)</li>
+ * </ul>
+ * </p>
+ *
+ * <p><b><small>(1)</small></b> for now, to find supported locales if this parameter is not provided,
+ * the filter try to use the <code>rewrite-uri</code> param and to check for the existence of corresponding directories (only if the rewriting
+ * string contains a pattern like '/@/', that is if you use directories to store localized sites).
+ * <p><b><small>(2)</small></b> The different <code>match-</code> and <code>rewrite-</code> parameters pairs are mutually exclusive.
+ * All matches are case-insensitive.</p>
+ *
+ * <p>When the <code>redirect</code> method is used, these supplementary parameters (mutually exclusive) allow the filter to
+ * know whether or not an incoming URI is localized.
+ * <ul>
+ * <li><code>inspect-host</code>: not yet implemented.</li>
+ * <li><code>inspect-uri</code>: default is <code>^/(.+)(?:/|$)</code>.
+ * <li><code>inspect-query-string</code>: not yet implemented.</li>
+ * <li><code>inspect-url</code>: not yet implemented.</li>
+ * </ul>
+ * </p>
+ *
+ *  @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+public class LocalizationFilter implements Filter
+{
+    /** filter config. */
+    private FilterConfig config = null;
+
+    /** supported locales. */
+    private List<Locale> supportedLocales = null;
+
+    /** default locale. */
+    private Locale defaultLocale = null;
+
+    /** seconds in year (for setting cookies age). */
+    private static int SECONDS_IN_YEAR = 31536000;
+
+    /** default match uri. */
+    private static String defaultMatchUri = "^/(.*)$";
+
+    /** default rewrite uri. */
+    private static String defaultRewriteUri = "/@/$1";
+
+    /** default inspect uri. */
+    private static String defaultInspectUri = "^/(.+)(?:/|$)";
+
+    /** match uri. */
+    private Pattern matchUri = null;
+
+    /** rewrite uri */
+    private String rewriteUri = null;
+
+    /** inspect uri. */
+    private Pattern inspectUri = null;
+
+    /** forward method constant. */
+    private static final int FORWARD = 1;
+
+    /** redirect method constant. */
+    private static final int REDIRECT = 2;
+
+    /** localization method. */
+    private int l10nMethod = REDIRECT;
+
+    /**
+     * initialization.
+     *
+     * @param config filter config
+     * @throws ServletException
+     */
+    public synchronized void init(FilterConfig config) throws ServletException
+    {
+        this.config = config;
+
+        /* logger initialization */
+        if(!Logger.isInitialized())
+        {
+            Logger.setWriter(new ServletLogWriter(config.getServletContext()));
+        }
+
+        String param;
+
+        /* uri */
+        matchUri = Pattern.compile(getInitParameter("match-uri", defaultMatchUri), Pattern.CASE_INSENSITIVE);
+        rewriteUri = getInitParameter("rewrite-uri", defaultRewriteUri);
+        inspectUri = Pattern.compile(getInitParameter("inspect-uri", defaultInspectUri), Pattern.CASE_INSENSITIVE);
+
+        /* method */
+        param = getInitParameter("localization-method", "redirect");
+        if(param.equalsIgnoreCase("redirect"))
+        {
+            l10nMethod = REDIRECT;
+        }
+        else if(param.equalsIgnoreCase("forward"))
+        {
+            l10nMethod = FORWARD;
+        }
+        else
+        {
+            Logger.error("LocalizationFilter: '" + param
+                         + "' is not a valid l10n method; should be 'forward' or 'redirect'.");
+        }
+
+        /* supported locales */
+        findSupportedLocales(this.config);
+
+        /* default locale */
+        defaultLocale = getMatchedLocale(getInitParameter("default-locale"));
+    }
+
+    /**
+     * 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(true);    /* we'll store the active locale in it */
+        HttpServletResponse response = (HttpServletResponse)servletResponse;
+        Locale locale = null;
+
+        /* should an action (forward/redirect) be taken by the filter? */
+        boolean shouldAct = true;
+
+        /*
+         *  Now, what is the current locale ?
+         *  Guess #1 is the URI, if already localized (only for REDIRECT method).
+         *  Guess #2 is the session attribute 'active-locale'.
+         *  Guess #3 is a cookie 'used-locale'.
+         *  Guess #4 is from the Accepted-Language header.
+         */
+
+        // Logger.trace("l10n: URI ="+request.getRequestURI());
+
+        /* Guess #1 - if using redirect method, deduce from URI (and, while looking at URI, fills the shouldAct vairable) */
+        if(l10nMethod == REDIRECT)
+        {
+            Matcher matcher = inspectUri.matcher(request.getRequestURI());
+
+            if(matcher.find())
+            {
+                String candidate = matcher.group(1);
+
+                locale = getMatchedLocale(candidate);
+                if(locale != null)
+                {
+                    shouldAct = false;
+                }
+            }
+
+//          Logger.trace("l10n: URI locale = "+locale);
+        }
+        else
+        {
+            /* for the forward method, shouldAct rule is: always only when not already forwarded */
+            Boolean forwarded = (Boolean)request.getAttribute("velosurf.l10n.l10n-forwarded");
+
+            if(forwarded != null && forwarded.booleanValue())
+            {
+                shouldAct = false;
+            }
+        }
+        if(locale == null)
+        {
+            /* Guess #2 - is there an attribute in the session? */
+            locale = (Locale)session.getAttribute("velosurf.l10n.active-locale");
+
+//          Logger.trace("l10n: session locale = "+locale);
+            if(locale == null)
+            {
+                /* Guess #3 - is there a cookie? */
+                Cookie cookies[] = request.getCookies();
+
+                if(cookies != null)
+                {
+                    for(Cookie cookie : cookies)
+                    {
+                        if("velosurf.l10n.active-locale".equals(cookie.getName()))
+                        {
+                            locale = getMatchedLocale(cookie.getValue());
+                        }
+                    }
+                }
+
+//              Logger.trace("l10n: cookies locale = "+locale);
+                if(locale == null)
+                {
+                    /* Guess #4 - use the Accepted-Language HTTP header */
+                    List<Locale> requestedLocales = getRequestedLocales(request);
+
+                    locale = getPreferredLocale(requestedLocales);
+                    Logger.trace("l10n: Accepted-Language header best matching locale = " + locale);
+                }
+            }
+        }
+        if(locale == null && defaultLocale != null)
+        {
+            locale = defaultLocale;
+        }
+
+/*      not needed - the tool should find the active locale in the session
+             if (locale != null) {
+                 Localizer tool = ToolFinder.findSessionTool(session,Localizer.class);
+                 if (tool != null) {
+                     tool.setLocale(locale);
+                 } else {
+                     Logger.warn("l10n: cannot find any Localizer tool!");
+                 }
+             }
+*/
+
+        /* sets the session atribute and the cookies */
+
+        // Logger.trace("l10n: setting session current locale to "+locale);
+        session.setAttribute("velosurf.l10n.active-locale", locale);
+
+        Cookie localeCookie = new Cookie("velosurf.l10n.active-locale", locale.toString());
+
+        localeCookie.setPath("/");
+        localeCookie.setMaxAge(SECONDS_IN_YEAR);
+        response.addCookie(localeCookie);
+
+        Matcher match = matchUri.matcher(request.getRequestURI());
+
+        shouldAct &= match.find();
+        if(shouldAct)
+        {
+            // && (i = rewriteUri.indexOf("@")) != -1) ?
+            String rewriteUri = this.rewriteUri.replaceFirst("@", locale.toString());
+            String newUri = match.replaceFirst(rewriteUri);
+            RequestDispatcher dispatcher;
+            String query = request.getQueryString();
+
+            if(query == null)
+            {
+                query = "";
+            }
+            else
+            {
+                query = "?" + query;
+            }
+            switch(l10nMethod)
+            {
+                case REDIRECT :
+                    Logger.trace("l10n: redirecting request to " + newUri + query);
+                    response.sendRedirect(newUri + query);
+                    break;
+                case FORWARD :
+                    dispatcher = config.getServletContext().getRequestDispatcher(newUri + query);
+                    if(dispatcher == null)
+                    {
+                        Logger.error("l10n: cannot find a request dispatcher for path '" + newUri + "'");
+                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                    }
+                    else
+                    {
+                        Logger.trace("l10n: forwarding request to " + newUri + query);
+                        request.setAttribute("velosurf.l10n.l10n-forwarded", Boolean.valueOf(shouldAct));
+                        dispatcher.forward(request, response);
+                    }
+                    break;
+            }
+        }
+        else
+        {
+//          Logger.trace("l10n: letting request pass towards "+request.getRequestURI());
+            chain.doFilter(request, response);
+        }
+    }
+
+    /**
+     * Find supported locales.
+     *
+     * @param config filter config
+     */
+    private void findSupportedLocales(FilterConfig config)
+    {
+        /* look in the filter init-params */
+        String param = config.getInitParameter("supported-locales");
+
+        if(param == null)
+        {
+            /* look in the webapp context-params */
+            param = config.getServletContext().getInitParameter("supported-locales");
+        }
+        if(param == null)
+        {
+            /* try to determine it */
+            int i;
+
+            if(rewriteUri != null && (i = rewriteUri.indexOf("@")) != -1)
+            {
+                supportedLocales = guessSupportedLocales(this.config.getServletContext(), rewriteUri.substring(0, i));
+                if(Logger.getLogLevel() <= Logger.TRACE_ID)
+                {
+                    Logger.trace("l10n: supported locales = " + StringLists.join(Arrays.asList(supportedLocales), ","));
+                }
+                if(supportedLocales != null && supportedLocales.size() > 0)
+                {
+                    return;
+                }
+            }
+        }
+        else
+        {
+            supportedLocales = new ArrayList<Locale>();
+
+            String[] list = param.split(",");
+
+            for(String code : list)
+            {
+                supportedLocales.add(new Locale(code));
+            }
+        }
+        if(supportedLocales != null && supportedLocales.size() > 0)
+        {
+            /*
+             *  let other objects see it?
+             * config.getServletContext().setAttribute("velosurf.l10n.supported-locales",supportedLocales);
+             */
+        }
+        else
+        {
+            Logger.error("l10n: Cannot find any supported locale! Please add a 'supported-locales' context-param.");
+        }
+    }
+
+    /**
+     * Helper function.
+     *
+     * @param key
+     * @return init-parameter
+     */
+    private String getInitParameter(String key)
+    {
+        return config.getInitParameter(key);
+    }
+
+    /**
+     * Helper function.
+     *
+     * @param key
+     * @param defaultValue
+     * @return init-parameter
+     */
+    private String getInitParameter(String key, String defaultValue)
+    {
+        String param = config.getInitParameter(key);
+
+        return param == null ? defaultValue : param;
+    }
+
+    /**
+     * Destroy the filter.
+     *
+     */
+    public void destroy(){}
+
+    /**
+     * Guess supported locales.
+     * @param ctx servlet context
+     * @param path path
+     * @return list of locales
+     */
+    private List<Locale> guessSupportedLocales(ServletContext ctx, String path)
+    {
+        List<Locale> locales = new ArrayList<Locale>();
+        String languages[] = Locale.getISOLanguages();
+        String countries[] = Locale.getISOCountries();
+
+        Arrays.sort(languages);
+        Arrays.sort(countries);
+
+        String language, country;
+
+        for(String resource : (Set<String>)ctx.getResourcePaths(path))
+        {
+            /* first, it must be a path */
+            if(resource.endsWith("/"))
+            {
+                int len = resource.length();
+                int i = resource.lastIndexOf('/', len - 2);
+                String locale = resource.substring(i + 1, len - 1);
+
+                if((i = locale.indexOf('_')) != -1)
+                {
+                    language = locale.substring(0, i);
+                    country = locale.substring(i + 1);
+                }
+                else
+                {
+                    language = locale;
+                    country = null;
+                }
+
+                /* then it must contains valid language and country codes */
+                if(Arrays.binarySearch(languages, language) >= 0
+                    && (country == null || Arrays.binarySearch(countries, country) >= 0))
+                {
+                    /* looks ok... */
+                    locales.add(country == null ? new Locale(language) : new Locale(language, country));
+                }
+            }
+        }
+        supportedLocales = locales;
+        return locales;
+    }
+
+    /**
+     * get the list of requested locales.
+     *
+     * @param request request
+     * @return list of locales
+     */
+    private List<Locale> getRequestedLocales(HttpServletRequest request)
+    {
+        List<Locale> list = (List<Locale>)Collections.list(request.getLocales());
+
+        if( /* list.size() == 0 && */defaultLocale != null)
+        {
+            list.add(defaultLocale);
+        }
+        return list;
+    }
+
+    /**
+     * get matched locale.
+     * @param candidate candidate
+     * @return locale
+     */
+    private Locale getMatchedLocale(String candidate)
+    {
+        if(candidate == null)
+        {
+            return null;
+        }
+        if(supportedLocales == null)
+        {
+            Logger.error("l10n: the list of supported locales is empty!");
+            return null;
+        }
+        for(Locale locale : supportedLocales)
+        {
+            if(candidate.startsWith(locale.toString()))
+            {
+                return locale;
+            }
+        }
+        for(Locale locale : supportedLocales)
+        {
+            if(locale.toString().startsWith(candidate))
+            {
+                return locale;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get preferred locale.
+     * @param requestedLocales requested locales
+     * @return preferred locale
+     */
+    private Locale getPreferredLocale(List<Locale> requestedLocales)
+    {
+        for(Locale locale : requestedLocales)
+        {
+            if(supportedLocales.contains(locale))
+            {
+                return locale;
+            }
+        }
+
+        /* still there? Ok, second pass without the country. */
+        for(Locale locale : requestedLocales)
+        {
+            if(locale.getCountry() != null)
+            {
+                locale = new Locale(locale.getLanguage());
+                if(supportedLocales.contains(locale))
+                {
+                    return locale;
+                }
+            }
+        }
+        Logger.warn("l10n: did not find a matching locale for " + StringLists.join(requestedLocales, ","));
+
+        /*
+         *  then return the default locale, even if it doesn't match...
+         * if(defaultLocale != null) {
+         *   return defaultLocale;
+         * }
+         */
+
+        /* Oh, well... */
+        return null;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/Localizer.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/Localizer.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/Localizer.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/Localizer.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,57 @@
+/*
+ * 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.util.Locale;
+
+/**
+ * To be implemented by localizers used by the engine to get localized strings from their database id.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public interface Localizer
+{
+    /**
+     * Locale setter.
+     * @param locale locale
+     */
+    public void setLocale(Locale locale);
+
+    /**
+     * Locale getter.
+     * @return locale
+     */
+    public Locale getLocale();
+
+    /**
+     * Message getter.
+     * @param id message id
+     * @return localized message
+     */
+    public String get(Object id);
+
+    /**
+     * Parameterized message getter.
+     * @param id message id
+     * @param param message parameters
+     * @return localized message
+     */
+    public String get(Object id, Object... param);
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/SimpleDBLocalizer.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/SimpleDBLocalizer.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/SimpleDBLocalizer.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/SimpleDBLocalizer.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,277 @@
+/*
+ * 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.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+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.context.EntityReference;
+import org.apache.velocity.velosurf.context.Instance;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.web.VelosurfTool;
+
+/**
+ * <p>A basic database based Localizer implementation.</p>
+ * <p>The following <code>toolbox.xml</code> parameters are available:</p>
+ * <ul>
+ * <li><code>localized-table</code>: the name of the table containing localized strings (default: "<code>localized</code>").</li>
+ * <li><code>id-field</code>: the name of the field containing the string id (default: "<code>id</code>").</li>
+ * <li><code>locale-field</code>: the name of the field containing the ISO locale code (default: "<code>locale</code>").</li>
+ * <li><code>string-field</code>: the name of the field containing the lcoalized string (default: "<code>string</code>").</li>
+ * </ul>
+ *
+ * <p>You can find on the web the list of
+ * <a href="http://www.loc.gov/standards/iso639-2/englangn.html">ISO Language Codes</a>
+ * and the list of
+ * <a href="http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html">ISO Country Codes</a>.
+ *
+ */
+public class SimpleDBLocalizer extends HTTPLocalizerTool
+{
+    /** localized table name parameter key. */
+    private static final String LOCALIZED_TABLE_KEY = "localized-table";
+
+    /** id field name parameter key. */
+    private static final String ID_FIELD_KEY = "id-field";
+
+    /** locale field name parameter key. */
+    private static final String LOCALE_FIELD_KEY = "locale-field";
+
+    /** localized string field name parameter key. */
+    private static final String STRING_FIELD_KEY = "string-field";
+
+    /** default localized table name. */
+    private static final String LOCALIZED_TABLE_DEFAULT = "localized";
+
+    /** default id fied name. */
+    private static final String ID_FIELD_DEFAULT = "id";
+
+    /** default locale field name. */
+    private static final String LOCALE_FIELD_DEFAULT = "locale";
+
+    /** default localized string field name. */
+    private static final String STRING_FIELD_DEFAULT = "string";
+
+    /** localized table name. */
+    private static String localizedTable = LOCALIZED_TABLE_DEFAULT;
+
+    /** id field name. */
+    private static String idField = ID_FIELD_DEFAULT;
+
+    /** locale field name. */
+    private static String localeField = LOCALE_FIELD_DEFAULT;
+
+    /** localized string field name. */
+    private static String stringField = STRING_FIELD_DEFAULT;
+
+    /** initialization status. */
+    private static boolean initialized = false;
+
+    /** map locale -&gt; (id -&gt; localized). */
+    private static Map<Locale, Map<Object, String>> localeStrings = null;
+
+    /** map (id -&gt; localized) for the current locale. */
+    private Map<Object, String> currentStrings = null;
+
+    /** tool configuration. */
+    private Map config;
+
+    /**
+     * Constructor.
+     */
+    public SimpleDBLocalizer(){}
+
+    /**
+     * Configure this tool.
+     * @param config tool configuration
+     */
+    public void configure(Map config)
+    {
+        this.config = config;
+    }
+
+    /**
+     * Initialize this tool.
+     * @param initData a view context
+     */
+    public void init(Object initData)
+    {
+        if(!initialized)
+        {
+            super.init(initData);
+            if(config != null)
+            {
+                String value;
+
+                value = (String)config.get(LOCALIZED_TABLE_KEY);
+                if(value != null)
+                {
+                    localizedTable = value;
+                }
+                value = (String)config.get(ID_FIELD_KEY);
+                if(value != null)
+                {
+                    idField = value;
+                }
+                value = (String)config.get(LOCALE_FIELD_KEY);
+                if(value != null)
+                {
+                    localeField = value;
+                }
+                value = (String)config.get(STRING_FIELD_KEY);
+                if(value != null)
+                {
+                    stringField = value;
+                }
+            }
+
+            ServletContext ctx = initData instanceof ServletContext
+                                 ? (ServletContext)initData : ((ViewContext)initData).getServletContext();
+
+            readLocales(ctx);
+            if(initData instanceof ViewContext)
+            {
+                HttpSession session = ((ViewContext)initData).getRequest().getSession(true);
+
+                session.setAttribute(Localizer.class.getName(), this);
+            }
+        }
+        super.init(initData);
+    }
+
+    /**
+     * read localized messages into memory.
+     * @param ctx servlet context
+     */
+    private static synchronized void readLocales(ServletContext ctx)
+    {
+        if(initialized)
+        {
+            return;
+        }
+        try
+        {
+            DBReference db = VelosurfTool.getDefaultInstance(ctx);
+
+            if(db == null)
+            {
+                throw new Exception("Cannot find database!");
+            }
+
+            EntityReference entity = (EntityReference)db.get(localizedTable);
+
+            if(entity == null)
+            {
+                throw new Exception("Cannot find 'localized' database entity!");
+            }
+            localeStrings = new HashMap<Locale, Map<Object, String>>();
+            entity.setOrder(localeField);
+
+            Iterator locales = entity.iterator();    // sorted by locale
+            Map<Object, String> map = null;
+            String current = null;
+            Locale loc = null;
+
+            while(locales.hasNext())
+            {
+                Instance row = (Instance)locales.next();
+                String key = (String)row.get(idField);
+                String locale = (String)row.get(localeField);
+                String string = (String)row.get(stringField);
+
+                if(!locale.equals(current))
+                {
+                    current = locale;
+                    Logger.trace("Found new locale in db: " + locale);
+                    map = new HashMap<Object, String>();
+
+                    /* for now, take language and country into account... TODO: take variant into account */
+                    int sep = locale.indexOf('_');
+
+                    loc = (sep == -1
+                           ? new Locale(locale) : new Locale(locale.substring(0, sep), locale.substring(sep + 1)));
+                    localeStrings.put(loc, map);
+                }
+                map.put(key, string);
+            }
+            initialized = true;
+        }
+        catch(Exception e)
+        {
+//          Logger.log(e);
+            Logger.error(e.getMessage());
+        }
+    }
+
+    /**
+     * Check for the presence of a locale.
+     * @param locale locale
+     * @return true if present
+     */
+    public boolean hasLocale(Locale locale)
+    {
+        return localeStrings.containsKey(locale);
+    }
+
+    /**
+     * Locale setter.
+     * @param locale locale
+     */
+    public void setLocale(Locale locale)
+    {
+        if(locale == null && this.locale == null || locale != null && locale.equals(this.locale))
+        {
+            /* no change */
+            return;
+        }
+        super.setLocale(locale);
+        if(localeStrings != null)
+        {
+            currentStrings = localeStrings.get(getLocale());
+        }
+        if(currentStrings == null)
+        {
+            Logger.warn("l10n: no strings found for locale " + locale);
+        }
+    }
+
+    /**
+     * Localized message getter.
+     *  @param id message id
+     * @return localized message or id itself if not found
+     */
+    public String get(Object id)
+    {
+        if(currentStrings == null)
+        {
+            Logger.warn("l10n: no current locale! (was getting string id '" + id + "')");
+            return id.toString();
+        }
+
+        String message = currentStrings.get(id);
+
+        // Logger.trace("l10n: "+id+" -> "+message);
+        return message == null ? id.toString() : message;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/l10n/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains classes related to localization.
+
+<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/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/web/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains the Velocity View Tools version of the Velosurf tool, plus an http query parameter parser tool.
+
+<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/javascript/md5-LICENSE
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/javascript/md5-LICENSE?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/javascript/md5-LICENSE (added)
+++ velocity/sandbox/velosurf/src/javascript/md5-LICENSE Fri Mar  9 16:23:25 2012
@@ -0,0 +1,35 @@
+COPYRIGHT & LICENSE
+-------------------
+
+Copyright (c) 1998 - 2008, Paul Johnston & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+FURTHER RESTRICTIONS
+--------------------
+
+The JavaScript code implementing the algorithm is derived from the C code in RFC 1321 and is covered by the following copyright:
+
+    License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function.
+
+    License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work.
+
+    RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this documentation and/or software.
+
+This copyright does not prohibit distribution of the JavaScript MD5 code under the BSD license.
+
+CRYPTOGRAPHIC LAW
+-----------------
+
+Some countries restrict the import and use of cryptographic software. You must ensure that you are not violating any local law when downloading anything contained on these pages.
+

Added: velocity/sandbox/velosurf/src/javascript/md5.js
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/javascript/md5.js?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/javascript/md5.js (added)
+++ velocity/sandbox/velosurf/src/javascript/md5.js Fri Mar  9 16:23:25 2012
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}

Added: velocity/sandbox/velosurf/src/javascript/sha1.js
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/javascript/sha1.js?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/javascript/sha1.js (added)
+++ velocity/sandbox/velosurf/src/javascript/sha1.js Fri Mar  9 16:23:25 2012
@@ -0,0 +1,330 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS 180-1
+ * Version 2.2 Copyright Paul Johnston 2000 - 2009.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s)    { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
+function b64_sha1(s)    { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
+function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
+function hex_hmac_sha1(k, d)
+  { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_sha1(k, d)
+  { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_sha1(k, d, e)
+  { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+  return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA1 of a raw string
+ */
+function rstr_sha1(s)
+{
+  return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data (raw strings)
+ */
+function rstr_hmac_sha1(key, data)
+{
+  var bkey = rstr2binb(key);
+  if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
+  return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+  try { hexcase } catch(e) { hexcase=0; }
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var output = "";
+  var x;
+  for(var i = 0; i < input.length; i++)
+  {
+    x = input.charCodeAt(i);
+    output += hex_tab.charAt((x >>> 4) & 0x0F)
+           +  hex_tab.charAt( x        & 0x0F);
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+  try { b64pad } catch(e) { b64pad=''; }
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var output = "";
+  var len = input.length;
+  for(var i = 0; i < len; i += 3)
+  {
+    var triplet = (input.charCodeAt(i) << 16)
+                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+    }
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+  var divisor = encoding.length;
+  var remainders = Array();
+  var i, q, x, quotient;
+
+  /* Convert to an array of 16-bit big-endian values, forming the dividend */
+  var dividend = Array(Math.ceil(input.length / 2));
+  for(i = 0; i < dividend.length; i++)
+  {
+    dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+  }
+
+  /*
+   * Repeatedly perform a long division. The binary array forms the dividend,
+   * the length of the encoding is the divisor. Once computed, the quotient
+   * forms the dividend for the next step. We stop when the dividend is zero.
+   * All remainders are stored for later use.
+   */
+  while(dividend.length > 0)
+  {
+    quotient = Array();
+    x = 0;
+    for(i = 0; i < dividend.length; i++)
+    {
+      x = (x << 16) + dividend[i];
+      q = Math.floor(x / divisor);
+      x -= q * divisor;
+      if(quotient.length > 0 || q > 0)
+        quotient[quotient.length] = q;
+    }
+    remainders[remainders.length] = x;
+    dividend = quotient;
+  }
+
+  /* Convert the remainders to the output string */
+  var output = "";
+  for(i = remainders.length - 1; i >= 0; i--)
+    output += encoding.charAt(remainders[i]);
+
+  /* Append leading zero equivalents */
+  var full_length = Math.ceil(input.length * 8 /
+                                    (Math.log(encoding.length) / Math.log(2)))
+  for(i = output.length; i < full_length; i++)
+    output = encoding[0] + output;
+
+  return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+  var output = "";
+  var i = -1;
+  var x, y;
+
+  while(++i < input.length)
+  {
+    /* Decode utf-16 surrogate pairs */
+    x = input.charCodeAt(i);
+    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+    {
+      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+      i++;
+    }
+
+    /* Encode output as utf-8 */
+    if(x <= 0x7F)
+      output += String.fromCharCode(x);
+    else if(x <= 0x7FF)
+      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0xFFFF)
+      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0x1FFFFF)
+      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+                                    0x80 | ((x >>> 12) & 0x3F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+  }
+  return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
+                                  (input.charCodeAt(i) >>> 8) & 0xFF);
+  return output;
+}
+
+function str2rstr_utf16be(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+                                   input.charCodeAt(i)        & 0xFF);
+  return output;
+}
+
+/*
+ * Convert a raw string to an array of big-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binb(input)
+{
+  var output = Array(input.length >> 2);
+  for(var i = 0; i < output.length; i++)
+    output[i] = 0;
+  for(var i = 0; i < input.length * 8; i += 8)
+    output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
+  return output;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2rstr(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length * 32; i += 8)
+    output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
+  return output;
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function binb_sha1(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << (24 - len % 32);
+  x[((len + 64 >> 9) << 4) + 15] = len;
+
+  var w = Array(80);
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+  var e = -1009589776;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+    var olde = e;
+
+    for(var j = 0; j < 80; j++)
+    {
+      if(j < 16) w[j] = x[i + j];
+      else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+      var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
+                       safe_add(safe_add(e, w[j]), sha1_kt(j)));
+      e = d;
+      d = c;
+      c = bit_rol(b, 30);
+      b = a;
+      a = t;
+    }
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+    e = safe_add(e, olde);
+  }
+  return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+  if(t < 20) return (b & c) | ((~b) & d);
+  if(t < 40) return b ^ c ^ d;
+  if(t < 60) return (b & c) | (b & d) | (c & d);
+  return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+  return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
+         (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}

Added: velocity/sandbox/velosurf/test/conf/included.xml
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/conf/included.xml?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/conf/included.xml (added)
+++ velocity/sandbox/velosurf/test/conf/included.xml Fri Mar  9 16:23:25 2012
@@ -0,0 +1 @@
+<attribute name="count_publishers" result="scalar"><xi:include href="included2.xml"/></attribute>

Added: velocity/sandbox/velosurf/test/conf/included2.xml
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/conf/included2.xml?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/conf/included2.xml (added)
+++ velocity/sandbox/velosurf/test/conf/included2.xml Fri Mar  9 16:23:25 2012
@@ -0,0 +1 @@
+select count(*) from publisher

Added: velocity/sandbox/velosurf/test/conf/model.xml
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/conf/model.xml?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/conf/model.xml (added)
+++ velocity/sandbox/velosurf/test/conf/model.xml Fri Mar  9 16:23:25 2012
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<database user="sa" password="" url="jdbc:hsqldb:hsql://127.0.0.1/test"
+	read-only="false" loglevel="trace" reverse="full" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <attribute name="user_by_login" result="row/user" xml:space="preserve">
+    select * from user where login=<login/>
+  </attribute>
+
+  <entity name="publisher">
+    <attribute name="books" result="rowset/book">
+		select * from book where publisher_id=<publisher_id/>
+	</attribute>
+  </entity>
+
+  <entity name="book" readonly="true"> <!-- made read-only to test failure on updates -->
+    <aliases ref="isbn"/>
+  </entity>
+
+  <xi:include href="included.xml"/>
+
+</database>

Added: velocity/sandbox/velosurf/test/hsqlclient.sh
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/hsqlclient.sh?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/hsqlclient.sh (added)
+++ velocity/sandbox/velosurf/test/hsqlclient.sh Fri Mar  9 16:23:25 2012
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+java -classpath lib/hsqldb-1.8.0.5.jar org.hsqldb.util.DatabaseManager $*
+
+

Propchange: velocity/sandbox/velosurf/test/hsqlclient.sh
------------------------------------------------------------------------------
    svn:executable = *

Added: velocity/sandbox/velosurf/test/hsqldb.sh
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/hsqldb.sh?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/hsqldb.sh (added)
+++ velocity/sandbox/velosurf/test/hsqldb.sh Fri Mar  9 16:23:25 2012
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+java -classpath lib/hsqldb-1.8.0.5.jar org.hsqldb.Server -address 127.0.0.1 -port 9001 -database.0 mem:test -dbname.0 test
+

Propchange: velocity/sandbox/velosurf/test/hsqldb.sh
------------------------------------------------------------------------------
    svn:executable = *

Added: velocity/sandbox/velosurf/test/jetty/etc/jetty.xml
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/test/jetty/etc/jetty.xml?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/test/jetty/etc/jetty.xml (added)
+++ velocity/sandbox/velosurf/test/jetty/etc/jetty.xml Fri Mar  9 16:23:25 2012
@@ -0,0 +1,212 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server                                      -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.mortbay.jetty.Server">
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+      <New class="org.mortbay.thread.BoundedThreadPool">
+        <Set name="minThreads">10</Set>
+        <Set name="lowThreads">10</Set>
+        <Set name="maxThreads">50</Set>
+      </New>
+    </Set>
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+    <!-- One of each type!                                           -->
+    <!-- =========================================================== -->
+
+    <!-- Use this connector for many frequently idle connections
+         and for threadless continuations.
+    -->    
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.mortbay.jetty.nio.SelectChannelConnector">
+            <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
+            <Set name="maxIdleTime">30000</Set>
+            <Set name="Acceptors">2</Set>
+            <Set name="confidentialPort">8443</Set>
+          </New>
+      </Arg>
+    </Call>
+        
+    <!-- Use this connector if NIO is not available.
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.mortbay.jetty.bio.SocketConnector">
+            <Set name="port">8081</Set>
+            <Set name="maxIdleTime">50000</Set>
+            <Set name="lowResourceMaxIdleTime">1500</Set>
+          </New>
+      </Arg>
+    </Call>
+    -->
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- To add a HTTPS SSL listener                                     -->
+    <!-- see jetty-ssl.xml to add an ssl connector. use                  -->
+    <!-- java -jar start.jar etc/jetty.xml etc/jetty-ssl.xml             -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    
+
+
+    <!-- =========================================================== -->
+    <!-- Set handler Collection Structure                            --> 
+    <!-- =========================================================== -->
+    <Set name="handler">
+      <New id="handlers" class="org.mortbay.jetty.handler.HandlerCollection">
+        <Set name="handlers">
+         <Array type="org.mortbay.jetty.Handler">
+           <Item>
+             <New id="contexts" class="org.mortbay.jetty.handler.ContextHandlerCollection"/>
+           </Item>
+           <Item>
+             <New id="defaultHandler" class="org.mortbay.jetty.handler.DefaultHandler"/>
+           </Item>
+           <Item>
+             <New id="requestLog" class="org.mortbay.jetty.handler.RequestLogHandler"/>
+           </Item>
+         </Array>
+        </Set>
+      </New>
+    </Set>
+    
+    
+    <!-- ======================================================= -->
+    <!-- Configure a WebApp                                      -->
+    <!-- ======================================================= -->
+
+    <New id="VelosurfContext" class="org.mortbay.jetty.webapp.WebAppContext">
+      <Arg><Ref id="contexts"/></Arg>
+      <Arg>../webapp</Arg>
+      <Arg>/</Arg>
+      <Set name="classLoader">
+        <New class="org.mortbay.jetty.webapp.WebAppClassLoader">
+          <Arg><Ref id="VelosurfContext"/></Arg>
+        </New>
+      </Set>
+      <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+      <Set name="virtualHosts">
+        <Array type="java.lang.String">
+          <Item>localhost</Item>
+        </Array>
+      </Set>
+      <Get name="SessionHandler">
+        <Set name="SessionManager">
+          <New class="org.mortbay.jetty.servlet.HashSessionManager">
+            <Set name="maxInactiveInterval">600</Set>
+          </New>
+        </Set>
+      </Get>
+    </New>
+   
+
+    <!-- ======================================================= -->
+    <!-- Configure a Context                                     -->
+    <!-- ======================================================= -->
+<!--
+    <New class="org.mortbay.jetty.servlet.Context">
+      <Arg><Ref id="contexts"/></Arg>
+      <Arg>/javadoc</Arg>
+      <Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/javadoc/</Set>
+      <Call name="addServlet">
+        <Arg>org.mortbay.jetty.servlet.DefaultServlet</Arg>
+        <Arg>/</Arg>
+      </Call>
+    </New>
+-->
+    <!-- =========================================================== -->
+    <!-- Discover contexts from webapps directory                    -->
+    <!-- =========================================================== -->
+<!--
+    <Call class="org.mortbay.jetty.webapp.WebAppContext" name="addWebApplications">
+      <Arg><Ref id="contexts"/></Arg>
+      <Arg><SystemProperty name="jetty.home" default="."/>/webapps</Arg>
+      <Arg><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Arg>
+      <Arg type="boolean">True</Arg>  <!- - extract - ->
+      <Arg type="boolean">False</Arg> <!- - parent priority class loading - ->
+    </Call>
+-->
+    <!-- =========================================================== -->
+    <!-- Configure Realms                                            -->
+    <!-- =========================================================== -->
+<!--
+    <Set name="UserRealms">
+      <Array type="org.mortbay.jetty.security.UserRealm">
+        <Item>
+          <New class="org.mortbay.jetty.security.HashUserRealm">
+            <Set name="name">Test Realm</Set>
+            <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
+          </New>
+        </Item>
+      </Array>
+    </Set>
+-->    
+
+    <!-- =========================================================== -->
+    <!-- Configure Request Log                                       -->
+    <!-- =========================================================== -->
+
+    <Ref id="requestLog">
+      <Set name="requestLog">
+        <New id="requestLogImpl" class="org.mortbay.jetty.NCSARequestLog">
+          <Arg><SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log</Arg>
+          <Set name="retainDays">90</Set>
+          <Set name="append">true</Set>
+          <Set name="extended">false</Set>
+          <Set name="LogTimeZone">GMT</Set>
+        </New>
+      </Set>
+    </Ref>
+
+<!-- 
+
+system property org.mortbay.log.class
+
+    <Call class="org.mortbay.log.LogFactory" name="getFactory">
+       <Call name="setAttribute">
+         <Arg>*</Arg>
+         <Arg ref="VelosurfContext"/>
+       </Call>
+     </Call>
+
+
+ <Call class="org.apache.commons.logging.LogFactory" name="getFactory">
+    <Call name="setAttribute">
+      <Arg>*</Arg>
+      <Arg>
+       <New class="org.mortbay.log.LogImpl">
+        <Call name="add">
+          <Arg>
+            <New class="org.mortbay.log.OutputStreamLogSink">
+               standard log sink config stuff here
+...
+            </New>
+          </Arg>
+        </Call>
+       </New>
+      </Arg>
+    </Call>  
+
+    <Call name="setAttribute">
+      <Arg>test</Arg>
+      <Arg>*</Arg>
+    </Call>
+  </Call>
+-->
+
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <!-- ensure/prevent Server: header being sent to browsers        -->
+    <Set name="sendServerVersion">true</Set>
+
+</Configure>