You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC

svn commit: r627255 [38/41] - in /incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/ com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/ com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/CommentedProperties.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/CommentedProperties.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/CommentedProperties.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/CommentedProperties.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,235 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import java.io.*;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Map.Entry;
+
+import com.ecyrd.jspwiki.FileUtil;
+import com.ecyrd.jspwiki.TextUtil;
+
+/**
+ * Extends {@link java.util.Properties} by providing support for comment
+ * preservation. When the properties are written to disk, previous
+ * comments present in the file are preserved.
+ * @author Andrew Jaquith
+ * @author Janne Jalkanen
+ * @since 2.4.22
+ */
+public class CommentedProperties extends Properties
+{
+    private static final long serialVersionUID = 8057284636436329669L;
+
+    private String m_propertyString;
+
+    /**
+     * @see java.util.Properties#Properties()
+     */
+    public CommentedProperties()
+    {
+        super();
+    }
+
+    /**
+     *  Creates new properties.
+     *
+     *  @param defaultValues A list of default values, which are used if in subsequent gets
+     *                       a key is not found.
+     */
+    public CommentedProperties( Properties defaultValues )
+    {
+        super( defaultValues );
+    }
+
+    /**
+     *{@inheritDoc}
+     */
+    public synchronized void load( InputStream inStream ) throws IOException
+    {
+        // Load the file itself into a string
+        m_propertyString = FileUtil.readContents( inStream, "ISO-8859-1" );
+
+        // Now load it into the properties object as normal
+        super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) );
+    }
+
+    /**
+     *  {@inheritDoc}
+     */
+    public synchronized void load( Reader in ) throws IOException
+    {
+        m_propertyString = FileUtil.readContents( in );
+
+        // Now load it into the properties object as normal
+        super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized Object setProperty( String key, String value )
+    {
+        return put(key, value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void store( OutputStream out, String comments ) throws IOException
+    {
+        byte[] bytes = m_propertyString.getBytes("ISO-8859-1");
+        FileUtil.copyContents( new ByteArrayInputStream( bytes ), out );
+        out.flush();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized Object put( Object arg0, Object arg1 )
+    {
+        // Write the property to the stored string
+        writeProperty( arg0, arg1 );
+
+        // Return the result of from the superclass properties object
+        return super.put(arg0, arg1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void putAll( Map arg0 )
+    {
+        // Shove all of the entries into the property string
+        for ( Iterator it = arg0.entrySet().iterator(); it.hasNext(); )
+        {
+            Entry entry = (Entry)it.next();
+            writeProperty( entry.getKey(), entry.getValue() );
+        }
+
+        // Call the superclass method
+        super.putAll(arg0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized Object remove( Object key )
+    {
+        // Remove from the property string
+        deleteProperty( key );
+
+        // Call the superclass method
+        return super.remove(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized String toString()
+    {
+        return m_propertyString;
+    }
+
+    private void deleteProperty( Object arg0 )
+    {
+        // Get key and value
+        if ( arg0 == null )
+        {
+            throw new IllegalArgumentException( "Key cannot be null." );
+        }
+        String key = arg0.toString();
+
+        // Iterate through each line and replace anything matching our key
+        int idx = 0;
+        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
+        {
+            int prevret = m_propertyString.lastIndexOf( "\n", idx );
+            if ( prevret != -1 )
+            {
+                // Commented lines are skipped
+                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
+                {
+                    idx += key.length();
+                    continue;
+                }
+            }
+
+            // If "=" present, delete the entire line
+            int eqsign = m_propertyString.indexOf( "=", idx );
+            if ( eqsign != -1 )
+            {
+                int ret = m_propertyString.indexOf( "\n", eqsign );
+                m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" );
+                return;
+            }
+        }
+    }
+
+    private void writeProperty( Object arg0, Object arg1 )
+    {
+        // Get key and value
+        if ( arg0 == null )
+        {
+            throw new IllegalArgumentException( "Key cannot be null." );
+        }
+        if ( arg1 == null )
+        {
+            arg1 = "";
+        }
+        String key = arg0.toString();
+        String value = TextUtil.native2Ascii( arg1.toString() );
+
+        // Iterate through each line and replace anything matching our key
+        int idx = 0;
+        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
+        {
+            int prevret = m_propertyString.lastIndexOf( "\n", idx );
+            if ( prevret != -1 )
+            {
+                // Commented lines are skipped
+                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
+                {
+                    idx += key.length();
+                    continue;
+                }
+            }
+
+            // If "=" present, replace everything in line after it
+            int eqsign = m_propertyString.indexOf( "=", idx );
+            if ( eqsign != -1 )
+            {
+                int ret = m_propertyString.indexOf( "\n", eqsign );
+                if ( ret == -1 )
+                {
+                    ret = m_propertyString.length();
+                }
+                m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value );
+                return;
+            }
+        }
+
+        // If it was not found, we'll add it to the end.
+        m_propertyString += "\n" + key + " = " + value + "\n";
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/FormUtil.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/FormUtil.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/FormUtil.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/FormUtil.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,178 @@
+/*
+    WikiForms - a WikiPage FORM handler for JSPWiki.
+ 
+    Copyright (C) 2003 BaseN. 
+
+    JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published
+    by the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+ 
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+ 
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.util;
+
+import java.util.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * A collection of (static) utilities used by the WikiForms code.
+ * FormUtil is mainly concerned with mapping HTTP parameters to
+ * WikiPlugin parameters.
+ *
+ * @author ebu
+ */
+public final class FormUtil
+{
+    /**
+     * Private constructor to prevent direct instantiation.
+     */
+    private FormUtil()
+    {
+    }
+    
+    /**
+     * <p>Looks for a named value in the Map. Returns either the
+     * value named by key, or values named by key.0, key.1, ...
+     * if the direct value is not found. The values are packed
+     * in an ArrayList.</p>
+     * <p>This is a utility method, mainly used when we don't know
+     * whether there was just one value, or several, in a mapping list
+     * (e.g. an HttpRequest / FORM checkbox).</p>
+     * @param params the Map container form parameters
+     * @param key the key to look up
+     * @return the List of keys
+     */
+    public static List getValues( Map params, String key )
+    {
+        if( params == null || key == null )
+            return new ArrayList<Object>(0);
+
+        Object entry = params.get( key );
+        if( entry != null )
+        {
+            List<Object> rval = new ArrayList<Object>(1);
+            rval.add( entry );
+            return rval;
+        }
+
+        return getNumberedValues( params, key );
+    }
+
+
+    /**
+     * Looks up all keys starting with a given prefix and returns
+     * the values in an ArrayList. The keys must be Strings.
+     *
+     * <p>For example, calling this method for a Map containing
+     * key-value pairs foo.1 = a, foo.2 = b, and foo.3 = c returns
+     * an ArrayList containing [a, b, c].
+     *
+     * <p>Handles both 0- and 1-indexed names. Parsing stops at the
+     * first gap in the numeric postfix.
+     *
+     * @param params a Map of string-object pairs, presumably containing
+     *               key.1, key.2,...
+     * @param keyPrefix a String prefix; values will be looked up by adding
+     *                  ".0", ".1", and so on, until the first gap.
+     * @return ArrayList, containing the values corresponding to the
+     *          keyPrefix, in order.
+     */
+    public static List<Object> getNumberedValues( Map<String,Object> params, String keyPrefix )
+    {
+        List<Object> rval = new ArrayList<Object>();
+        if( params == null || 
+            params.size() == 0 || 
+            keyPrefix == null || 
+            keyPrefix.length() == 0 )
+            return rval;
+
+        String fullPrefix = null;
+        if( keyPrefix.charAt( keyPrefix.length() - 1 ) == '.' )
+            fullPrefix = keyPrefix;
+        else
+            fullPrefix = keyPrefix + ".";
+
+        int ix = 0;
+        Object value = params.get( fullPrefix + (ix++) );
+        if( value == null )
+            value = params.get( fullPrefix + (ix++) );
+        if( value == null )
+            return rval;
+        while( true )
+        {
+            rval.add( value );
+            value = params.get( fullPrefix + (ix++) );
+            if( value == null )
+                break;
+        }
+
+        return rval;
+    }
+
+
+    /**
+     * <p>Converts the parameter contents of an HTTP request into a map,
+     * modifying the keys to preserve multiple values per key. This
+     * is done by adding an ordered suffix to the key:</p>
+     * <p><pre>foo=bar,baz,xyzzy</pre></p>
+     * <p>becomes</p>
+     * <p><pre>foo.0=bar foo.1=baz foo.2=xyzzy</pre></p>
+     * <p>If filterPrefix is specified, only keys starting with the prefix
+     * are included in the result map. If the prefix is null, all keys are
+     * checked.</p>
+     * <p>FIX: this is not necessarily encoding-safe: see
+     * WikiContext.getHttpParameter().</p>
+     * @param req the HTTP request
+     * @param filterPrefix the prefix
+     * @return the Map containing parsed key/value pairs
+     */
+    public static Map<String,String> requestToMap( HttpServletRequest req, 
+                                    String filterPrefix )
+    {
+        Map<String,String> params = new HashMap<String,String>();
+        
+        if( filterPrefix == null ) filterPrefix = "";
+        
+        Enumeration en = req.getParameterNames();
+        while( en.hasMoreElements() )
+        {
+            String param = (String)en.nextElement();
+            
+            if( param.startsWith( filterPrefix ) )
+            {
+                String realName = param.substring( filterPrefix.length() );
+                String[] values = req.getParameterValues( param );
+                if( values != null )
+                {
+                    if( values.length == 1 )
+                    {
+                        params.put( realName, values[0] );
+                    }
+                    else
+                    {
+                        for( int i = 0; i < values.length; i++ )
+                        {
+                            if( values[i] != null && values[i].length() > 0 )
+                            {
+                                params.put( realName + "." + i, values[i] );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return params;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/HttpUtil.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/HttpUtil.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/HttpUtil.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/HttpUtil.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,224 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2003 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiPage;
+
+/**
+ *  Contains useful utilities for some common HTTP tasks.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.1.61.
+ */
+public final class HttpUtil
+{
+    static Logger log = Logger.getLogger( HttpUtil.class );
+    
+    /**
+     * Private constructor to prevent direct instantiation.
+     */
+    private HttpUtil()
+    {
+    }
+
+    /**
+     *  Attempts to retrieve the given cookie value from the request.
+     *  Returns the string value (which may or may not be decoded
+     *  correctly, depending on browser!), or null if the cookie is
+     *  not found. The algorithm will automatically trim leading
+     *  and trailing double quotes, if found.
+     *
+     *  @param request The current request
+     *  @param cookieName The name of the cookie to fetch.
+     *  @return Value of the cookie, or null, if there is no such cookie.
+     */
+
+    public static String retrieveCookieValue( HttpServletRequest request, String cookieName )
+    {
+        Cookie[] cookies = request.getCookies();
+
+        if( cookies != null )
+        {
+            for( int i = 0; i < cookies.length; i++ )
+            {
+                if( cookies[i].getName().equals( cookieName ) )
+                {
+                    String value = cookies[i].getValue();
+                    if ( value.length() == 0 )
+                    {
+                        return null;
+                    }
+                    if ( value.charAt( 0 ) == '"' && value.charAt( value.length() - 1 ) == '"')
+                    {
+                        value = value.substring( 1, value.length() - 1 );
+                    }
+                    return value;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     *  Creates an ETag based on page information.  An ETag is unique to each page
+     *  and version, so it can be used to check if the page has changed.  Do not
+     *  assume that the ETag is in any particular format.
+     *  
+     *  @param p  The page for which the ETag should be created.
+     *  @return A String depiction of an ETag.
+     */
+    public static String createETag( WikiPage p )
+    {
+        return Long.toString(p.getName().hashCode() ^ p.getLastModified().getTime());
+    }
+    
+    /**
+     *  If returns true, then should return a 304 (HTTP_NOT_MODIFIED)
+     *  @param req the HTTP request
+     *  @param page the wiki page to check for
+     *  @return the result of the check
+     */
+    public static boolean checkFor304( HttpServletRequest req,
+                                       WikiPage page )
+    {
+        //
+        //  We'll do some handling for CONDITIONAL GET (and return a 304)
+        //  If the client has set the following headers, do not try for a 304.
+        //
+        //    pragma: no-cache
+        //    cache-control: no-cache
+        //
+
+        if( "no-cache".equalsIgnoreCase(req.getHeader("Pragma"))
+            || "no-cache".equalsIgnoreCase(req.getHeader("cache-control"))) 
+        {
+            // Wants specifically a fresh copy
+        } 
+        else 
+        {
+            //
+            //  HTTP 1.1 ETags go first
+            //
+            String thisTag = createETag( page );
+                        
+            String eTag = req.getHeader( "If-None-Match" );
+            
+            if( eTag != null )
+            {
+                if( eTag.equals(thisTag) )
+                {
+                    return true;
+                }
+            }
+            
+            //
+            //  Next, try if-modified-since
+            //
+            DateFormat rfcDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
+            Date lastModified = page.getLastModified();
+
+            try
+            {
+                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
+
+                //log.info("ifModifiedSince:"+ifModifiedSince);
+                if( ifModifiedSince != -1 )
+                {
+                    long lastModifiedTime = lastModified.getTime();
+
+                    //log.info("lastModifiedTime:" + lastModifiedTime);
+                    if( lastModifiedTime <= ifModifiedSince )
+                    {
+                        return true;
+                    }
+                } 
+                else
+                {
+                    try 
+                    {
+                        String s = req.getHeader("If-Modified-Since");
+
+                        if( s != null ) 
+                        {
+                            Date ifModifiedSinceDate = rfcDateFormat.parse(s);
+                            //log.info("ifModifiedSinceDate:" + ifModifiedSinceDate);
+                            if( lastModified.before(ifModifiedSinceDate) ) 
+                            {
+                                return true;
+                            }
+                        }
+                    } 
+                    catch (ParseException e) 
+                    {
+                        log.warn(e.getLocalizedMessage(), e);
+                    }
+                }
+            }
+            catch( IllegalArgumentException e )
+            {
+                // Illegal date/time header format.
+                // We fail quietly, and return false.
+                // FIXME: Should really move to ETags.
+            }
+        }
+         
+        return false;
+    }
+
+    /**
+     *  Attempts to form a valid URI based on the string given.  Currently
+     *  it can guess email addresses (mailto:).  If nothing else is given,
+     *  it assumes it to be a http:// url.
+     * 
+     *  @param uri  URI to take a poke at
+     *  @return Possibly a valid URI
+     *  @since 2.2.8
+     */
+    public static String guessValidURI( String uri )
+    {
+        if( uri.indexOf('@') != -1 )
+        {
+            if( !uri.startsWith("mailto:") )
+            {
+                // Assume this is an email address
+            
+                uri = "mailto:"+uri;
+            }
+        }
+        else if( uri.length() > 0 && !((uri.startsWith("http://") || uri.startsWith("https://")) ))
+        {
+            uri = "http://"+uri;
+        }
+        
+        return uri;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/MailUtil.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/MailUtil.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/MailUtil.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/MailUtil.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,469 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import java.util.Date;
+import java.util.Properties;
+
+import javax.mail.*;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiEngine;
+
+/**
+ * <p>Contains static methods for sending e-mails to recipients using JNDI-supplied
+ * <a href="http://java.sun.com/products/javamail/">JavaMail</a>
+ * Sessions supplied by a web container (preferred) or configured via
+ * <code>jspwiki.properties</code>; both methods are described below.
+ * Because most e-mail servers require authentication,
+ * for security reasons implementors are <em>strongly</em> encouraged to use
+ * container-managed JavaMail Sessions so that passwords are not exposed in
+ * <code>jspwiki.properties</code>.</p>
+ * <p>To enable e-mail functions within JSPWiki, administrators must do three things:
+ * ensure that the required JavaMail JARs are on the runtime classpath, configure
+ * JavaMail appropriately, and (recommdended) configure the JNDI JavaMail session factory.</p>
+ * <strong>JavaMail runtime JARs</strong>
+ * <p>The first step is easy: JSPWiki bundles
+ * recent versions of the required JavaMail <code>mail.jar</code> and
+ * <code>activation.jar</code> into the JSPWiki WAR file; so, out of the box
+ * this is already taken care of. However, when using JNDI-supplied
+ * Session factories, these should be moved, <em>not copied</em>, to a classpath location
+ * where the JARs can be shared by both the JSPWiki webapp and the container. For example,
+ * Tomcat 5 provides the directory <code><var>$CATALINA_HOME></var>/common/lib</code>
+ * for storage of shared JARs; move <code>mail.jar</code> and <code>activation</code>
+ * there instead of keeping them in <code>/WEB-INF/lib</code>.</p>
+ * <strong>JavaMail configuration</strong>
+ * <p>Regardless of the method used for supplying JavaMail sessions (JNDI container-managed
+ * or via <code>jspwiki.properties</code>, JavaMail needs certain properties
+ * set in order to work correctly. Configurable properties are these:</p>
+ * <table border="1">
+ *   <tr>
+ *   <thead>
+ *     <th>Property</th>
+ *     <th>Default</th>
+ *     <th>Definition</th>
+ *   <thead>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.mail.jndiname</code></td>
+ *     <td><code>mail/Session</code></td>
+ *     <td>The JNDI name of the JavaMail session factory</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.host</code></td>
+ *     <td><code>127.0.0.1</code></td>
+ *     <td>The SMTP mail server from which messages will be sent.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.port</code></td>
+ *     <td><code>25</code></td>
+ *     <td>The port number of the SMTP mail service.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.account</code></td>
+ *     <td>(not set)</td>
+ *     <td>The user name of the sender. If this value is supplied, the JavaMail
+ *     session will attempt to authenticate to the mail server before sending
+ *     the message. If not supplied, JavaMail will attempt to send the message
+ *     without authenticating (i.e., it will use the server as an open relay).
+ *     In real-world scenarios, you should set this value.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.password</code></td>
+ *     <td>(not set)</td>
+ *     <td>The password of the sender. In real-world scenarios, you
+ *     should set this value.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.from</code></td>
+ *     <td><code><var>${user.name}</var>@<var>${mail.smtp.host}</var>*</code></td>
+ *     <td>The e-mail address of the sender.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.timeout</code></td>
+ *     <td><code>5000*</code></td>
+ *     <td>Socket I/O timeout value, in milliseconds. The default is 5 seconds.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.connectiontimeout</code></td>
+ *     <td><code>5000*</code></td>
+ *     <td>Socket connection timeout value, in milliseconds. The default is 5 seconds.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>mail.smtp.starttls.enable</code></td>
+ *     <td><code>true*</code></td>
+ *     <td>If true, enables the use of the STARTTLS command (if
+ *     supported by the server) to switch the connection to a
+ *     TLS-protected connection before issuing any login commands.
+ *     Note that an appropriate trust store must configured so that
+ *     the client will trust the server's certificate. By default,
+ *     the JRE trust store contains root CAs for most public certificate
+ *     authorities.</td>
+ *   </tr>
+ * </table>
+ * <p>*These defaults apply only if the stand-alone Session factory is used
+ * (that is, these values are obtained from <code>jspwiki.properties</code>).
+ * If using a container-managed JNDI Session factory, the container will
+ * likely supply its own default values, and you should probably override
+ * them (see the next section).</p>
+ * <strong>Container JNDI Session factory configuration</strong>
+ * <p>You are strongly encouraged to use a container-managed JNDI factory for
+ * JavaMail sessions, rather than configuring JavaMail through <code>jspwiki.properties</code>.
+ * To do this, you need to two things: uncomment the <code>&lt;resource-ref&gt;</code> block
+ * in <code>/WEB-INF/web.xml</code> that enables container-managed JavaMail, and
+ * configure your container's JavaMail resource factory. The <code>web.xml</code>
+ * part is easy: just uncomment the section that looks like this:</p>
+ * <pre>&lt;resource-ref&gt;
+ *   &lt;description>Resource reference to a container-managed JNDI JavaMail factory for sending e-mails.&lt;/description&gt;
+ *   &lt;res-ref-name>mail/Session&lt;/res-ref-name&gt;
+ *   &lt;res-type>javax.mail.Session&lt;/res-type&gt;
+ *   &lt;res-auth>Container&lt;/res-auth&gt;
+ * &lt;/resource-ref&gt;</pre>
+ * <p>To configure your container's resource factory, follow the directions supplied by
+ * your container's documentation. For example, the
+ * <a href="http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html#JavaMail%20Sessions">Tomcat
+ * 5.5 docs</a> state that you need a properly configured <code>&lt;Resource&gt;</code>
+ * element inside the JSPWiki webapp's <code>&lt;Context&gt;</code> declaration. Here's an example shows
+ * how to do it:</p>
+ * <pre>&lt;Context ...&gt;
+ * ...
+ * &lt;Resource name="mail/Session" auth="Container"
+ *           type="javax.mail.Session"
+ *           mail.smtp.host="127.0.0.1"/&gt;
+ *           mail.smtp.port="25"/&gt;
+ *           mail.smtp.account="your-account-name"/&gt;
+ *           mail.smtp.password="your-password"/&gt;
+ *           mail.from="Snoop Dogg &lt;snoop@dogg.org&gt;"/&gt;
+ *           mail.smtp.timeout="5000"/&gt;
+ *           mail.smtp.connectiontimeout="5000"/&gt;
+ *           mail.smtp.starttls.enable="true"/&gt;
+ * ...
+ * &lt;/Context&gt;</pre>
+ * <p>Note that with Tomcat (and most other application containers) you can also declare the JavaMail
+ * JNDI factory as a global resource, shared by all applications, instead of as a local JSPWiki
+ * resource as we have done here. For example, the following entry in
+ * <code><var>$CATALINA_HOME</var>/conf/server.xml</code> creates a global resource:</p>
+ * <pre>&lt;GlobalNamingResources&gt;
+ *   &lt;Resource name="mail/Session" auth="Container"
+ *             type="javax.mail.Session"
+ *             ...
+ *             mail.smtp.starttls.enable="true"/&gt;
+ * &lt;/GlobalNamingResources&gt;</pre>
+ * <p>This approach &#8212; creating a global JNDI resource &#8212; yields somewhat decreased
+ * deployment complexity because the JSPWiki webapp no longer needs its own JavaMail resource
+ * declaration. However, it is slightly less secure because it means that all other applications
+ * can now obtain a JavaMail session if they want to. In many cases, this <em>is</em> what
+ * you want.</p>
+ * <p>NOTE: Versions of Tomcat 5.5 later than 5.5.17, and up to and including 5.5.23 have a
+ * b0rked version of <code><var>$CATALINA_HOME</var>/common/lib/naming-factory.jar</code>
+ * that prevents usage of JNDI. To avoid this problem, you should patch your 5.5.23 version
+ * of <code>naming-factory.jar</code> with the one from 5.5.17. This is a known issue
+ * and the bug report (#40668) is
+ * <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=40668">here</a>.
+ *
+ * @author Christoph Sauer
+ * @author Dan Frankowski
+ * @author Andrew Jaquith
+ */
+public final class MailUtil
+{
+    private static final String JAVA_COMP_ENV = "java:comp/env";
+
+    private static final String FALSE = "false";
+
+    private static final String TRUE = "true";
+
+    private static boolean c_useJndi = true;
+
+    public static final String PROP_MAIL_AUTH = "mail.smtp.auth";
+
+    protected static final Logger log = Logger.getLogger(MailUtil.class);
+
+    protected static final String DEFAULT_MAIL_JNDI_NAME       = "mail/Session";
+
+    protected static final String DEFAULT_MAIL_HOST            = "localhost";
+
+    protected static final String DEFAULT_MAIL_PORT            = "25";
+
+    protected static final String DEFAULT_MAIL_TIMEOUT         = "5000";
+    
+    protected static final String DEFAULT_SENDER               = "jspwiki@localhost";
+
+    protected static final String PROP_MAIL_JNDI_NAME          = "jspwiki.mail.jndiname";
+
+    protected static final String PROP_MAIL_HOST               = "mail.smtp.host";
+
+    protected static final String PROP_MAIL_PORT               = "mail.smtp.port";
+
+    protected static final String PROP_MAIL_ACCOUNT            = "mail.smtp.account";
+
+    protected static final String PROP_MAIL_PASSWORD           = "mail.smtp.password";
+
+    protected static final String PROP_MAIL_TIMEOUT            = "mail.smtp.timeout";
+
+    protected static final String PROP_MAIL_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
+
+    protected static final String PROP_MAIL_TRANSPORT          = "smtp";
+
+    protected static final String PROP_MAIL_SENDER             = "mail.from";
+
+    protected static final String PROP_MAIL_STARTTLS           = "mail.smtp.starttls.enable";
+
+    /**
+     *  Private constructor prevents instantiation.
+     */
+    private MailUtil()
+    {
+    }
+
+    /**
+     * <p>Sends an e-mail to a specified receiver using a  JavaMail Session supplied
+     * by a JNDI mail session factory (preferred) or a locally initialized
+     * session based on properties in <code>jspwiki.properties</code>.
+     * See the top-level JavaDoc for this class for a description of
+     * required properties and their default values.</p>
+     * <p>The e-mail address used for the <code>to</code> parameter must be in
+     * RFC822 format, as described in the JavaDoc for {@link javax.mail.internet.InternetAddress}
+     * and more fully at
+     * <a href="http://www.freesoft.org/CIE/RFC/822/index.htm">http://www.freesoft.org/CIE/RFC/822/index.htm</a>.
+     * In other words, e-mail addresses should look like this:</p>
+     * <blockquote><code>Snoop Dog &lt;snoop.dog@shizzle.net&gt;<br/>
+     * snoop.dog@shizzle.net</code></blockquote>
+     * <p>Note that the first form allows a "friendly" user name to be supplied
+     * in addition to the actual e-mail address.</p>
+     *
+     * @param engine the WikiEngine for the current wiki
+     * @param to the receiver
+     * @param subject the subject line of the message
+     * @param content the contents of the mail message, as plain text
+     */
+    public static void sendMessage( WikiEngine engine, String to, String subject, String content )
+        throws AddressException, MessagingException
+    {
+        String from = engine.getWikiProperties().getProperty( PROP_MAIL_SENDER, DEFAULT_SENDER ).trim();
+        sendMessage( engine, to, from, subject, content );
+    }
+
+    /**
+     * <p>Sends an e-mail to a specified receiver from a specified sender, using a
+     * JavaMail Session supplied by a JNDI mail session factory (preferred) or
+     * a locally initialized session based on properties in
+     * <code>jspwiki.properties</code>. See the top-level JavaDoc for this
+     * class for a description of required properties and their
+     * default values.</p>
+     * <p>The e-mail addresses used for the <code>to</code> and <code>from</code>
+     * parameters must be in RFC822 format, as described in the JavaDoc for
+     * {@link javax.mail.internet.InternetAddress} and more fully at
+     * <a href="http://www.freesoft.org/CIE/RFC/822/index.htm">http://www.freesoft.org/CIE/RFC/822/index.htm</a>.
+     * In other words, e-mail addresses should look like this:</p>
+     * <blockquote><code>Snoop Dog &lt;snoop.dog@shizzle.net&gt;<br/>
+     * snoop.dog@shizzle.net</code></blockquote>
+     * <p>Note that the first form allows a "friendly" user name to be supplied
+     * in addition to the actual e-mail address.</p>
+     *
+     * @param engine the WikiEngine for the current wiki
+     * @param to the receiver
+     * @param from the address the email will be from
+     * @param subject the subject line of the message
+     * @param content the contents of the mail message, as plain text
+     */
+    public static void sendMessage(WikiEngine engine, String to, String from, String subject, String content)
+        throws MessagingException
+    {
+        Properties props = engine.getWikiProperties();
+        String jndiName = props.getProperty( PROP_MAIL_JNDI_NAME, DEFAULT_MAIL_JNDI_NAME ).trim();
+        Session session = null;
+
+        if (c_useJndi)
+        {
+            // Try getting the Session from the JNDI factory first
+            try
+            {
+                session = getJNDIMailSession(jndiName);
+                c_useJndi = false;
+            }
+            catch (NamingException e)
+            {
+                // Oops! JNDI factory must not be set up
+            }
+        }
+
+        // JNDI failed; so, get the Session from the standalone factory
+        if ( session == null )
+        {
+            session = getStandaloneMailSession( props );
+        }
+
+        try
+        {
+            // Create and address the message
+            MimeMessage msg = new MimeMessage( session );
+            msg.setFrom( new InternetAddress( from ) );
+            msg.setRecipients( Message.RecipientType.TO, InternetAddress.parse( to, false ) );
+            msg.setSubject( subject );
+            msg.setText( content, "UTF-8" );
+            msg.setSentDate( new Date() );
+
+            // Send and log it
+            Transport.send( msg );
+            if ( log.isInfoEnabled() )
+            {
+                log.info( "Sent e-mail to=" + to + ", subject=\"" + subject
+                    + "\", jndi=" + ( c_useJndi ? TRUE : FALSE ) );
+            }
+        }
+        catch ( MessagingException e )
+        {
+            log.error( e );
+            throw e;
+        }
+    }
+
+    // --------- JavaMail Session Helper methods ---------------------------------
+
+    /**
+     * Returns a stand-alone JavaMail Session by looking up the correct
+     * mail account, password and host from a supplied set of properties.
+     * If the JavaMail property {@value #PROP_MAIL_ACCOUNT} is set to
+     * a value that is non-<code>null</code> and of non-zero length, the
+     * Session will be initialized with an instance of
+     * {@link javax.mail.Authenticator}.
+     * @param props the properties that contain mail session properties
+     * @return the initialized JavaMail Session
+     */
+    protected static Session getStandaloneMailSession( Properties props )
+    {
+        // Read the JSPWiki settings from the properties
+        String host     = props.getProperty( PROP_MAIL_HOST, DEFAULT_MAIL_HOST );
+        String port     = props.getProperty( PROP_MAIL_PORT, DEFAULT_MAIL_PORT );
+        String account  = props.getProperty( PROP_MAIL_ACCOUNT );
+        String password = props.getProperty( PROP_MAIL_PASSWORD );
+        boolean starttls = TextUtil.getBooleanProperty( props, PROP_MAIL_STARTTLS, true);
+        
+        boolean useAuthentication = account != null && account.length() > 0;
+
+        Properties mailProps = new Properties();
+
+        // Set JavaMail properties
+        mailProps.put( PROP_MAIL_HOST, host );
+        mailProps.put( PROP_MAIL_PORT, port );
+        mailProps.put( PROP_MAIL_TIMEOUT, DEFAULT_MAIL_TIMEOUT );
+        mailProps.put( PROP_MAIL_CONNECTION_TIMEOUT, DEFAULT_MAIL_TIMEOUT );
+        mailProps.put( PROP_MAIL_STARTTLS, starttls ? TRUE : FALSE );
+
+        // Add SMTP authentication if required
+        Session session = null;
+        if ( useAuthentication )
+        {
+            mailProps.put( PROP_MAIL_AUTH, TRUE );
+            SmtpAuthenticator auth = new SmtpAuthenticator( account, password );
+
+            session = Session.getInstance( mailProps, auth );
+        }
+        else
+        {
+            session = Session.getInstance( mailProps );
+        }
+
+        if ( log.isDebugEnabled() )
+        {
+            String mailServer = host + ":" + port + ", auth=" + ( useAuthentication ? TRUE : FALSE );
+            log.debug( "JavaMail session obtained from standalone mail factory: " + mailServer );
+        }
+        return session;
+    }
+
+
+    /**
+     * Returns a JavaMail Session instance from a JNDI container-managed factory.
+     * @param jndiName the JNDI name for the resource. If <code>null</code>, the default value
+     * of <code>mail/Session</code> will be used
+     * @return the initialized JavaMail Session
+     * @throws NamingException if the Session cannot be obtained; for example, if the factory is not configured
+     */
+    protected static Session getJNDIMailSession( String jndiName ) throws NamingException
+    {
+        Session session = null;
+        try
+        {
+            Context initCtx = new InitialContext();
+            Context ctx = (Context) initCtx.lookup( JAVA_COMP_ENV );
+            session = (Session) ctx.lookup( jndiName );
+        }
+        catch( NamingException e )
+        {
+            log.warn( "JavaMail initialization error: " + e.getMessage() );
+            throw e;
+        }
+        if ( log.isDebugEnabled() )
+        {
+            log.debug( "JavaMail session obtained from JNDI mail factory: " + jndiName );
+        }
+        return session;
+    }
+
+    /**
+     * Simple {@link javax.mail.Authenticator} subclass that authenticates a user to
+     * an SMTP server.
+     * @author Christoph Sauer
+     */
+    protected static class SmtpAuthenticator extends Authenticator
+    {
+
+        private static final String BLANK = "";
+        private final String m_pass;
+        private final String m_login;
+
+        /**
+         * Constructs a new SmtpAuthenticator with a supplied username and password.
+         * @param login the user name
+         * @param pass the password
+         */
+        public SmtpAuthenticator(String login, String pass)
+        {
+            super();
+            m_login =   login == null ? BLANK : login;
+            m_pass =     pass == null ? BLANK : pass;
+        }
+
+        /**
+         * Returns the password used to authenticate to the SMTP server.
+         */
+        public PasswordAuthentication getPasswordAuthentication()
+        {
+            if ( BLANK.equals(m_pass) )
+            {
+                return null;
+            }
+
+            return new PasswordAuthentication( m_login, m_pass );
+        }
+
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/PriorityList.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/PriorityList.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/PriorityList.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/PriorityList.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,118 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2003 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *  Builds a simple, priority-based List implementation.  The list
+ *  will be sorted according to the priority.  If two items are
+ *  inserted with the same priority, their order is the insertion order - i.e. the new one
+ *  is appended last in the insertion list.
+ *  <p>
+ *  Priority is an integer, and the list is sorted in descending order
+ *  (that is, 100 is before 10 is before 0 is before -40).
+ *
+ *  @author Janne Jalkanen
+ */
+public class PriorityList<T>
+    extends AbstractList<T>
+{
+    private final List<Item<T>> m_elements = new ArrayList<Item<T>>();
+
+    /**
+     *  This is the default priority, which is used if no priority
+     *  is defined.  It's current value is zero.
+     */
+    public static final int DEFAULT_PRIORITY = 0;
+
+    /**
+     *  Adds an object to its correct place in the list, using the
+     *  given priority.
+     *
+     *  @param o Object to add.
+     *  @param priority Priority.
+     */
+    public void add( T o, int priority )
+    {
+        int i = 0;
+
+        for( ; i < m_elements.size(); i++ )
+        {
+            Item item = m_elements.get(i);
+
+            if( item.m_priority < priority )
+            {
+                break;
+            }
+        }
+
+        Item<T> newItem = new Item<T>();
+        newItem.m_priority = priority;
+        newItem.m_object   = o;
+
+        m_elements.add( i, newItem );
+    }
+
+    /**
+     *  Adds an object using the default priority to the List.
+     *
+     *  @param o Object to add.
+     *  @return true, as per the general Collections.add contract.
+     */
+    public boolean add( T o )
+    {
+        add( o, DEFAULT_PRIORITY );
+
+        return true;
+    }
+
+    /**
+     *  Returns the object at index "index".
+     *
+     *  @param index The index.
+     *  @return The object at the list at the position "index".
+     */
+    public T get( int index )
+    {
+        return m_elements.get( index ).m_object;
+    }
+
+    /**
+     *  Returns the current size of the list.
+     *  
+     *  @return size of the list.
+     */
+    public int size()
+    {
+        return m_elements.size();
+    }
+
+    /**
+     *  Provides a holder for the priority-object 2-tuple.
+     */
+    private static class Item<T>
+    {
+        public int     m_priority;
+        public T m_object;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/ProviderConverter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/ProviderConverter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/ProviderConverter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/ProviderConverter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,162 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.commons.lang.SystemUtils;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.providers.AbstractFileProvider;
+import com.ecyrd.jspwiki.providers.VersioningFileProvider;
+import com.ecyrd.jspwiki.providers.WikiPageProvider;
+
+/**
+ *  A command-line application that allows you to convert from
+ *  one provider to another.  Currently this only supports
+ *  conversion from RCSFileProvider to VersioningFileProvider.
+ *  <p>
+ *  This class is mostly a hack, so do not trust it very much.
+ *  It leaves the converted directory in /tmp/converter-tmp/ 
+ *  and does not touch the original in any way.
+ *  
+ *  @author jalkanen
+ *
+ *  @since
+ */
+public class ProviderConverter
+{
+    private String m_rcsSourceDir;
+    
+    protected void setRCSSourceDir( String dir )
+    {
+        m_rcsSourceDir = dir;
+    }
+
+    private static final String[] WINDOWS_DEVICE_NAMES =
+    {
+        "con", "prn", "nul", "aux", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
+        "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9"
+    };
+    
+    protected String mangleName( String pagename )
+    {
+        pagename = TextUtil.urlEncode( pagename, "UTF-8" );
+        
+        pagename = TextUtil.replaceString( pagename, "/", "%2F" );
+
+        if( SystemUtils.IS_OS_WINDOWS )
+        {
+            String pn = pagename.toLowerCase();
+            for( int i = 0; i < WINDOWS_DEVICE_NAMES.length; i++ )
+            {
+                if( WINDOWS_DEVICE_NAMES[i].equals(pn) )
+                {
+                    pagename = "$$$" + pagename;
+                }
+            }
+        }
+        
+        return pagename;
+    }
+
+    protected void convert()
+        throws WikiException, IOException
+    {
+        Properties props = new Properties();
+        
+        props.setProperty( WikiEngine.PROP_APPNAME, "JSPWikiConvert" );
+        props.setProperty( AbstractFileProvider.PROP_PAGEDIR, m_rcsSourceDir );
+        props.setProperty( PageManager.PROP_PAGEPROVIDER, "RCSFileProvider" );
+        props.setProperty( PageManager.PROP_USECACHE, "false" );
+        props.setProperty( "log4j.appender.outlog", "org.apache.log4j.ConsoleAppender" );
+        props.setProperty( "log4j.appender.outlog.layout", "org.apache.log4j.PatternLayout" );
+        props.setProperty( "jspwiki.useLucene", "false" );
+        props.setProperty( "log4j.rootCategory", "INFO,outlog" );
+        WikiEngine engine = new WikiEngine( props );
+
+        WikiPageProvider sourceProvider = engine.getPageManager().getProvider();
+        
+        File tmpDir = new File( SystemUtils.JAVA_IO_TMPDIR, "converter-tmp" );
+        
+        props.setProperty( AbstractFileProvider.PROP_PAGEDIR, tmpDir.getAbsolutePath() );
+        WikiPageProvider destProvider = new VersioningFileProvider();
+        
+        destProvider.initialize( engine, props );
+        
+        Collection allPages = sourceProvider.getAllPages();
+        
+        int idx = 1;
+        
+        for( Iterator i = allPages.iterator(); i.hasNext(); )
+        {
+            WikiPage p = (WikiPage)i.next();
+            
+            System.out.println("Converting page: "+p.getName()+" ("+idx+"/"+allPages.size()+")");
+            List pageHistory = engine.getVersionHistory( p.getName() );
+            
+            
+            for( ListIterator v = pageHistory.listIterator(pageHistory.size()); v.hasPrevious(); )
+            {
+                WikiPage pv = (WikiPage)v.previous();
+                
+                String text = engine.getPureText( pv.getName(), pv.getVersion() );
+                
+                destProvider.putPageText( pv, text );
+            }
+            
+            //
+            //  Do manual setting now
+            //
+            
+            for( Iterator v = pageHistory.iterator(); v.hasNext(); )
+            {
+                WikiPage pv = (WikiPage)v.next();
+
+                File f = new File( tmpDir, "OLD" );
+                f = new File( f, mangleName(pv.getName()) );
+                f = new File( f, pv.getVersion()+".txt" );
+                
+                System.out.println("   Setting old version "+pv.getVersion()+" to date "+pv.getLastModified() );
+                f.setLastModified( pv.getLastModified().getTime() );
+            }
+            
+            idx++;
+        }
+        
+        tmpDir.delete();
+    }
+    
+    /**
+     * @param args
+     */
+    public static void main( String[] args )
+        throws Exception
+    {
+        ProviderConverter c = new ProviderConverter();
+        
+        c.setRCSSourceDir( args[0] );
+        
+        c.convert();
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WatchDog.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WatchDog.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WatchDog.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WatchDog.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,459 @@
+/*
+  JSPWiki - a JSP-based WikiWiki clone.
+
+  Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+package com.ecyrd.jspwiki.util;
+
+import java.lang.ref.WeakReference;
+import java.util.*;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiEngine;
+
+/**
+ *  WatchDog is a general system watchdog.  You can attach any Watchable
+ *  or a Thread object to it, and it will notify you if a timeout has been
+ *  exceeded.
+ *  <p>
+ *  The notification of the timeouts is done from a separate WatchDog thread,
+ *  of which there is one per watched thread.  This Thread is named 'WatchDog for
+ *  XXX', where XXX is your Thread name.
+ *  <p>
+ *  The suggested method of obtaining a WatchDog is via the static factory
+ *  method, since it will return you the correct watchdog for the current
+ *  thread.  However, we do not prevent you from creating your own watchdogs
+ *  either.
+ *  <p>
+ *  If you create a WatchDog for a Thread, the WatchDog will figure out when
+ *  the Thread is dead, and will stop itself accordingly.  However, this object
+ *  is not automatically released, so you might want to check it out after a while.
+ *
+ *  @author Janne Jalkanen
+ *  @since  2.4.92
+ */
+public final class WatchDog
+{
+    private Watchable m_watchable;
+    private Stack<State> m_stateStack = new Stack<State>();
+    private boolean   m_enabled    = true;
+    private WikiEngine m_engine;
+
+    private static Logger log = Logger.getLogger(WatchDog.class.getName());
+
+    private static HashMap<Integer,WeakReference<WatchDog>> c_kennel = new HashMap<Integer,WeakReference<WatchDog>>();
+    private static WikiBackgroundThread c_watcherThread;
+
+    /**
+     *  Returns the current watchdog for the current thread. This
+     *  is the preferred method of getting you a Watchdog, since it
+     *  keeps an internal list of Watchdogs for you so that there
+     *  won't be more than one watchdog per thread.
+     *
+     *  @param engine The WikiEngine to which the Watchdog should
+     *                be bonded to.
+     *  @return A usable WatchDog object.
+     */
+    public static WatchDog getCurrentWatchDog( WikiEngine engine )
+    {
+        Thread t = Thread.currentThread();
+        WatchDog wd = null;
+
+        WeakReference<WatchDog> w = c_kennel.get( new Integer(t.hashCode()) );
+
+        if( w != null ) wd = w.get();
+
+        if( w == null || wd == null )
+        {
+            wd = new WatchDog( engine, t );
+            w = new WeakReference<WatchDog>(wd);
+
+            synchronized( c_kennel )
+            {
+                c_kennel.put( new Integer(t.hashCode()), w );
+            }
+        }
+
+        return wd;
+    }
+
+    /**
+     *  Creates a new WatchDog for a Watchable.
+     *
+     *  @param engine  The WikiEngine.
+     *  @param watch   A Watchable object.
+     */
+    public WatchDog(WikiEngine engine, Watchable watch)
+    {
+        m_engine    = engine;
+        m_watchable = watch;
+
+        synchronized(this.getClass())
+        {
+            if( c_watcherThread == null )
+            {
+                c_watcherThread = new WatchDogThread( engine );
+
+                c_watcherThread.start();
+            }
+        }
+    }
+
+    /**
+     *  Creates a new WatchDog for a Thread.  The Thread is wrapped
+     *  in a Watchable wrapper for this purpose.
+     *
+     *  @param engine The WikiEngine
+     *  @param thread A Thread for watching.
+     */
+    public WatchDog(WikiEngine engine, Thread thread)
+    {
+        this( engine, new ThreadWrapper(thread) );
+    }
+
+    /**
+     *  Hopefully finalizes this properly.  This is rather untested
+     *  for now...
+     */
+    private static void scrub()
+    {
+        //
+        //  During finalization, the object may already be cleared (depending
+        //  on the finalization order).  Therefore, it's possible that this
+        //  method is called from another thread after the WatchDog itself
+        //  has been cleared.
+        //
+        if( c_kennel == null ) return;
+
+        synchronized( c_kennel )
+        {
+            for( Map.Entry<Integer,WeakReference<WatchDog>> e : c_kennel.entrySet() )
+            {
+                WeakReference w = e.getValue();
+
+                //
+                //  Remove expired as well
+                //
+                if( w.get() == null )
+                {
+                    c_kennel.remove( e.getKey() );
+                    scrub();
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     *  Can be used to enable the WatchDog.  Will cause a new
+     *  Thread to be created, if none was existing previously.
+     *
+     */
+    public void enable()
+    {
+        synchronized(this.getClass())
+        {
+            if( !m_enabled )
+            {
+                m_enabled = true;
+                c_watcherThread = new WatchDogThread( m_engine );
+                c_watcherThread.start();
+            }
+        }
+    }
+
+    /**
+     *  Is used to disable a WatchDog.  The watchdog thread is
+     *  shut down and resources released.
+     *
+     */
+    public void disable()
+    {
+        synchronized(this.getClass())
+        {
+            if( m_enabled )
+            {
+                m_enabled = false;
+                c_watcherThread.shutdown();
+                c_watcherThread = null;
+            }
+        }
+    }
+
+    /**
+     *  Enters a watched state with no expectation of the expected completion time.
+     *  In practice this method is used when you have no idea, but would like to figure
+     *  out, e.g. via debugging, where exactly your Watchable is.
+     *
+     *  @param state A free-form string description of your state.
+     */
+    public void enterState( String state )
+    {
+        enterState( state, Integer.MAX_VALUE );
+    }
+
+    /**
+     *  Enters a watched state which has an expected completion time.  This is the
+     *  main method for using the WatchDog.  For example:
+     *
+     *  <code>
+     *     WatchDog w = m_engine.getCurrentWatchDog();
+     *     w.enterState("Processing Foobar", 60);
+     *     foobar();
+     *     w.exitState();
+     *  </code>
+     *
+     *  If the call to foobar() takes more than 60 seconds, you will receive an
+     *  ERROR in the log stream.
+     *
+     *  @param state A free-form string description of the state
+     *  @param expectedCompletionTime The timeout in seconds.
+     */
+    public void enterState( String state, int expectedCompletionTime )
+    {
+        if( log.isDebugEnabled() )
+            log.debug( m_watchable.getName()+": Entering state "+state+", expected completion in "+expectedCompletionTime+" s");
+
+        synchronized( m_stateStack )
+        {
+            State st = new State( state, expectedCompletionTime );
+
+            m_stateStack.push( st );
+        }
+    }
+
+    /**
+     *  Exits a state entered with enterState().  This method does not check
+     *  that the state is correct, it'll just pop out whatever is on the top
+     *  of the state stack.
+     *
+     */
+    public void exitState()
+    {
+        exitState(null);
+    }
+
+    /**
+     *  Exits a particular state entered with enterState().  The state is
+     *  checked against the current state, and if they do not match, an error
+     *  is flagged.
+     *
+     *  @param state The state you wish to exit.
+     */
+    public void exitState( String state )
+    {
+        try
+        {
+            synchronized( m_stateStack )
+            {
+                State st = m_stateStack.peek();
+
+                if( state == null || st.getState().equals(state) )
+                {
+                    m_stateStack.pop();
+
+                    if( log.isDebugEnabled() )
+                        log.debug(m_watchable.getName()+": Exiting state "+st.getState());
+                }
+                else
+                {
+                    // FIXME: should actually go and fix things for that
+                    log.error("exitState() called before enterState()");
+                }
+            }
+        }
+        catch( EmptyStackException e )
+        {
+            log.error("Stack is empty!", e);
+        }
+
+    }
+
+    private void check()
+    {
+        if( log.isDebugEnabled() ) log.debug("Checking watchdog '"+m_watchable.getName()+"'");
+
+        synchronized( m_stateStack )
+        {
+            try
+            {
+                WatchDog.State st = m_stateStack.peek();
+
+                long now = System.currentTimeMillis();
+
+                if( now > st.getExpiryTime() )
+                {
+                    log.info("Watchable '"+m_watchable.getName()+
+                             "' exceeded timeout in state '"+
+                             st.getState()+
+                             "' by "+
+                             (now-st.getExpiryTime())/1000+" seconds");
+
+                    m_watchable.timeoutExceeded( st.getState() );
+                }
+            }
+            catch( EmptyStackException e )
+            {
+                // FIXME: Do something?
+            }
+        }
+    }
+
+    /**
+     *  Strictly for debugging/informative purposes.
+     *
+     *  @return Random ramblings.
+     */
+    public String toString()
+    {
+        synchronized( m_stateStack )
+        {
+            String state = "Idle";
+
+            try
+            {
+                State st = m_stateStack.peek();
+                state = st.getState();
+            }
+            catch( EmptyStackException e ) {}
+            return "WatchDog state="+state;
+        }
+    }
+
+    /**
+     *  This is the chief watchdog thread.
+     *
+     *  @author jalkanen
+     *
+     */
+    private static class WatchDogThread extends WikiBackgroundThread
+    {
+        /** How often the watchdog thread should wake up (in seconds) */
+        private static final int CHECK_INTERVAL = 30;
+
+        public WatchDogThread( WikiEngine engine )
+        {
+            super(engine, CHECK_INTERVAL);
+            setName("WatchDog for '"+engine.getApplicationName()+"'");
+        }
+
+        public void startupTask()
+        {
+        }
+
+        public void shutdownTask()
+        {
+            WatchDog.scrub();
+        }
+
+        /**
+         *  Checks if the watchable is alive, and if it is, checks if
+         *  the stack is finished.
+         *
+         *  If the watchable has been deleted in the mean time, will
+         *  simply shut down itself.
+         */
+        public void backgroundTask() throws Exception
+        {
+            synchronized( c_kennel )
+            {
+                for( Map.Entry<Integer,WeakReference<WatchDog>> entry : c_kennel.entrySet() )
+                {
+                    WeakReference<WatchDog> wr = entry.getValue();
+
+                    WatchDog w = wr.get();
+
+                    if( w != null )
+                    {
+                        if( w.m_watchable != null && w.m_watchable.isAlive() )
+                        {
+                            w.check();
+                        }
+                        else
+                        {
+                            c_kennel.remove( entry.getKey() );
+                            break;
+                        }
+                    }
+                } // for
+            } // synchronized
+
+            WatchDog.scrub();
+        }
+    }
+
+    /**
+     *  A class which just stores the state in our State stack.
+     *
+     *  @author Janne Jalkanen
+     */
+    private static class State
+    {
+        protected String m_state;
+        protected long   m_enterTime;
+        protected long   m_expiryTime;
+
+        protected State( String state, int expiry )
+        {
+            m_state      = state;
+            m_enterTime  = System.currentTimeMillis();
+            m_expiryTime = m_enterTime + (expiry * 1000L);
+        }
+
+        protected String getState()
+        {
+            return m_state;
+        }
+
+        protected long getExpiryTime()
+        {
+            return m_expiryTime;
+        }
+    }
+
+    /**
+     *  This class wraps a Thread so that it can become Watchable.
+     *
+     *  @author Janne Jalkanen
+     *
+     */
+    private static class ThreadWrapper implements Watchable
+    {
+        private Thread m_thread;
+
+        public ThreadWrapper( Thread thread )
+        {
+            m_thread = thread;
+        }
+
+        public void timeoutExceeded( String state )
+        {
+            // TODO: Figure out something sane to do here.
+        }
+
+        public String getName()
+        {
+            return m_thread.getName();
+        }
+
+        public boolean isAlive()
+        {
+            return m_thread.isAlive();
+        }
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/Watchable.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/Watchable.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/Watchable.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/Watchable.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,55 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+/**
+ *  A watchdog needs something to watch.  If you wish to be watched,
+ *  implement this interface.
+ *
+ *  @author jalkanen
+ */
+public interface Watchable
+{
+    /**
+     *  This is a callback which is called whenever your expected
+     *  completion time is exceeded.  The current state of the
+     *  stack is available.
+     *
+     *  @param state The state in which your Watchable is currently.
+     */
+    public void timeoutExceeded( String state );
+
+    /**
+     *  Returns a human-readable name of this Watchable.  Used in
+     *  logging.
+     *
+     *  @return The name of the Watchable.
+     */
+    public String getName();
+
+    /**
+     *  Returns <code>true</code>, if this Watchable is still alive and can be
+     *  watched; otherwise <code>false</code>. For example, a stopped Thread
+     *  is not very interesting to watch.
+     *
+     *  @return the result
+     */
+    public boolean isAlive();
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WikiBackgroundThread.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WikiBackgroundThread.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WikiBackgroundThread.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/WikiBackgroundThread.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,194 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.util;
+
+import com.ecyrd.jspwiki.InternalWikiException;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.event.WikiEngineEvent;
+import com.ecyrd.jspwiki.event.WikiEvent;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+
+/**
+ * Abstract Thread subclass that operates in the background;
+ * when it detects the {@link WikiEngineEvent#SHUTDOWN} event,
+ * it terminates itself. Subclasses of this method need only
+ * implement the method {@link #backgroundTask()}, instead of
+ * the normal {@link Thread#run()}, and provide a constructor that
+ * passes the WikiEngine and sleep interval. This class is
+ * thread-safe.
+ * @author Andrew Jaquith
+ */
+public abstract class WikiBackgroundThread extends Thread implements WikiEventListener
+{
+    private volatile boolean m_killMe = false;
+    private final WikiEngine m_engine;
+    private final int m_interval;
+    private static final long POLLING_INTERVAL = 1000L;
+    
+    /**
+     * Constructs a new instance of this background thread with 
+     * a specified sleep interval, and adds the new instance to the 
+     * wiki engine's event listeners.
+     * @param engine the wiki engine
+     * @param sleepInterval the interval between invocations of
+     * the thread's {@link Thread#run()} method, in seconds
+     */
+    public WikiBackgroundThread( WikiEngine engine, int sleepInterval )
+    {
+        super();
+        m_engine = engine;
+        m_interval = sleepInterval;
+        engine.addWikiEventListener( this );
+        setDaemon( false );
+        
+    }
+    
+    /**
+     * Listens for {@link com.ecyrd.jspwiki.event.WikiEngineEvent#SHUTDOWN}
+     * and, if detected, marks the thread for death.
+     * @see com.ecyrd.jspwiki.event.WikiEventListener#actionPerformed(com.ecyrd.jspwiki.event.WikiEvent)
+     */
+    public final void actionPerformed( WikiEvent event )
+    {
+        if ( event instanceof WikiEngineEvent )
+        {
+            if ( ((WikiEngineEvent)event).getType() == WikiEngineEvent.SHUTDOWN )
+            {
+                System.out.println( "Detected wiki engine shutdown: killing " + getName() + "." );
+                m_killMe = true;
+            }
+        }
+    }
+    
+    /**
+     * Abstract method that performs the actual work for this
+     * background thread; subclasses must implement this method.
+     */
+    public abstract void backgroundTask() throws Exception;
+    
+    /**
+     * Returns the WikiEngine that created this background thread.
+     * @return the wiki engine
+     */
+    public WikiEngine getEngine()
+    {
+        return m_engine;
+    }
+    
+    /**
+     *  Requests the shutdown of this background thread.  Note that the shutdown
+     *  is not immediate.
+     *  
+     *  @since 2.4.92
+     *
+     */
+    public void shutdown()
+    {
+        m_killMe = true;
+    }
+    
+    /**
+     * Runs the background thread's {@link #backgroundTask()} method
+     * at the interval specified at construction.
+     * The thread will initially pause for a full sleep interval
+     * before starting, after which it will execute 
+     * {@link #startupTask()}. This method will cleanly 
+     * terminates the thread if the it has previously 
+     * been marked for death, before which it will execute
+     * {@link #shutdownTask()}. If any of the three methods
+     * return an exception, it will be re-thrown as a
+     * {@link com.ecyrd.jspwiki.InternalWikiException}.
+     * @see java.lang.Thread#run()
+     */
+    public final void run() 
+    {
+        try 
+        {
+            // Perform the initial startup task
+            final String name = getName();
+            System.out.println( "Starting up background thread: " + name + ".");
+            startupTask();
+            
+            // Perform the background task; check every
+            // second for thread death
+            while( !m_killMe )
+            {
+                // Perform the background task
+                // log.debug( "Running background task: " + name + "." );
+                backgroundTask();
+                
+                // Sleep for the interval we're supposed do, but
+                // wake up every second to see if thread should die
+                boolean interrupted = false;
+                try
+                {
+                    for( int i = 0; i < m_interval; i++ )
+                    {
+                        Thread.sleep( POLLING_INTERVAL );
+                        if( m_killMe )
+                        {
+                            interrupted = true;
+                            System.out.println( "Interrupted background thread: " + name + "." );
+                            break;
+                        }
+                    }
+                    if( interrupted )
+                    {
+                        break;
+                    }
+                }
+                catch( Throwable t ) 
+                {
+                    System.err.println( "Background thread error: (stack trace follows)" );
+                    t.printStackTrace();
+                }
+            }
+            
+            // Perform the shutdown task
+            shutdownTask();
+        }
+        catch( Throwable t )
+        {
+            System.err.println( "Background thread error: (stack trace follows)" );
+            t.printStackTrace();
+            throw new InternalWikiException( t.getMessage() );
+        }
+    }
+    
+    /**
+     * Executes a task after shutdown signal was detected.
+     * By default, this method does nothing; override it 
+     * to implement custom functionality.
+     */
+    public void shutdownTask() throws Exception
+    {
+    }
+    
+    /**
+     * Executes a task just after the thread's {@link Thread#run()}
+     * method starts, but before the {@link #backgroundTask()}
+     * task executes. By default, this method does nothing; 
+     * override it to implement custom functionality.
+     */
+    public void startupTask() throws Exception
+    {
+    }
+    
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/package.html?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/util/package.html Tue Feb 12 21:53:55 2008
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<title>com.ecyrd.jspwiki.util</title>
+</head>
+<body>
+
+Provides a number of utility libraries that are of general use.
+
+<h2>Package Specification</h2>
+
+The util-package provides a number of utility libraries that are of
+general use, and should not be JSPWiki-specific.
+
+<h2>Related Documentation</h2>
+
+</body>
+</html>
\ No newline at end of file

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/workflow/AbstractStep.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/workflow/AbstractStep.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/workflow/AbstractStep.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/workflow/AbstractStep.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,287 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.workflow;
+
+import java.security.Principal;
+import java.util.*;
+
+import com.ecyrd.jspwiki.WikiException;
+
+/**
+ * Abstact superclass that provides a complete implementation of most
+ * Step methods; subclasses need only implement {@link #execute()} and
+ * {@link #getActor()}.
+ *
+ * @author Andrew Jaquith
+ * @since 2.5
+ */
+public abstract class AbstractStep implements Step
+{
+
+    /** Timestamp of when the step started. */
+    private Date m_start;
+
+    /** Timestamp of when the step ended. */
+    private Date m_end;
+
+    private final String m_key;
+
+    private boolean m_completed;
+
+    private final Map<Outcome,Step> m_successors;
+
+    private Workflow m_workflow;
+
+    private Outcome m_outcome;
+
+    private final List<String> m_errors;
+
+    private boolean m_started;
+
+    /**
+     * Protected constructor that creates a new Step with a specified message key.
+     * After construction, the protected method {@link #setWorkflow(Workflow)} should be
+     * called.
+     *
+     * @param messageKey
+     *            the Step's message key, such as
+     *            <code>decision.editPageApproval</code>. By convention, the
+     *            message prefix should be a lower-case version of the Step's
+     *            type, plus a period (<em>e.g.</em>, <code>task.</code>
+     *            and <code>decision.</code>).
+     */
+    protected AbstractStep( String messageKey )
+    {
+        m_started = false;
+        m_start = Workflow.TIME_NOT_SET;
+        m_completed = false;
+        m_end = Workflow.TIME_NOT_SET;
+        m_errors = new ArrayList<String>();
+        m_outcome = Outcome.STEP_CONTINUE;
+        m_key = messageKey;
+        m_successors = new LinkedHashMap<Outcome,Step>();
+    }
+
+    /**
+     * Constructs a new Step belonging to a specified Workflow and having a
+     * specified message key.
+     *
+     * @param workflow
+     *            the workflow the Step belongs to
+     * @param messageKey
+     *            the Step's message key, such as
+     *            <code>decision.editPageApproval</code>. By convention, the
+     *            message prefix should be a lower-case version of the Step's
+     *            type, plus a period (<em>e.g.</em>, <code>task.</code>
+     *            and <code>decision.</code>).
+     */
+    public AbstractStep( Workflow workflow, String messageKey )
+    {
+        this( messageKey );
+        setWorkflow( workflow );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void addSuccessor(Outcome outcome, Step step)
+    {
+        m_successors.put( outcome, step );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final Collection<Outcome> getAvailableOutcomes()
+    {
+        return Collections.unmodifiableCollection( m_successors.keySet() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final List<String> getErrors()
+    {
+        return Collections.unmodifiableList( m_errors );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract Outcome execute() throws WikiException;
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract Principal getActor();
+
+    /**
+     * {@inheritDoc}
+     */
+    public final Date getEndTime()
+    {
+        return m_end;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final Object[] getMessageArguments()
+    {
+        if ( m_workflow == null )
+        {
+            return new Object[0];
+        }
+        return m_workflow.getMessageArguments();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final String getMessageKey()
+    {
+        return m_key;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final synchronized Outcome getOutcome()
+    {
+        return m_outcome;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Principal getOwner()
+    {
+        if ( m_workflow == null )
+        {
+            return null;
+        }
+        return m_workflow.getOwner();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final Date getStartTime()
+    {
+        return m_start;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final synchronized Workflow getWorkflow()
+    {
+        return m_workflow;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final boolean isCompleted()
+    {
+        return m_completed;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final boolean isStarted()
+    {
+        return m_started;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final synchronized void setOutcome(Outcome outcome)
+    {
+        // Is this an allowed Outcome?
+        if ( !m_successors.containsKey( outcome ) )
+        {
+            if ( !Outcome.STEP_CONTINUE.equals( outcome ) &&
+                 !Outcome.STEP_ABORT.equals( outcome ) )
+            {
+                 throw new IllegalArgumentException( "Outcome " + outcome.getMessageKey() + " is not supported for this Step." );
+            }
+        }
+
+        // Is this a "completion" outcome?
+        if ( outcome.isCompletion() )
+        {
+            if ( m_completed )
+            {
+                throw new IllegalStateException( "Step has already been marked complete; cannot set again." );
+            }
+            m_completed = true;
+            m_end = new Date( System.currentTimeMillis() );
+        }
+        m_outcome = outcome;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final synchronized void start() throws WikiException
+    {
+        if ( m_started )
+        {
+            throw new IllegalStateException( "Step already started." );
+        }
+        m_started = true;
+        m_start = new Date( System.currentTimeMillis() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final Step getSuccessor( Outcome outcome )
+    {
+        return m_successors.get( outcome );
+    }
+
+    // --------------------------Helper methods--------------------------
+
+    /**
+     * Protected method that sets the parent Workflow post-construction.
+     * @param workflow the parent workflow to set
+     */
+    protected final synchronized void setWorkflow( Workflow workflow )
+    {
+        m_workflow = workflow;
+    }
+
+    /**
+     * Protected helper method that adds a String representing an error message
+     * to the Step's cached errors list.
+     *
+     * @param message
+     *            the error message
+     */
+    protected final synchronized void addError( String message )
+    {
+        m_errors.add( message );
+    }
+
+}