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 [10/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/sql/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/sql/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/sql/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/sql/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains all core classes dealing with the database itself.
+
+<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/util/Cryptograph.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Cryptograph.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Cryptograph.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Cryptograph.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,29 @@
+package org.apache.velocity.velosurf.util;
+
+/**
+ * Cryptograph - used to encrypt and decrypt strings.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public interface Cryptograph
+{
+    /**
+     * init.
+     * @param random random string
+     */
+    public void init(String random);
+
+    /**
+     * encrypt.
+     * @param str string to encrypt
+     * @return encrypted string
+     */
+    public String encrypt(String str);
+
+    /**
+     * decrypt.
+     * @param str string to decrypt
+     * @return decrypted string
+     */
+    public String decrypt(String str);
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DBResourceLoader.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DBResourceLoader.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DBResourceLoader.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DBResourceLoader.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,86 @@
+/*
+ * 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.util;
+
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Date;
+import org.apache.commons.collections.ExtendedProperties;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+import org.apache.velocity.velosurf.context.DBReference;
+import org.apache.velocity.velosurf.context.EntityReference;
+import org.apache.velocity.velosurf.web.VelosurfTool;
+
+/**
+ * A database resource loader for use with Velosurf. Experimental.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class DBResourceLoader extends ResourceLoader
+{
+    protected DBReference db = null;
+    protected EntityReference table = null;
+    protected String entity = null;
+    protected String dataField = null;
+    protected String timestampField = null;
+
+    public void init(ExtendedProperties configuration)
+    {
+        entity = configuration.getString("entity", "template");
+        dataField = configuration.getString("data", "data");
+        timestampField = configuration.getString("lastmodified", "lastmodified");
+        initdb();
+    }
+
+    protected synchronized void initdb()
+    {
+        if(db == null)
+        {
+            db = VelosurfTool.getDefaultInstance();
+            if(db != null)
+            {
+                table = (EntityReference)db.get(entity);
+            }
+        }
+    }
+
+    public InputStream getResourceStream(String id) throws ResourceNotFoundException
+    {
+        if(db == null)
+        {
+            initdb();
+        }
+
+        String template = (String)table.fetch(id).get(dataField);
+
+        return new ReaderInputStream(new StringReader(template));
+    }
+
+    public boolean isSourceModified(Resource resource)
+    {
+        return((Date)table.fetch(resource.getName()).get(timestampField)).getTime() > resource.getLastModified();
+    }
+
+    public long getLastModified(Resource resource)
+    {
+        return((Date)table.fetch(resource.getName()).get(timestampField)).getTime();
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DESCryptograph.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DESCryptograph.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DESCryptograph.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DESCryptograph.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,204 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+import java.security.Security;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+/**
+ * Implemenation of the cryptograph for the DES algorithm.
+ * Inspired from some code found at http://javaalmanac.com
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class DESCryptograph implements Cryptograph
+{
+    /** encryption cypher */
+    Cipher ecipher;
+
+    /** decryption cypher */
+    Cipher dcipher;
+
+    /**
+     * Constructor.
+     */
+    public DESCryptograph(){}
+
+    /**
+     * initialization.
+     * @param random random string
+     */
+    public void init(String random)
+    {
+        try
+        {
+            // this is the only method that gives us reproducibility
+            SecureRandom seed = SecureRandom.getInstance("SHA1PRNG");
+
+            seed.setSeed(random.getBytes());
+
+            KeyGenerator keygen = KeyGenerator.getInstance("DES");
+
+            keygen.init(seed);
+
+            SecretKey key = keygen.generateKey();
+
+            ecipher = Cipher.getInstance("DES");
+            dcipher = Cipher.getInstance("DES");
+            ecipher.init(Cipher.ENCRYPT_MODE, key);
+            dcipher.init(Cipher.DECRYPT_MODE, key);
+        }
+        catch(javax.crypto.NoSuchPaddingException e)
+        {
+            e.printStackTrace();
+        }
+        catch(java.security.NoSuchAlgorithmException e)
+        {
+            e.printStackTrace();
+        }
+        catch(java.security.InvalidKeyException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * encrypt a string.
+     * @param str string to encrypt
+     * @return encrypted string
+     */
+    public String encrypt(String str)
+    {
+        try
+        {
+            // Encode the string into bytes using utf-8
+            byte[] utf8 = str.getBytes("UTF8");
+
+            // Encrypt
+            byte[] enc = ecipher.doFinal(utf8);
+
+            // Encode bytes to base64 to get a string
+            return new sun.misc.BASE64Encoder().encode(enc);
+        }
+        catch(javax.crypto.BadPaddingException e)
+        {
+            e.printStackTrace();
+        }
+        catch(IllegalBlockSizeException e)
+        {
+            e.printStackTrace();
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            e.printStackTrace();
+        }
+        catch(java.io.IOException e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Decrypt a string.
+     * @param str string to decrypt
+     * @return decrypted string
+     */
+    public String decrypt(String str)
+    {
+        try
+        {
+            // Decode base64 to get bytes
+            byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
+
+            // Decrypt
+            byte[] utf8 = dcipher.doFinal(dec);
+
+            // Decode using utf-8
+            return new String(utf8, "UTF8");
+        }
+        catch(javax.crypto.BadPaddingException e) {}
+        catch(IllegalBlockSizeException e) {}
+        catch(UnsupportedEncodingException e) {}
+        catch(java.io.IOException e) {}
+        return null;
+    }
+
+    static
+    {
+        Security.addProvider(new com.sun.crypto.provider.SunJCE());
+    }
+
+    /**
+     * test method
+     * @param args not used
+     */
+    public static void main(String args[])
+    {
+        DESCryptograph crypt = new DESCryptograph();
+
+        crypt.init("hello there!");
+        while(true)
+        {
+            try
+            {
+                StringBuffer rst = new StringBuffer();
+                int c;
+
+                while((c = System.in.read()) != 0x0A)
+                {
+                    if(c != 0x0D)
+                    {
+                        rst.append((char)c);
+                    }
+                }
+
+                String text = rst.toString();
+
+                System.out.println("text      -> <" + rst);
+
+                String enc = crypt.encrypt(text);
+
+                System.out.println("encrypted -> <" + enc + ">");
+                enc = enc.replace('=', '.');
+                enc = enc.replace('/', '_');
+                enc = enc.replace('+', '*');
+                System.out.println("encoded -> <" + enc + ">");
+
+                String dec = enc;
+
+                dec = dec.replace('.', '=');
+                dec = dec.replace('_', '/');
+                dec = dec.replace('*', '+');
+                System.out.println("decoded -> <" + dec + ">");
+                System.out.println("decrypted -> <" + crypt.decrypt(dec) + ">");
+            }
+            catch(IOException ioe)
+            {
+                Logger.log(ioe);
+            }
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DNSResolver.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DNSResolver.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DNSResolver.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/DNSResolver.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,159 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+/**
+ * Utility class to resolve names against DN servers
+ */
+public class DNSResolver
+{
+    /**
+     * check DNS.
+     * @param hostname hostname
+     * @return true if valid
+     */
+    public static boolean checkDNS(String hostname)
+    {
+        return checkDNS(hostname, false);
+    }
+
+    /**
+     * check DNS.
+     * @param hostname hostname
+     * @param mx do MX query or not
+     * @return true if valid
+     */
+    public static boolean checkDNS(String hostname, boolean mx)
+    {
+        List<String> records = resolveDNS(hostname, mx);
+
+        return records != null && records.size() > 0;
+    }
+
+    /**
+     * Resolve MX DNS.
+     * @param hostname hostname
+     * @return list of MXs
+     */
+    public static List<String> resolveDNS(String hostname, boolean mx)
+    {
+        List<String> result = new ArrayList<String>();
+
+        try
+        {
+            Logger.trace("DNS validation: resolving DNS for " + hostname + " " + (mx ? "(MX)" : "(A/CNAME)"));
+
+            Hashtable env = new Hashtable();
+
+            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+            env.put("com.sun.jndi.dns.timeout.initial", "5000");    /* quite short... too short? */
+            env.put("com.sun.jndi.dns.timeout.retries", "1");
+
+            DirContext ictx = new InitialDirContext(env);
+            String[] ids = (mx ? new String[] { "MX" } : new String[] { "A", "CNAME" });
+            Attributes attrs = ictx.getAttributes(hostname, ids);
+
+            if(mx)
+            {
+                Attribute attr = attrs.get("MX");
+
+                if(attr != null && attr.size() > 0)
+                {
+                    NamingEnumeration e = attr.getAll();
+
+                    while(e.hasMore())
+                    {
+                        String mxs = (String)e.next();
+                        String f[] = mxs.split("\\s+");
+
+                        for(int i = 0; i < f.length; i++)
+                        {
+                            if(f[i].endsWith("."))
+                            {
+                                result.add(f[i].substring(0, f[i].length() - 1));
+                            }
+                        }
+                    }
+                    return result;
+                }
+                else
+                {
+                    Logger.trace("DNS validation: DNS query of '" + hostname + "' failed");
+                    return null;
+                }
+            }
+            else
+            {
+                Attribute attr = attrs.get("A");
+
+                if(attr != null && attr.size() > 0)
+                {
+                    NamingEnumeration e = attr.getAll();
+
+                    while(e.hasMore())
+                    {
+                        result.add((String)e.next());
+                    }
+                    return result;
+                }
+                else
+                {
+                    attr = attrs.get("CNAME");
+                    if(attr != null && attr.size() > 0)
+                    {
+                        NamingEnumeration e = attr.getAll();
+
+                        while(e.hasMore())
+                        {
+                            String h = (String)e.next();
+
+                            if(h.endsWith("."))
+                            {
+                                h = h.substring(0, h.lastIndexOf('.'));
+                            }
+                            Logger.trace("DNS validation: recursing on CNAME record towards host " + h);
+                            result.addAll(resolveDNS(h, false));
+                        }
+                        return result;
+                    }
+                    else
+                    {
+                        Logger.trace("DNS validation: DNS query of '" + hostname + "' failed");
+                        return null;
+                    }
+                }
+            }
+        }
+        catch(NamingException ne)
+        {
+            Logger.trace("DNS validation: DNS MX query failed: " + ne.getMessage());
+            return null;
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Enumerator.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Enumerator.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Enumerator.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Enumerator.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,151 @@
+/*
+ * 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.util;
+
+import java.util.*;
+
+/**
+ * <p>Adapter class that wraps an <code>Enumeration</code> around a Java2
+ * collection classes object <code>Iterator</code> so that existing APIs
+ * returning Enumerations can easily run on top of the new collections.
+ * Constructors are provided to easliy create such wrappers.</p>
+ * <p>The source code is taken from Apache Tomcat</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
+ */
+public class Enumerator implements Enumeration
+{
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     */
+    public Enumerator(Collection collection)
+    {
+        this(collection.iterator());
+    }
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Collection collection, boolean clone)
+    {
+        this(collection.iterator(), clone);
+    }
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     */
+    public Enumerator(Iterator iterator)
+    {
+        super();
+        this.iterator = iterator;
+    }
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Iterator iterator, boolean clone)
+    {
+        super();
+        if(!clone)
+        {
+            this.iterator = iterator;
+        }
+        else
+        {
+            List list = new ArrayList();
+
+            while(iterator.hasNext())
+            {
+                list.add(iterator.next());
+            }
+            this.iterator = list.iterator();
+        }
+    }
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     */
+    public Enumerator(Map map)
+    {
+        this(map.values().iterator());
+    }
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Map map, boolean clone)
+    {
+        this(map.values().iterator(), clone);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The <code>Iterator</code> over which the <code>Enumeration</code>
+     * represented by this class actually operates.
+     */
+    private Iterator iterator = null;
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Tests if this enumeration contains more elements.
+     *
+     * @return <code>true</code> if and only if this enumeration object
+     *  contains at least one more element to provide, <code>false</code>
+     *  otherwise
+     */
+    public boolean hasMoreElements()
+    {
+        return(iterator.hasNext());
+    }
+
+    /**
+     * Returns the next element of this enumeration if this enumeration
+     * has at least one more element to provide.
+     *
+     * @return the next element of this enumeration
+     *
+     * @exception NoSuchElementException if no more elements exist
+     */
+    public Object nextElement() throws NoSuchElementException
+    {
+        return(iterator.next());
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/FastHttpDateFormat.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/FastHttpDateFormat.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/FastHttpDateFormat.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/FastHttpDateFormat.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,256 @@
+/*
+ * 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.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <p>Utility class to generate HTTP dates.</p>
+ * <p>This source code is taken from Tomcat Apache</p>
+ *
+ * @author Remy Maucherat
+ * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
+ */
+public class FastHttpDateFormat
+{
+    // -------------------------------------------------------------- Variables
+
+    /**
+     * HTTP date format.
+     */
+    private static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+    /**
+     * The set of SimpleDateFormat formats to use in <code>getDateHeader()</code>.
+     */
+    private static final SimpleDateFormat formats[] = { new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",
+                                                          Locale.US),
+        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    private final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+    static
+    {
+        format.setTimeZone(gmtZone);
+        formats[0].setTimeZone(gmtZone);
+        formats[1].setTimeZone(gmtZone);
+        formats[2].setTimeZone(gmtZone);
+    }
+
+    /**
+     * Instant on which the currentDate object was generated.
+     */
+    private static long currentDateGenerated = 0L;
+
+    /**
+     * Current formatted date.
+     */
+    private static String currentDate = null;
+
+    /**
+     * Formatter cache.
+     */
+    private static final HashMap formatCache = new HashMap();
+
+    /**
+     * Parser cache.
+     */
+    private static final HashMap parseCache = new HashMap();
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Gets the current date in HTTP format.
+     *
+     * @return Current date in HTTP format
+     */
+    public static final String getCurrentDate()
+    {
+        long now = System.currentTimeMillis();
+
+        if((now - currentDateGenerated) > 1000)
+        {
+            synchronized(format)
+            {
+                if((now - currentDateGenerated) > 1000)
+                {
+                    currentDateGenerated = now;
+                    currentDate = format.format(new Date(now));
+                }
+            }
+        }
+        return currentDate;
+    }
+
+    /**
+     * Formats a specified date to HTTP format. If local format is not
+     * <code>null</code>, it's used instead.
+     *
+     * @param value Date value to format
+     * @param threadLocalformat The format to use (or <code>null</code> -- then
+     *                          HTTP format will be used)
+     * @return Formatted date
+     */
+    public static final String formatDate(long value, DateFormat threadLocalformat)
+    {
+        String cachedDate = null;
+        Long longValue = Long.valueOf(value);
+
+        try
+        {
+            cachedDate = (String)formatCache.get(longValue);
+        }
+        catch(Exception e) {}
+        if(cachedDate != null)
+        {
+            return cachedDate;
+        }
+
+        String newDate = null;
+        Date dateValue = new Date(value);
+
+        if(threadLocalformat != null)
+        {
+            newDate = threadLocalformat.format(dateValue);
+            synchronized(formatCache)
+            {
+                updateCache(formatCache, longValue, newDate);
+            }
+        }
+        else
+        {
+            synchronized(formatCache)
+            {
+                newDate = format.format(dateValue);
+                updateCache(formatCache, longValue, newDate);
+            }
+        }
+        return newDate;
+    }
+
+    /**
+     * Tries to parse the given date as an HTTP date. If local format list is not
+     * <code>null</code>, it's used instead.
+     *
+     * @param value The string to parse
+     * @param threadLocalformats Array of formats to use for parsing.
+     *                           If <code>null</code>, HTTP formats are used.
+     * @return Parsed date (or -1 if error occured)
+     */
+    public static final long parseDate(String value, DateFormat[] threadLocalformats)
+    {
+        Long cachedDate = null;
+
+        try
+        {
+            cachedDate = (Long)parseCache.get(value);
+        }
+        catch(Exception e) {}
+        if(cachedDate != null)
+        {
+            return cachedDate.longValue();
+        }
+
+        Long date = null;
+
+        if(threadLocalformats != null)
+        {
+            date = internalParseDate(value, threadLocalformats);
+            synchronized(parseCache)
+            {
+                updateCache(parseCache, value, date);
+            }
+        }
+        else
+        {
+            synchronized(parseCache)
+            {
+                date = internalParseDate(value, formats);
+                updateCache(parseCache, value, date);
+            }
+        }
+        if(date == null)
+        {
+            return(-1L);
+        }
+        else
+        {
+            return date.longValue();
+        }
+    }
+
+    /**
+     * Parses date with given formatters.
+     *
+     * @param value The string to parse
+     * @param formats Array of formats to use
+     * @return Parsed date (or <code>null</code> if no formatter mached)
+     */
+    private static final Long internalParseDate(String value, DateFormat[] formats)
+    {
+        Date date = null;
+
+        for(int i = 0; (date == null) && (i < formats.length); i++)
+        {
+            try
+            {
+                date = formats[i].parse(value);
+            }
+            catch(ParseException e)
+            {
+                ;
+            }
+        }
+        if(date == null)
+        {
+            return null;
+        }
+        return Long.valueOf(date.getTime());
+    }
+
+    /**
+     * Updates cache.
+     *
+     * @param cache Cache to be updated
+     * @param key Key to be updated
+     * @param value New value
+     */
+    private static final void updateCache(HashMap cache, Object key, Object value)
+    {
+        if(value == null)
+        {
+            return;
+        }
+        if(cache.size() > 1000)
+        {
+            cache.clear();
+        }
+        cache.put(key, value);
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/HashMultiMap.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/HashMultiMap.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/HashMultiMap.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/HashMultiMap.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,268 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A MultiMap is a Map allowing multiple occurrences of keys.
+ *
+ * @author Mark Richters
+ * @see java.util.Map
+ */
+public class HashMultiMap implements MultiMap
+{
+    /**
+     * inner map.
+     */
+    private Map map;    // (Object key -> List values)
+
+    /**
+     * total number of values.
+     */
+    private transient int sizeAll;
+
+    /**
+     * build a new HashMultiMap.
+     */
+    public HashMultiMap()
+    {
+        map = new HashMap();
+    }
+
+    // Query Operations
+
+    /**
+     * Returns the number of values in this multimap.
+     *
+     * @return totla number of values
+     */
+    public int size()
+    {
+        return sizeAll;
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains no mappings.
+     *
+     * @return empty status
+     */
+    public boolean isEmpty()
+    {
+        return sizeAll == 0;
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains a mapping for
+     * the specified key.
+     *
+     * @param key
+     */
+    public boolean containsKey(Object key)
+    {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap maps one or more keys to
+     * the specified value.
+     */
+    public boolean containsValue(Object value)
+    {
+        Iterator it = map.values().iterator();
+
+        while(it.hasNext())
+        {
+            List l = (List)it.next();
+
+            if(l.contains(value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a list of values to which this multimap maps the specified
+     * key.
+     *
+     * @return the list of values to which this map maps the specified key, the
+     *     list may be empty if the multimap contains no mapping for this key.
+     */
+    public List get(Object key)
+    {
+        List l = (List)map.get(key);
+
+        if(l == null)
+        {
+            l = new ArrayList();
+        }
+        return l;
+    }
+
+    // Modification Operations
+
+    /**
+     * Adds the specified value with the specified key to this multimap.
+     * @param key
+     * @param value
+     */
+    public void put(Object key, Object value)
+    {
+        List l = (List)map.get(key);
+
+        if(l == null)
+        {
+            l = new ArrayList();
+            map.put(key, l);
+        }
+        l.add(value);
+        sizeAll++;
+    }
+
+    /**
+     * Copies all entries from the specified multimap to this
+     * multimap.
+     * @param t source multimap
+     */
+    public void putAll(MultiMap t)
+    {
+        Iterator it = t.keySet().iterator();
+
+        while(it.hasNext())
+        {
+            Object key = it.next();
+            List tl = (List)t.get(key);
+            List l = (List)map.get(key);
+
+            if(l == null)
+            {
+                l = new ArrayList();
+                map.put(key, l);
+            }
+            l.addAll(tl);
+            sizeAll += tl.size();
+        }
+    }
+
+    /**
+     * Removes all mappings for this key from this multimap if present.
+     * @param key
+     */
+    public void remove(Object key)
+    {
+        List l = (List)map.get(key);
+
+        if(l != null)
+        {
+            sizeAll -= l.size();
+        }
+        map.remove(key);
+    }
+
+    /**
+     * Removes the specified key/value mapping from this multimap if present.
+     * @param key
+     * @param value
+     */
+    public void remove(Object key, Object value)
+    {
+        List l = (List)map.get(key);
+
+        if(l != null)
+        {
+            if(l.remove(value))
+            {
+                sizeAll--;
+            }
+        }
+    }
+
+    // Bulk Operations
+
+    /**
+     * Removes all mappings from this map (optional operation).
+     */
+    public void clear()
+    {
+        map.clear();
+        sizeAll = 0;
+    }
+
+    // Views
+
+    /**
+     * Returns a set view of the keys contained in this multimap.
+     */
+    public Set keySet()
+    {
+        return map.keySet();
+    }
+
+    // Comparison and hashing
+
+    /**
+     * Compares the specified object with this multimap for equality.
+     * @param o
+     */
+    public boolean equals(Object o)
+    {
+        if(o == this)
+        {
+            return true;
+        }
+
+        // FIXME: use MultiMap interface only
+        if(!(o instanceof HashMultiMap))
+        {
+            return false;
+        }
+
+        HashMultiMap c = (HashMultiMap)o;
+
+        if(c.size() != size())
+        {
+            return false;
+        }
+        return map.equals(c.map);
+    }
+
+    /**
+     * Returns the hash code value for this multimap.
+     */
+    public int hashCode()
+    {
+        int h = 0;
+        Iterator it = map.entrySet().iterator();
+
+        while(it.hasNext())
+        {
+            Object obj = it.next();
+
+            h += obj.hashCode();
+        }
+        return h;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/LineWriterOutputStream.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/LineWriterOutputStream.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/LineWriterOutputStream.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/LineWriterOutputStream.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,66 @@
+/*
+ * 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.util;
+
+/**
+ * Bufferize chars written on the wrapped writer into lines.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+public class LineWriterOutputStream extends OutputStream
+{
+    /** writer */
+    private Writer writer = null;
+
+    /** buffer */
+    private StringBuffer buffer = new StringBuffer(200);
+
+    /**
+     * Construct a new LineWriterOutputStream, bound to the specified writer.
+     *
+     * @param w the writer
+     */
+    public LineWriterOutputStream(Writer w)
+    {
+        writer = w;
+    }
+
+    /**
+     * Write a byte to this output stream.
+     * @param c byte
+     * @exception IOException may be thrown
+     */
+    public void write(int c) throws IOException
+    {
+        if(c == '\n')
+        {
+            writer.write(buffer.toString());
+            buffer.delete(0, buffer.length());
+        }
+        else
+        {
+            buffer.append((char)c);
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Logger.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Logger.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Logger.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/Logger.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.util;
+
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * This class is the logger used by velosurf.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class Logger
+{
+    /**
+     * trace messages loglevel.
+     */
+    public final static int TRACE_ID = 0;
+
+    /**
+     * debug messages loglevel.
+     */
+    public final static int DEBUG_ID = 1;
+
+    /**
+     * info messages loglevel.
+     */
+    public final static int INFO_ID = 2;
+
+    /**
+     * warn messages loglevel.
+     */
+    public final static int WARN_ID = 3;
+
+    /**
+     * error messages loglevel.
+     */
+    public final static int ERROR_ID = 4;
+
+    /**
+     * fatal messages loglevel.
+     */
+    public final static int FATAL_ID = 5;
+
+    /**
+     * Current log level.
+     */
+    private static int logLevel = INFO_ID;
+
+    /**
+     * whether to display timestamps.
+     */
+    private static boolean displayTimestamps = false;
+
+    /**
+     * whether the logger captures stdout.
+     */
+    private static boolean captureStdout = false;
+
+    /**
+     * whether the logger captures stderr.
+     */
+    private static boolean captureStderr;
+
+    /**
+     * max number of lines to log in asynchronous mode.
+     */
+    private static int asyncLimit = 50;
+
+    /**
+     * Did someone give me an otput writer?
+     */
+    private static boolean initialized = false;
+
+    /**
+     * Sets the log level.
+     * @param logLevel log level
+     */
+    public static void setLogLevel(int logLevel)
+    {
+        info("Setting log level to " + logLevel);
+        Logger.logLevel = logLevel;
+    }
+
+    /**
+     * whether to display timestamps.
+     * @param timestamps
+     */
+    public static void setDisplayTimestamps(boolean timestamps)
+    {
+        displayTimestamps = timestamps;
+    }
+
+    /**
+     * Gets the current log level.
+     * @return the current log level
+     */
+    public static int getLogLevel()
+    {
+        return logLevel;
+    }
+
+    /**
+     * date format for timestamps.
+     */
+    static SimpleDateFormat format = null;
+
+    /**
+     * asynchronous log used at start.
+     */
+    static StringWriter asyncLog = null;
+
+    /**
+     * log output printwriter.
+     */
+    static PrintWriter log = null;
+
+    static
+    {
+        try
+        {
+            format = new SimpleDateFormat("[yyyy/MM/dd HH:mm:ss]");
+
+            // initialize with an asynchronous buffer
+            asyncLog = new StringWriter();
+            log = new PrintWriter(asyncLog);
+
+            // info("log initialized"); we do not always this line!
+        }
+        catch(Throwable e)
+        {
+            System.err.println("no log!");
+            e.printStackTrace(System.err);
+        }
+    }
+
+    /**
+     * stdout old value.
+     */
+    static PrintStream oldStdout = null;
+
+    /**
+     * stderr old value.
+     */
+    static PrintStream oldStderr = null;
+
+    /**
+     * logs a string.
+     *
+     * @param s string
+     */
+    static private void log(String s)
+    {
+        log.println(header() + s);
+        log.flush();
+    }
+
+    /**
+     * logs an exception with a string.
+     *
+     * @param s string
+     * @param e exception
+     */
+    static public void log(String s, Throwable e)
+    {
+        error(s);
+        e.printStackTrace(log);
+        log.flush();
+    }
+
+    /**
+     * log an exception.
+     *
+     * @param e exception
+     */
+    static public void log(Throwable e)
+    {
+        String msg = e.getMessage();
+
+        log((msg != null ? msg : ""), e);
+    }
+
+    static int lines = 0;
+
+    /**
+     * log a string using a verbose level.
+     *
+     * @param level verbose level
+     * @param s string to log
+     */
+    static public void log(int level, String s)
+    {
+        if(level < logLevel)
+        {
+            return;
+        }
+
+        String prefix = "";
+
+        switch(level)
+        {
+            case TRACE_ID :
+                prefix = " [trace] ";
+                break;
+            case DEBUG_ID :
+                prefix = " [debug] ";
+                break;
+            case INFO_ID :
+                prefix = "  [info] ";
+                break;
+            case WARN_ID :
+                prefix = "  [warn] ";
+                break;
+            case ERROR_ID :
+                prefix = " [error] ";
+                break;
+            case FATAL_ID :
+                prefix = " [fatal] ";
+                break;
+        }
+        log(prefix + s);
+        if(notify && level >= notifLevel && notifier != null)
+        {
+            String msg = prefix + s;
+            int cr = msg.indexOf('\n');
+            String subject = (cr == -1 ? msg : msg.substring(0, cr));
+
+            notifier.sendNotification(subject, msg);
+        }
+        lines++;
+
+        // no more than 100 lines in asynchronous mode
+        if(asyncLog != null && lines > asyncLimit)
+        {
+            log2Stderr();
+            flushAsyncLog();
+            warn("More than " + asyncLimit + " lines in asynchronous logging mode...");
+            warn("Automatically switching to stderr");
+        }
+    }
+
+    /**
+     * logs a tracing string.
+     *
+     * @param s tracing string
+     */
+    static public void trace(String s)
+    {
+        log(TRACE_ID, s);
+    }
+
+    /**
+     * logs a debug string.
+     *
+     * @param s debug string
+     */
+    static public void debug(String s)
+    {
+        log(DEBUG_ID, s);
+    }
+
+    /**
+     * logs an info string.
+     *
+     * @param s info string
+     */
+    static public void info(String s)
+    {
+        log(INFO_ID, s);
+    }
+
+    /**
+     * logs a warning string.
+     *
+     * @param s warning string
+     */
+    static public void warn(String s)
+    {
+        log(WARN_ID, s);
+    }
+
+    /**
+     * logs an error string.
+     *
+     * @param s error string
+     */
+    static public void error(String s)
+    {
+        log(ERROR_ID, s);
+    }
+
+    /**
+     * logs a fatal error string.
+     *
+     * @param s fatal error string
+     */
+    static public void fatal(String s)
+    {
+        log(ERROR_ID, s);
+    }
+
+    /**
+     * get the output writer.
+     *
+     * @return writer
+     */
+    static public PrintWriter getWriter()
+    {
+        return log;
+    }
+
+    /**
+     * set the output writer.
+     *
+     * @param out PrintWriter or Writer or OutputStream
+     */
+    static public void setWriter(Object out)
+    {
+        if(log != null)
+        {
+            log.flush();
+            log.close();
+        }
+        if(out instanceof PrintWriter)
+        {
+            log = (PrintWriter)out;
+        }
+        else if(out instanceof Writer)
+        {
+            log = new PrintWriter((Writer)out);
+        }
+        else if(out instanceof OutputStream)
+        {
+            log = new PrintWriter((OutputStream)out);
+        }
+        else
+        {
+            throw new RuntimeException("Logger.setWriter: PANIC! class " + out.getClass().getName()
+                                       + " cannot be used to build a PrintWriter!");
+        }
+        if(asyncLog != null)
+        {
+            flushAsyncLog();
+        }
+        initialized = true;
+    }
+
+    /**
+     * redirects stdout towards output writer.
+     */
+    static public void startCaptureStdout()
+    {
+        oldStdout = System.out;
+        System.setOut(new PrintStream(new WriterOutputStream(log)));
+        captureStdout = true;
+    }
+
+    /**
+     * stop redirecting stdout.
+     */
+    static public void stopCaptureStdout()
+    {
+        if(captureStdout)
+        {
+            System.setOut(oldStdout);
+        }
+        captureStdout = false;
+    }
+
+    /**
+     * redirects stderr towards the output writer.
+     */
+    static public void startCaptureStderr()
+    {
+        oldStderr = System.err;
+        System.setErr(new PrintStream(new WriterOutputStream(log)));
+        captureStderr = true;
+    }
+
+    /**
+     * stops redirecting stderr.
+     */
+    static public void stopCaptureStderr()
+    {
+        if(captureStderr)
+        {
+            System.setErr(oldStderr);
+        }
+        captureStderr = false;
+    }
+
+    /**
+     * log to stdout.
+     */
+    static public void log2Stdout()
+    {
+        stopCaptureStdout();
+        stopCaptureStderr();
+        setWriter(new PrintWriter(System.out));
+        flushAsyncLog();
+    }
+
+    /**
+     * log to stderr.
+     */
+    static public void log2Stderr()
+    {
+        stopCaptureStdout();
+        stopCaptureStderr();
+        setWriter(new PrintWriter(System.err));
+        flushAsyncLog();
+    }
+
+    /**
+     * log to file.
+     */
+    static public void log2File(String file) throws FileNotFoundException, IOException
+    {
+        setWriter(new PrintWriter(new FileWriter(file, true)));
+        log.println();
+        log.println("=================================================");
+        flushAsyncLog();
+    }
+
+    /**
+     * returns "Velosurf ".
+     *
+     * @return return the header
+     */
+    static private String header()
+    {
+        return displayTimestamps ? format.format(new Date()) + " Velosurf " : " Velosurf ";
+    }
+
+    /**
+     * flush the asynchronous log in the output writer.
+     */
+    static private void flushAsyncLog()
+    {
+        if(asyncLog != null)
+        {
+            try
+            {
+                String pastLog = asyncLog.toString();
+
+                if(pastLog.length() > 0)
+                {
+                    log(asyncLog.toString());
+                }
+                asyncLog.close();
+                asyncLog = null;
+            }
+            catch(IOException ioe)
+            {
+                log(ioe);
+            }
+        }
+    }
+
+    /**
+     * queries the initialized state.
+     *
+     */
+    static public boolean isInitialized()
+    {
+        return initialized;
+    }
+
+    /**
+     * dumps the current stack.
+     */
+    static public void dumpStack()
+    {
+        try
+        {
+            throw new Exception("dumpStack");
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace(log);
+        }
+    }
+
+    static private MailNotifier notifier = null;
+    static private int notifLevel = ERROR_ID;
+    static private boolean notify = false;
+
+    static public void setNotificationParams(String host, String sender, String recipient)
+    {
+        if(notifier != null)
+        {
+            notifier.stop();
+        }
+        notifier = new MailNotifier(host, sender, recipient);
+    }
+
+    static public void setNotificationLevel(int level)
+    {
+        notifLevel = level;
+    }
+
+    static public int getNotificationLevel()
+    {
+        return notifLevel;
+    }
+
+    static public void enableNotifications(boolean enable)
+    {
+        if(enable == notify)
+        {
+            return;
+        }
+        if(enable)
+        {
+            if(notifier == null)
+            {
+                Logger.error("Please set notification params before enabling notification!");
+                return;
+            }
+            notifier.start();
+        }
+        else
+        {
+            if(notifier != null)
+            {
+                notifier.stop();
+            }
+        }
+        notify = enable;
+    }
+
+    static public boolean getNotifierEnabled()
+    {
+        return notify && notifier != null;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MailNotifier.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MailNotifier.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MailNotifier.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MailNotifier.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,132 @@
+package org.apache.velocity.velosurf.util;
+
+import java.util.LinkedList;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.commons.net.smtp.SMTPReply;
+import org.apache.commons.net.smtp.SimpleSMTPHeader;
+
+public class MailNotifier implements Runnable
+{
+    private String host;
+    private String sender;
+    private String recipient;
+    private LinkedList<Notification> queue = new LinkedList<Notification>();
+    private boolean running = false;
+
+    class Notification
+    {
+        String subject;
+        String body;
+
+        Notification(String subject, String body)
+        {
+            this.subject = subject;
+            this.body = body;
+        }
+    }
+
+    public MailNotifier(String host, String sender, String recipient)
+    {
+        this.host = host;
+        this.sender = sender;
+        this.recipient = recipient;
+    }
+
+    public void start()
+    {
+        new Thread(this, "email notifications").start();
+    }
+
+    public void stop()
+    {
+        running = false;
+        synchronized(this)
+        {
+            notify();
+        }
+    }
+
+    public void sendNotification(String subject, String body)
+    {
+        synchronized(this)
+        {
+            queue.add(new Notification(subject, body));
+            notify();
+        }
+    }
+
+    public void run()
+    {
+        Notification notif;
+        SMTPClient client = null;
+
+        try
+        {
+            running = true;
+            while(running)
+            {
+                synchronized(this)
+                {
+                    if(queue.size() == 0)
+                    {
+                        wait();
+                    }
+                    notif = queue.removeFirst();
+                }
+                if(notif == null)
+                {
+                    continue;
+                }
+
+                String header = new SimpleSMTPHeader(sender, recipient, notif.subject).toString();
+
+                client = new SMTPClient();
+                client.connect(host);
+                if(!SMTPReply.isPositiveCompletion(client.getReplyCode()))
+                {
+                    throw new Exception("SMTP server " + host + " refused connection.");
+                }
+                if(!client.login())
+                {
+                    throw new Exception("SMTP: Problem logging in: error #" + client.getReplyCode() + " "
+                                        + client.getReplyString());
+                }
+                if(!client.setSender(sender))
+                {
+                    throw new Exception("SMTP: Problem setting sender to " + sender + ": error #"
+                                        + client.getReplyCode() + " " + client.getReplyString());
+                }
+                if(!client.addRecipient(recipient))
+                {
+                    throw new Exception("SMTP: Problem adding recipient " + recipient + ": error #"
+                                        + client.getReplyCode() + " " + client.getReplyString());
+                }
+                if(!client.sendShortMessageData(header + notif.body))
+                {
+                    throw new Exception("Problem sending notification : error #" + client.getReplyCode() + " "
+                                        + client.getReplyString());
+                }
+                try
+                {
+                    client.logout();
+                    client.disconnect();
+                }
+                catch(Exception e) {}
+            }
+        }
+        catch(Exception e)
+        {
+            try
+            {
+                if(client != null)
+                {
+                    client.logout();
+                    client.disconnect();
+                }
+            }
+            catch(Exception f) {}
+            Logger.enableNotifications(false);
+            Logger.log(e);
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MultiMap.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MultiMap.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MultiMap.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/MultiMap.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,133 @@
+/*
+ * 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.util;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A MultiMap is a Map allowing multiple occurrences of keys.
+ *
+ * @author     Mark Richters
+ * @see         java.util.Map
+ */
+public interface MultiMap
+{
+    // Query Operations
+
+    /**
+     * Returns the number of values in this multimap.
+     */
+    int size();
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains no mappings.
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains a mapping for
+     * the specified key.
+     * @param key
+     * @return a boolean
+     */
+    boolean containsKey(Object key);
+
+    /**
+     * Returns <tt>true</tt> if this multimap maps one or more keys to
+     * the specified value.
+     * @param value
+     * @return a boolean
+     */
+    boolean containsValue(Object value);
+
+    /**
+     * Returns a list of values to which this multimap maps the specified
+     * key.
+     * @param key
+     * @return the list of values to which this map maps the specified
+     *         key, the list may be empty if the multimap contains no
+     *         mapping for this key.
+     */
+    List get(Object key);
+
+    // Modification Operations
+
+    /**
+     * Adds the specified value with the specified key to this multimap.
+     *
+     * @param key
+     * @param value
+     */
+    void put(Object key, Object value);
+
+    /**
+     * Copies all entries from the specified multimap to this
+     * multimap.
+     * @param t multimap
+     */
+    void putAll(MultiMap t);
+
+    /**
+     * Removes all mappings for this key from this multimap if present.
+     * @param key
+     */
+    void remove(Object key);
+
+    /**
+     * Removes the specified key/value mapping from this multimap if present.
+     * @param key
+     * @param value
+     */
+    void remove(Object key, Object value);
+
+    // Bulk Operations
+
+    /**
+     * Removes all mappings from this map (optional operation).
+     */
+    void clear();
+
+    // Views
+
+    /**
+     * Returns a set view of the keys contained in this multimap.
+     * @return key set
+     */
+    Set keySet();
+
+    /*
+     * Returns a collection view of the values contained in this map.
+     */
+
+    // Collection values();
+    // Comparison and hashing
+
+    /**
+     * Compares the specified object with this multimap for equality.
+     * @param o other object
+     * @return a boolean
+     */
+    boolean equals(Object o);
+
+    /**
+     * Returns the hash code value for this map.
+     */
+    int hashCode();
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/NullServlet.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/NullServlet.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/NullServlet.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/NullServlet.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,100 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A null servlet, provided here for convenience, that will redirect the user to the forbidden uri (if provided
+ * in the init parameter "forbidden-uri") or respond with the FORBIDDEN error code.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class NullServlet extends HttpServlet
+{
+    /** page showing the 'forbidden' error message */
+    private String forbiddenUri = null;
+
+    /**
+     * init.
+     * @param config config
+     * @throws ServletException
+     */
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        forbiddenUri = config.getInitParameter("forbidden-uri");
+    }
+
+    /**
+     * doGet handler.
+     * @param request request
+     * @param response response
+     * @throws ServletException
+     * @throws IOException
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doRequest(request, response);
+    }
+
+    /**
+     * doPost handler.
+     * @param request request
+     * @param response response
+     * @throws ServletException
+     * @throws IOException
+     */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doRequest(request, response);
+    }
+
+    /**
+     * doRequest handler.
+     * @param request request
+     * @param response response
+     * @throws ServletException
+     * @throws IOException
+     */
+    private void doRequest(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException
+    {
+        Logger.trace("null servlet got hit: " + request.getRequestURI());
+        if(forbiddenUri == null)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+        }
+        else
+        {
+            response.sendRedirect(forbiddenUri);
+
+            /*
+             *  other option...
+             *   RequestDispatcher dispatcher = request.getRequestDispatcher(forbiddenUri);
+             *   dispatcher.forward(request,response);
+             */
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ReaderInputStream.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ReaderInputStream.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ReaderInputStream.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ReaderInputStream.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,25 @@
+package org.apache.velocity.velosurf.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Simple class to allow casting a reader in an input stream.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class ReaderInputStream extends InputStream
+{
+    private Reader reader = null;
+
+    public ReaderInputStream(Reader reader)
+    {
+        this.reader = reader;
+    }
+
+    public int read() throws IOException
+    {
+        return reader.read();
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequest.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequest.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequest.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequest.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,392 @@
+/*
+ * 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.util;
+
+import java.util.*;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * <p>
+ * Object that saves the critical information from a request so that
+ * form-based authentication can reproduce it once the user has been
+ * authenticated.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b> - It is assumed that this object is accessed
+ * only from the context of a single thread, so no synchronization around
+ * internal collection classes is performed.
+ * <p>
+ * <b>FIXME</b> - Currently, this object has no mechanism to save or
+ * restore the data content of the request, although it does save
+ * request parameters so that a POST transaction can be faithfully
+ * duplicated.
+ * </p>
+ * <p>The original source code is got from Apache Tomcat<p>
+ *
+ * @author Craig R. McClanahan
+ * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
+ */
+public class SavedRequest
+{
+    /**
+     * The set of Cookies associated with this Request.
+     */
+    private ArrayList cookies = new ArrayList();
+
+    /**
+     * Adds cookie to list of cookies.
+     *
+     * @param cookie cookie to add
+     */
+    public void addCookie(Cookie cookie)
+    {
+        cookies.add(cookie);
+    }
+
+    /**
+     * Returns list of cookies.
+     *
+     * @return list of cookies
+     */
+    public List getCookies()
+    {
+        return cookies;
+    }
+
+    /**
+     * The set of Headers associated with this Request.  Each key is a header
+     * name, while the value is a ArrayList containing one or more actual
+     * values for this header.  The values are returned as an Iterator when
+     * you ask for them.
+     */
+    private HashMap headers = new HashMap();
+
+    /**
+     * Adds header.
+     *
+     * @param name  header name
+     * @param value header value
+     */
+    public void addHeader(String name, String value)
+    {
+        name = name.toLowerCase();
+
+        ArrayList values = (ArrayList)headers.get(name);
+
+        if(values == null)
+        {
+            values = new ArrayList();
+            headers.put(name, values);
+        }
+        values.add(value);
+    }
+
+    /**
+     * Returns iterator over header names.
+     *
+     * @return iterator over header names
+     */
+    public Iterator getHeaderNames()
+    {
+        return(headers.keySet().iterator());
+    }
+
+    /**
+     * Returns iterator over header values.
+     *
+     * @param name header name
+     * @return iterator over header values
+     */
+    public Iterator getHeaderValues(String name)
+    {
+        name = name.toLowerCase();
+
+        ArrayList values = (ArrayList)headers.get(name);
+
+        if(values == null)
+        {
+            return((new ArrayList()).iterator());
+        }
+        else
+        {
+            return(values.iterator());
+        }
+    }
+
+    /**
+     * The set of Locales associated with this Request.
+     */
+    private ArrayList locales = new ArrayList();
+
+    /**
+     * Adds locale.
+     *
+     * @param locale locale to add
+     */
+    public void addLocale(Locale locale)
+    {
+        locales.add(locale);
+    }
+
+    /**
+     * Returns iterator over locales.
+     *
+     * @return iterator over locales
+     */
+    public Iterator getLocales()
+    {
+        return(locales.iterator());
+    }
+
+    /**
+     * The request method used on this Request.
+     */
+    private String method = null;
+
+    /**
+     * Returns request method.
+     *
+     * @return request method
+     */
+    public String getMethod()
+    {
+        return(this.method);
+    }
+
+    /**
+     * Sets request method.
+     *
+     * @param method request method to set
+     */
+    public void setMethod(String method)
+    {
+        this.method = method;
+    }
+
+    /**
+     * The set of request parameters associated with this Request.  Each
+     * entry is keyed by the parameter name, pointing at a String array of
+     * the corresponding values.
+     */
+    private HashMap parameters = new HashMap();
+
+    /**
+     * Adds parameter.
+     *
+     * @param name      parameter name
+     * @param values    parameter values
+     */
+    public void addParameter(String name, String values[])
+    {
+        parameters.put(name, values);
+    }
+
+    /**
+     * Returns iterator over parameter names.
+     *
+     * @return iterator over parameter names
+     */
+    public Iterator getParameterNames()
+    {
+        return(parameters.keySet().iterator());
+    }
+
+    /**
+     * Returns parameter values.
+     *
+     * @param name parameter name
+     * @return parameter values
+     */
+    public String[] getParameterValues(String name)
+    {
+        return((String[])parameters.get(name));
+    }
+
+    /**
+     * Returns parameters.
+     *
+     * @return parameters map
+     */
+    public Map getParameterMap()
+    {
+        return parameters;
+    }
+
+    /**
+     * The query string associated with this Request.
+     */
+    private String queryString = null;
+
+    /**
+     * Returns query string.
+     *
+     * @return query string
+     */
+    public String getQueryString()
+    {
+        return(this.queryString);
+    }
+
+    /**
+     * Sets query string.
+     *
+     * @param queryString query string to set
+     */
+    public void setQueryString(String queryString)
+    {
+        this.queryString = queryString;
+    }
+
+    /**
+     * The request URI associated with this Request.
+     */
+    private String requestURI = null;
+
+    /**
+     * Returns request URI.
+     *
+     * @return request URI
+     */
+    public String getRequestURI()
+    {
+        return(this.requestURI);
+    }
+
+    /**
+     * Sets request URI.
+     *
+     * @param requestURI request URI to set
+     */
+    public void setRequestURI(String requestURI)
+    {
+        this.requestURI = requestURI;
+    }
+
+    /**
+     * The request pathInfo associated with this Request.
+     */
+    private String pathInfo = null;
+
+    /**
+     * Returns path info.
+     *
+     * @return path info
+     */
+    public String getPathInfo()
+    {
+        return pathInfo;
+    }
+
+    /**
+     * Sets path info.
+     *
+     * @param pathInfo path info to set
+     */
+    public void setPathInfo(String pathInfo)
+    {
+        this.pathInfo = pathInfo;
+    }
+
+    /**
+     * Gets uri with path info and query string.
+     *
+     * @return uri with path info and query string
+     */
+    public String getRequestURL()
+    {
+        if(getRequestURI() == null)
+        {
+            return null;
+        }
+
+        StringBuffer sb = new StringBuffer(getRequestURI());
+
+//      if (getPathInfo() != null) {
+//          sb.append(getPathInfo());
+//      }
+        if(getQueryString() != null)
+        {
+            sb.append('?');
+            sb.append(getQueryString());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * This method provides ability to create SavedRequest from HttpServletRequest.
+     * @param request           request to be saved
+     * @return saved request    resulting SavedRequest
+     */
+    public static SavedRequest saveRequest(HttpServletRequest request)
+    {
+        if(request.getRequestURI() == null)
+        {
+            return null;
+        }
+
+        // Create and populate a SavedRequest object for this request
+        SavedRequest saved = new SavedRequest();
+        Cookie cookies[] = request.getCookies();
+
+        if(cookies != null)
+        {
+            for(int i = 0; i < cookies.length; i++)
+            {
+                saved.addCookie(cookies[i]);
+            }
+        }
+
+        Enumeration names = request.getHeaderNames();
+
+        while(names.hasMoreElements())
+        {
+            String name = (String)names.nextElement();
+            Enumeration values = request.getHeaders(name);
+
+            while(values.hasMoreElements())
+            {
+                String value = (String)values.nextElement();
+
+                saved.addHeader(name, value);
+            }
+        }
+
+        Enumeration locales = request.getLocales();
+
+        while(locales.hasMoreElements())
+        {
+            Locale locale = (Locale)locales.nextElement();
+
+            saved.addLocale(locale);
+        }
+
+        Map parameters = request.getParameterMap();
+
+        for(Map.Entry entry : (Set<Map.Entry>)parameters.entrySet())
+        {
+            saved.addParameter((String)entry.getKey(), (String[])entry.getValue());
+        }
+        saved.setMethod(request.getMethod());
+        saved.setQueryString(request.getQueryString());
+        saved.setRequestURI(request.getRequestURI());
+
+//      saved.setPathInfo(request.getPathInfo());
+        return saved;
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequestWrapper.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequestWrapper.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequestWrapper.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/SavedRequestWrapper.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,290 @@
+/*
+ * 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.util;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+/**
+ * <p>This class provides request parameters, headers, cookies from original requrest or saved request.</p>
+ *
+ * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+public @SuppressWarnings("deprecation")
+class SavedRequestWrapper extends HttpServletRequestWrapper
+{
+    /** the saved request. */
+    private SavedRequest savedRequest = null;
+
+    /** timezone. */
+    private static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+    /**
+     * The default Locale if none are specified.
+     */
+    private static final Locale defaultLocale = Locale.getDefault();
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     *
+     * Notice that because SimpleDateFormat is not thread-safe, we can't
+     * declare formats[] as a static variable.
+     */
+    private SimpleDateFormat formats[] = new SimpleDateFormat[3];
+
+    /**
+     * Constructor
+     * @param request to save
+     */
+    public SavedRequestWrapper(HttpServletRequest request, SavedRequest saved)
+    {
+        super(request);
+
+        HttpSession session = request.getSession(false);
+
+        if(session == null)
+        {
+            return;
+        }
+
+        String requestURI = saved.getRequestURI();
+
+        if(requestURI == null)
+        {
+            return;
+        }
+        savedRequest = saved;
+        formats[0] = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+        formats[1] = new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
+        formats[2] = new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US);
+        formats[0].setTimeZone(GMT_ZONE);
+        formats[1].setTimeZone(GMT_ZONE);
+        formats[2].setTimeZone(GMT_ZONE);
+    }
+
+    /**
+     * The default behavior of this method is to return getMethod()
+     * on the wrapped request object.
+     * @return the HTTP method
+     */
+    public String getMethod()
+    {
+        return savedRequest.getMethod();
+    }
+
+    /**
+     * The default behavior of this method is to return getHeader(String name)
+     * on the wrapped request object.
+     * @param name header name
+     * @return header value or null
+     */
+    public String getHeader(String name)
+    {
+        String header = null;
+        Iterator iterator = savedRequest.getHeaderValues(name);
+
+        while(iterator.hasNext())
+        {
+            header = (String)iterator.next();
+            break;
+        }
+        return header;
+    }
+
+    /**
+     * The default behavior of this method is to return getIntHeader(String name)
+     * on the wrapped request object.
+     * @param name integer header name
+     * @return integer header value or -1
+     */
+    public int getIntHeader(String name)
+    {
+        String value = getHeader(name);
+
+        if(value == null)
+        {
+            return(-1);
+        }
+        else
+        {
+            return(Integer.parseInt(value));
+        }
+    }
+
+    /**
+     * The default behavior of this method is to return getDateHeader(String name)
+     * on the wrapped request object.
+     * @param name date header name
+     * @return date header value or null
+     */
+    public long getDateHeader(String name)
+    {
+        String value = getHeader(name);
+
+        if(value == null)
+        {
+            return(-1L);
+        }
+
+        // Attempt to convert the date header in a variety of formats
+        long result = FastHttpDateFormat.parseDate(value, formats);
+
+        if(result != (-1L))
+        {
+            return result;
+        }
+        throw new IllegalArgumentException(value);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaderNames()
+     * on the wrapped request object.
+     * @return an enumeration of header names
+     */
+    public Enumeration getHeaderNames()
+    {
+        return new Enumerator(savedRequest.getHeaderNames());
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaders(String name)
+     * on the wrapped request object.
+     * @param name multivalued header
+     * @return enumeration of values for this header
+     */
+    public Enumeration getHeaders(String name)
+    {
+        return new Enumerator(savedRequest.getHeaderValues(name));
+    }
+
+    /**
+     * The default behavior of this method is to return getCookies()
+     * on the wrapped request object.
+     * @return cookies array
+     */
+    public Cookie[] getCookies()
+    {
+        List cookies = savedRequest.getCookies();
+
+        return(Cookie[])cookies.toArray(new Cookie[cookies.size()]);
+    }
+
+    /**
+     * The default behavior of this method is to return getParameterValues(String name)
+     * on the wrapped request object.
+     * @param name parameter name
+     * @value array of values
+     */
+    public String[] getParameterValues(String name)
+    {
+        return savedRequest.getParameterValues(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getParameterNames()
+     * on the wrapped request object.
+     * @return enumeration of parameter names
+     */
+    public Enumeration getParameterNames()
+    {
+        return new Enumerator(savedRequest.getParameterNames());
+    }
+
+    /**
+     * The default behavior of this method is to return getParameterMap()
+     * on the wrapped request object.
+     * @return parameter map
+     */
+    public Map getParameterMap()
+    {
+        return savedRequest.getParameterMap();
+    }
+
+    /**
+     * The default behavior of this method is to return getParameter(String name)
+     * on the wrapped request object.
+     * @param name parameter name
+     * @return  parameter value
+     */
+    public String getParameter(String name)
+    {
+        String[] values = savedRequest.getParameterValues(name);
+
+        if(values == null)
+        {
+            return null;
+        }
+        else
+        {
+            return values[0];
+        }
+    }
+
+    /**
+     * The default behavior of this method is to return getLocales()
+     * on the wrapped request object.
+     * @return enumeration of locales
+     */
+    public Enumeration getLocales()
+    {
+        Iterator iterator = savedRequest.getLocales();
+
+        if(iterator.hasNext())
+        {
+            return new Enumerator(iterator);
+        }
+        else
+        {
+            ArrayList results = new ArrayList();
+
+            results.add(defaultLocale);
+            return new Enumerator(results.iterator());
+        }
+    }
+
+    /**
+     * The default behavior of this method is to return getLocale()
+     * on the wrapped request object.
+     * @return locale
+     */
+    public Locale getLocale()
+    {
+        Locale locale = null;
+        Iterator iterator = savedRequest.getLocales();
+
+        while(iterator.hasNext())
+        {
+            locale = (Locale)iterator.next();
+            break;
+        }
+        if(locale == null)
+        {
+            return defaultLocale;
+        }
+        else
+        {
+            return locale;
+        }
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ServletLogWriter.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ServletLogWriter.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ServletLogWriter.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/ServletLogWriter.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,81 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import javax.servlet.ServletContext;
+
+/**
+ * This class implements a writer towards the servlet log.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class ServletLogWriter extends Writer
+{
+    /**
+     * build a new ServletLogWriter.
+     *
+     * @param log ServletContext
+     */
+    public ServletLogWriter(ServletContext log)
+    {
+        this.log = log;
+    }
+
+    /**
+     * write an array of chars to the servlet log.
+     *
+     * @param cbuf characters to write
+     * @param off offset in the array
+     * @param len number of characters to write
+     * @exception IOException thrown by underlying servlet logger
+     */
+    public void write(char[] cbuf, int off, int len) throws IOException
+    {
+        // ignore \r\n & \n
+        if((len == 2 && cbuf[off] == 13 && cbuf[off + 1] == 10) || (len == 1 && cbuf[off] == 10))
+        {
+            return;
+        }
+
+        String s = new String(cbuf, off, len);
+
+        log.log(s);
+    }
+
+    /**
+     * flush any pending output.
+     *
+     * @exception IOException thrown by underlying servlet logger
+     */
+    public void flush() throws IOException{}
+
+    /**
+     * close the writer.
+     *
+     * @exception IOException thrown by underlying servlet logger
+     */
+    public void close() throws IOException{}
+
+    /**
+     * the ServletContext object used to log.
+     */
+    private ServletContext log = null;
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/StringLists.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/StringLists.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/StringLists.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/util/StringLists.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,76 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * This static class gathers utilities for lists of strings.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ */
+public class StringLists
+{
+    /**
+     * builds an array list initially filled with <code>size</code> dummy objects.
+     *
+     * @param size size of the list
+     * @return the new ArrayList
+     */
+    public static List<String> getPopulatedArrayList(int size)
+    {
+        List<String> list = new ArrayList<String>(size);
+        String s = new String();
+
+        for(int i = 0; i < size; i++)
+        {
+            list.add(s);
+        }
+        return list;
+    }
+
+    /**
+     * joins strings using a separator.
+     *
+     * @param stringList strings to join
+     * @param joinString separator to include between strings
+     * @return the result of the join
+     */
+    public static String join(Collection stringList, String joinString)
+    {
+        Iterator i = stringList.iterator();
+        StringBuffer result = new StringBuffer();
+
+        if(!i.hasNext())
+        {
+            return "";
+        }
+        result.append(String.valueOf(i.next()));
+        while(i.hasNext())
+        {
+            result.append(joinString);
+            result.append(String.valueOf(i.next()));
+        }
+        return result.toString();
+    }
+}