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 [17/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/auth/user/JDBCUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,749 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 JSPWiki Development Group
+
+    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.auth.user;
+
+import java.security.Principal;
+import java.sql.*;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.WikiPrincipal;
+import com.ecyrd.jspwiki.auth.WikiSecurityException;
+
+/**
+ * <p>Implementation of UserDatabase that persists {@link DefaultUserProfile}
+ * objects to a JDBC DataSource, as might typically be provided by a web
+ * container. This implementation looks up the JDBC DataSource using JNDI.
+ * The JNDI name of the datasource, backing table and mapped columns used
+ * by this class are configured via settings in <code>jspwiki.properties</code>.</p>
+ * <p>Configurable properties are these:</p>
+ * <table>
+ *   <tr>
+ *   <thead>
+ *     <th>Property</th>
+ *     <th>Default</th>
+ *     <th>Definition</th>
+ *   <thead>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.datasource</code></td>
+ *     <td><code>jdbc/UserDatabase</code></td>
+ *     <td>The JNDI name of the DataSource</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.table</code></td>
+ *     <td><code>users</code></td>
+ *     <td>The table that stores the user profiles</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.created</code></td>
+ *     <td><code>created</code></td>
+ *     <td>The column containing the profile's creation timestamp</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.email</code></td>
+ *     <td><code>email</code></td>
+ *     <td>The column containing the user's e-mail address</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.fullName</code></td>
+ *     <td><code>full_name</code></td>
+ *     <td>The column containing the user's full name</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.loginName</code></td>
+ *     <td><code>login_name</code></td>
+ *     <td>The column containing the user's login id</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.password</code></td>
+ *     <td><code>password</code></td>
+ *     <td>The column containing the user's password</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.modified</code></td>
+ *     <td><code>modified</code></td>
+ *     <td>The column containing the profile's last-modified timestamp</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.wikiName</code></td>
+ *     <td><code>wiki_name</code></td>
+ *     <td>The column containing the user's wiki name</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.roleTable</code></td>
+ *     <td><code>roles</code></td>
+ *     <td>The table that stores user roles. When a new user is created,
+ *       a new record is inserted containing user's initial role. The
+ *       table will have an ID column whose name and values correspond
+ *       to the contents of the user table's login name column. It will
+ *       also contain a role column (see next row).</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.role</code></td>
+ *     <td><code>role</code></td>
+ *     <td>The column in the role table that stores user roles. When a new user
+ *       is created, this column will be populated with the value
+ *       <code>Authenticated</code>. Once created, JDBCUserDatabase does not
+ *       use this column again; it is provided strictly for the convenience
+ *       of container-managed authentication services.</td>
+ *   </tr>
+ *   <tr>
+ *     <td><code>jspwiki.userdatabase.hashPrefix</code></td>
+ *     <td><code>true</code></td>
+ *     <td>Whether or not to prepend a prefix for the hash algorithm, <em>e.g.</em>,
+ *         <code>{SHA}</code>.</td>
+ *   </tr>
+ * </table>
+ * <p>This class hashes passwords using SHA-1. All of the underying SQL commands used by this class are implemented using
+ * prepared statements, so it is immune to SQL injection attacks.</p>
+ * <p>This class is typically used in conjunction with a web container's JNDI resource
+ * factory. For example, Tomcat versions 4 and higher provide a basic JNDI factory
+ * for registering DataSources. To give JSPWiki access to the JNDI resource named
+ * by <code></code>, you would declare the datasource resource similar to this:</p>
+ * <blockquote><code>&lt;Context ...&gt;<br/>
+ *  &nbsp;&nbsp;...<br/>
+ *  &nbsp;&nbsp;&lt;Resource name="jdbc/UserDatabase" auth="Container"<br/>
+ *  &nbsp;&nbsp;&nbsp;&nbsp;type="javax.sql.DataSource" username="dbusername" password="dbpassword"<br/>
+ *  &nbsp;&nbsp;&nbsp;&nbsp;driverClassName="org.hsql.jdbcDriver" url="jdbc:HypersonicSQL:database"<br/>
+ *  &nbsp;&nbsp;&nbsp;&nbsp;maxActive="8" maxIdle="4"/&gt;<br/>
+ *  &nbsp;...<br/>
+ * &lt;/Context&gt;</code></blockquote>
+ * <p>JDBC driver JARs should be added to Tomcat's <code>common/lib</code> directory.
+ * For more Tomcat 5.5 JNDI configuration examples,
+ * see <a href="http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html">
+ * http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html</a>.</p>
+ * <p>JDBCUserDatabase commits changes as transactions if the back-end database supports them.
+ * If the database supports transactions, user profile changes are saved
+ * to permanent storage only when the {@link #commit()} method is called. If the database does <em>not</em>
+ * support transactions, then changes are made immediately (during the {@link #save(UserProfile)}
+ * method), and the {@linkplain #commit()} method no-ops. Thus, callers should always call the
+ * {@linkplain #commit()} method after saving a profile to guarantee that changes are applied.</p>
+ * @author Andrew R. Jaquith
+ * @since 2.3
+ */public class JDBCUserDatabase extends AbstractUserDatabase
+{
+
+    private static final String NOTHING = "";
+
+    public static final String DEFAULT_DB_CREATED    = "created";
+
+    public static final String DEFAULT_DB_EMAIL      = "email";
+
+    public static final String DEFAULT_DB_FULL_NAME  = "full_name";
+
+    public static final String DEFAULT_DB_HASH_PREFIX = "true";
+
+    public static final String DEFAULT_DB_JNDI_NAME  = "jdbc/UserDatabase";
+
+    public static final String DEFAULT_DB_MODIFIED   = "modified";
+
+    public static final String DEFAULT_DB_ROLE       = "role";
+
+    public static final String DEFAULT_DB_ROLE_TABLE = "roles";
+
+    public static final String DEFAULT_DB_TABLE      = "users";
+
+    public static final String DEFAULT_DB_LOGIN_NAME = "login_name";
+
+    public static final String DEFAULT_DB_PASSWORD   = "password";
+
+    public static final String DEFAULT_DB_WIKI_NAME  = "wiki_name";
+
+    public static final String PROP_DB_CREATED       = "jspwiki.userdatabase.created";
+
+    public static final String PROP_DB_EMAIL         = "jspwiki.userdatabase.email";
+
+    public static final String PROP_DB_FULL_NAME     = "jspwiki.userdatabase.fullName";
+
+    public static final String PROP_DB_DATASOURCE    = "jspwiki.userdatabase.datasource";
+
+    public static final String PROP_DB_HASH_PREFIX   = "jspwiki.userdatabase.hashPrefix";
+
+    public static final String PROP_DB_LOGIN_NAME    = "jspwiki.userdatabase.loginName";
+
+    public static final String PROP_DB_MODIFIED      = "jspwiki.userdatabase.modified";
+
+    public static final String PROP_DB_PASSWORD      = "jspwiki.userdatabase.password";
+
+    public static final String PROP_DB_ROLE          = "jspwiki.userdatabase.role";
+
+    public static final String PROP_DB_ROLE_TABLE    = "jspwiki.userdatabase.roleTable";
+
+    public static final String PROP_DB_TABLE         = "jspwiki.userdatabase.table";
+
+    public static final String PROP_DB_WIKI_NAME     = "jspwiki.userdatabase.wikiName";
+
+    private DataSource         m_ds                  = null;
+    private String m_deleteUserByLoginName = null;
+    private String m_deleteRoleByLoginName = null;
+    private String m_findByEmail = null;
+    private String m_findByFullName = null;
+    private String m_findByLoginName = null;
+    private String m_findByWikiName = null;
+    private String m_renameProfile = null;
+    private String m_renameRoles = null;
+    private String m_updateProfile = null;
+    private String m_findAll = null;
+    private String m_findRoles = null;
+    private String m_initialRole = "Authenticated";
+    private String m_insertProfile = null;
+    private String m_insertRole = null;
+    private String m_userTable = null;
+    private String m_email = null;
+    private String m_fullName = null;
+    private boolean m_hashPrefix = true;
+    private String m_loginName = null;
+    private String m_password = null;
+    private String m_role = null;
+    private String m_roleTable = null;
+    private String m_wikiName = null;
+    private String m_created = null;
+    private String m_modified = null;
+    private boolean m_sharedWithContainer = false;
+    private boolean m_supportsCommits = false;
+
+    /**
+     * Looks up and deletes the first {@link UserProfile} in the user database
+     * that matches a profile having a given login name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}. This method is intended to be atomic;
+     * results cannot be partially committed. If the commit fails, it should
+     * roll back its state appropriately. Implementing classes that persist
+     * to the file system may wish to make this method <code>synchronized</code>.
+     * @param loginName the login name of the user profile that shall be deleted
+     */
+    public void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException
+    {
+        // Get the existing user; if not found, throws NoSuchPrincipalException
+        findByLoginName( loginName );
+        Connection conn = null;
+        
+        try
+        {
+            // Open the database connection
+            conn = m_ds.getConnection();
+            if ( m_supportsCommits )
+            {
+                conn.setAutoCommit( false );
+            }
+
+            PreparedStatement ps;
+            // Delete user record
+            ps = conn.prepareStatement( m_deleteUserByLoginName );
+            ps.setString(1, loginName );
+            ps.execute();
+            ps.close();
+
+            // Delete role record
+            ps = conn.prepareStatement( m_deleteRoleByLoginName );
+            ps.setString(1, loginName );
+            ps.execute();
+            ps.close();
+
+            // Commit and close connection
+            if ( m_supportsCommits )
+            {
+                conn.commit();
+            }
+        }
+        catch ( SQLException e )
+        {
+            throw new WikiSecurityException( e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByEmail(java.lang.String)
+     */
+    public UserProfile findByEmail( String index ) throws NoSuchPrincipalException
+    {
+        return findByPreparedStatement( m_findByEmail, index );
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByFullName(java.lang.String)
+     */
+    public UserProfile findByFullName( String index ) throws NoSuchPrincipalException
+    {
+        return findByPreparedStatement( m_findByFullName, index );
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
+     */
+    public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException
+    {
+        return findByPreparedStatement( m_findByLoginName, index );
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(String)
+     */
+    public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException
+    {
+        return findByPreparedStatement( m_findByWikiName, index );
+    }
+
+    /**
+     * Returns all WikiNames that are stored in the UserDatabase
+     * as an array of WikiPrincipal objects. If the database does not
+     * contain any profiles, this method will return a zero-length
+     * array.
+     * @return the WikiNames
+     */
+    public Principal[] getWikiNames() throws WikiSecurityException
+    {
+        Set principals = new HashSet();
+        Connection conn = null;
+        try
+        {
+            conn = m_ds.getConnection();
+            PreparedStatement ps = conn.prepareStatement( m_findAll );
+            ResultSet rs = ps.executeQuery();
+            while ( rs.next() )
+            {
+                String wikiName = rs.getString( m_wikiName );
+                if ( wikiName == null )
+                {
+                    log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." );
+                }
+                else
+                {
+                    Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
+                    principals.add( principal );
+                }
+            }
+            ps.close();
+        }
+        catch ( SQLException e )
+        {
+            throw new WikiSecurityException( e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+
+        return (Principal[])principals.toArray( new Principal[principals.size()] );
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#initialize(com.ecyrd.jspwiki.WikiEngine,
+     * java.util.Properties)
+     */
+    public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
+    {
+        String jndiName = props.getProperty( PROP_DB_DATASOURCE, DEFAULT_DB_JNDI_NAME );
+        try
+        {
+            Context initCtx = new InitialContext();
+            Context ctx = (Context) initCtx.lookup("java:comp/env");
+            m_ds = (DataSource) ctx.lookup( jndiName );
+
+            // Prepare the SQL selectors
+            m_userTable = props.getProperty( PROP_DB_TABLE, DEFAULT_DB_TABLE );
+            m_email     = props.getProperty( PROP_DB_EMAIL, DEFAULT_DB_EMAIL );
+            m_fullName  = props.getProperty( PROP_DB_FULL_NAME, DEFAULT_DB_FULL_NAME );
+            m_hashPrefix = Boolean.valueOf( props.getProperty( PROP_DB_HASH_PREFIX, DEFAULT_DB_HASH_PREFIX ) ).booleanValue();
+            m_loginName = props.getProperty( PROP_DB_LOGIN_NAME, DEFAULT_DB_LOGIN_NAME );
+            m_password  = props.getProperty( PROP_DB_PASSWORD, DEFAULT_DB_PASSWORD );
+            m_wikiName  = props.getProperty( PROP_DB_WIKI_NAME, DEFAULT_DB_WIKI_NAME );
+            m_created   = props.getProperty( PROP_DB_CREATED, DEFAULT_DB_CREATED );
+            m_modified  = props.getProperty( PROP_DB_MODIFIED, DEFAULT_DB_MODIFIED );
+
+            m_findAll         = "SELECT * FROM " + m_userTable;
+            m_findByEmail     = "SELECT * FROM " + m_userTable + " WHERE " + m_email + "=?";
+            m_findByFullName  = "SELECT * FROM " + m_userTable + " WHERE " + m_fullName + "=?";
+            m_findByLoginName = "SELECT * FROM " + m_userTable + " WHERE " + m_loginName + "=?";
+            m_findByWikiName  = "SELECT * FROM " + m_userTable + " WHERE " + m_wikiName + "=?";
+
+            // Prepare the user isert/update SQL
+            m_insertProfile   = "INSERT INTO " + m_userTable + " ("
+                              + m_email + ","
+                              + m_fullName + ","
+                              + m_password + ","
+                              + m_wikiName + ","
+                              + m_modified + ","
+                              + m_loginName + ","
+                              + m_created
+                              + ") VALUES (?,?,?,?,?,?,?)";
+            m_updateProfile   = "UPDATE " + m_userTable + " SET "
+                              + m_email + "=?,"
+                              + m_fullName + "=?,"
+                              + m_password + "=?,"
+                              + m_wikiName + "=?,"
+                              + m_modified + "=? WHERE " + m_loginName + "=?";
+
+            // Prepare the role insert SQL
+            m_roleTable = props.getProperty( PROP_DB_ROLE_TABLE, DEFAULT_DB_ROLE_TABLE );
+            m_role = props.getProperty( PROP_DB_ROLE, DEFAULT_DB_ROLE );
+            m_insertRole      = "INSERT INTO " + m_roleTable + " ("
+                              + m_loginName + ","
+                              + m_role
+                              + ") VALUES (?,?)";
+            m_findRoles       = "SELECT * FROM " + m_roleTable + " WHERE " + m_loginName + "=?";
+
+            // Prepare the user delete SQL
+            m_deleteUserByLoginName = "DELETE FROM " + m_userTable + " WHERE " + m_loginName + "=?";
+
+            // Prepare the role delete SQL
+            m_deleteRoleByLoginName = "DELETE FROM " + m_roleTable + " WHERE " + m_loginName + "=?";
+
+            // Prepare the rename user/roles SQL
+            m_renameProfile   = "UPDATE " + m_userTable + " SET "
+                              + m_loginName + "=?,"
+                              + m_modified  + "=? WHERE " + m_loginName + "=?";
+            m_renameRoles     = "UPDATE " + m_roleTable + " SET "
+                              + m_loginName + "=? WHERE " + m_loginName + "=?";
+
+            // Set the "share users with container flag"
+            m_sharedWithContainer = TextUtil.isPositive( props.getProperty( PROP_SHARED_WITH_CONTAINER, "false" ) );
+        }
+        catch( NamingException e )
+        {
+            log.error( "JDBCUserDatabase initialization error: " + e.getMessage() );
+            throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
+        }
+
+        // Test connection by doing a quickie select
+        Connection conn = null;
+        try
+        {
+            conn = m_ds.getConnection();
+            PreparedStatement ps = conn.prepareStatement( m_findAll );
+            ps.executeQuery();
+            ps.close();
+        }
+        catch ( SQLException e )
+        {
+            log.error( "JDBCUserDatabase initialization error: " + e.getMessage() );
+            throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+        log.info( "JDBCUserDatabase initialized from JNDI DataSource: " + jndiName );
+
+        // Determine if the datasource supports commits
+        try
+        {
+            conn = m_ds.getConnection();
+            DatabaseMetaData dmd = conn.getMetaData();
+            if ( dmd.supportsTransactions() )
+            {
+                m_supportsCommits = true;
+                conn.setAutoCommit( false );
+                log.info("JDBCUserDatabase supports transactions. Good; we will use them." );
+            }
+        }
+        catch ( SQLException e )
+        {
+            log.warn("JDBCUserDatabase warning: user database doesn't seem to support transactions. Reason: " + e.getMessage() );
+            throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+    }
+
+    /**
+     * Determines whether the user database shares user/password data with the
+     * web container; returns <code>true</code> if the JSPWiki property
+     * <code>jspwiki.userdatabase.isSharedWithContainer</code> is <code>true</code>.
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#isSharedWithContainer()
+     */
+    public boolean isSharedWithContainer()
+    {
+        return m_sharedWithContainer;
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#rename(String, String)
+     */
+    public void rename(String loginName, String newName)
+        throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException
+    {
+        // Get the existing user; if not found, throws NoSuchPrincipalException
+        UserProfile profile = findByLoginName( loginName );
+
+        // Get user with the proposed name; if found, it's a collision
+        try
+        {
+            UserProfile otherProfile = findByLoginName( newName );
+            if ( otherProfile != null )
+            {
+                throw new DuplicateUserException( "Cannot rename: the login name '" + newName + "' is already taken." );
+            }
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            // Good! That means it's safe to save using the new name
+        }
+
+        Connection conn = null;
+        try
+        {
+            // Open the database connection
+            conn = m_ds.getConnection();
+            if ( m_supportsCommits )
+            {
+                conn.setAutoCommit( false );
+            }
+
+            Timestamp ts = new Timestamp( System.currentTimeMillis() );
+            Date modDate = new Date( ts.getTime() );
+
+            // Change the login ID for the user record
+            PreparedStatement ps = conn.prepareStatement( m_renameProfile );
+            ps.setString( 1, newName );
+            ps.setTimestamp( 2, ts );
+            ps.setString( 3, loginName );
+            ps.execute();
+            ps.close();
+
+            // Change the login ID for the role records
+            ps = conn.prepareStatement( m_renameRoles );
+            ps.setString( 1, newName );
+            ps.setString( 2, loginName );
+            ps.execute();
+            ps.close();
+
+            // Set the profile name and mod time
+            profile.setLoginName( newName );
+            profile.setLastModified( modDate );
+
+            // Commit and close connection
+            if ( m_supportsCommits )
+            {
+                conn.commit();
+            }
+        }
+        catch ( SQLException e )
+        {
+            throw new WikiSecurityException( e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#save(com.ecyrd.jspwiki.auth.user.UserProfile)
+     */
+    public void save( UserProfile profile ) throws WikiSecurityException
+    {
+        // Figure out which prepared statement to use & execute it
+        String loginName = profile.getLoginName();
+        PreparedStatement ps = null;
+        UserProfile existingProfile = null;
+        try
+        {
+            existingProfile = findByLoginName( loginName );
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            // Existing profile will be null
+        }
+
+        // Get a clean password from the passed profile.
+        // Blank password is the same as null, which means we re-use the existing one.
+        String password = profile.getPassword();
+        String existingPassword = ( existingProfile == null ) ? null : existingProfile.getPassword();
+        if ( NOTHING.equals( password ) )
+        {
+            password = null;
+        }
+        if ( password == null)
+        {
+            password = existingPassword;
+        }
+
+        // If password changed, hash it before we save
+        if ( !password.equals( existingPassword ) )
+        {
+            password =  m_hashPrefix ? SHA_PREFIX + getHash( password ) : getHash( password );
+        }
+
+        Connection conn = null;
+        try
+        {
+            // Open the database connection
+            conn = m_ds.getConnection();
+            if ( m_supportsCommits )
+            {
+                conn.setAutoCommit( false );
+            }
+
+            Timestamp ts = new Timestamp( System.currentTimeMillis() );
+            Date modDate = new Date( ts.getTime() );
+            if ( existingProfile == null )
+            {
+                // User is new: insert new user record
+                ps = conn.prepareStatement( m_insertProfile );
+                ps.setString(1, profile.getEmail() );
+                ps.setString(2, profile.getFullname() );
+                ps.setString(3, password );
+                ps.setString(4, profile.getWikiName() );
+                ps.setTimestamp(5, ts );
+                ps.setString(6, profile.getLoginName() );
+                ps.setTimestamp(7, ts );
+                ps.execute();
+                ps.close();
+
+                // Insert role record if no roles yet
+                if ( m_sharedWithContainer )
+                {
+                    ps = conn.prepareStatement( m_findRoles );
+                    ps.setString( 1, profile.getLoginName() );
+                    ResultSet rs = ps.executeQuery();
+                    int roles = 0;
+                    while ( rs.next() )
+                    {
+                        roles++;
+                    }
+                    ps.close();
+                    if ( roles == 0 )
+                    {
+                        ps = conn.prepareStatement( m_insertRole );
+                        ps.setString( 1, profile.getLoginName() );
+                        ps.setString( 2, m_initialRole );
+                        ps.execute();
+                        ps.close();
+                    }
+                }
+
+                // Set the profile creation time
+                profile.setCreated( modDate );
+            }
+            else
+            {
+                // User exists: modify existing record
+                ps = conn.prepareStatement( m_updateProfile );
+                ps.setString(1, profile.getEmail() );
+                ps.setString(2, profile.getFullname() );
+                ps.setString(3, password );
+                ps.setString(4, profile.getWikiName() );
+                ps.setTimestamp(5, ts );
+                ps.setString(6, profile.getLoginName() );
+                ps.execute();
+                ps.close();
+            }
+            // Set the profile mod time
+            profile.setLastModified( modDate );
+
+            // Commit and close connection
+            if ( m_supportsCommits )
+            {
+                conn.commit();
+            }
+        }
+        catch ( SQLException e )
+        {
+            throw new WikiSecurityException( e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+    }
+
+    /**
+     *
+     * @param rs
+     * @return
+     * @throws SQLException
+     */
+    private UserProfile findByPreparedStatement( String sql, String index ) throws NoSuchPrincipalException
+    {
+        UserProfile profile = null;
+        boolean found = false;
+        boolean unique = true;
+        Connection conn = null;
+        try
+        {
+            // Open the database connection
+            conn = m_ds.getConnection();
+            if ( m_supportsCommits )
+            {
+                conn.setAutoCommit( false );
+            }
+
+            PreparedStatement ps = conn.prepareStatement( sql );
+            ps.setString( 1, index );
+            ResultSet rs = ps.executeQuery();
+            while ( rs.next() )
+            {
+                if ( profile != null )
+                {
+                    unique = false;
+                    break;
+                }
+                profile = new DefaultUserProfile();
+                profile.setCreated( rs.getTimestamp( m_created ) );
+                profile.setEmail( rs.getString( m_email ) );
+                profile.setFullname( rs.getString( m_fullName) );
+                profile.setLastModified( rs.getTimestamp( m_modified ) );
+                profile.setLoginName( rs.getString( m_loginName ) ) ;
+                profile.setPassword( rs.getString( m_password ) );
+                found = true;
+            }
+            ps.close();
+        }
+        catch ( SQLException e )
+        {
+            throw new NoSuchPrincipalException( e.getMessage() );
+        }
+        finally
+        {
+            try { conn.close(); } catch (Exception e) {}
+        }
+
+        if ( !found )
+        {
+            throw new NoSuchPrincipalException("Could not find profile in database!");
+        }
+        if ( !unique )
+        {
+            throw new NoSuchPrincipalException("More than one profile in database!");
+        }
+        return profile;
+
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,227 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-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.auth.user;
+
+import java.security.Principal;
+import java.util.Properties;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.WikiSecurityException;
+
+/**
+ * Defines an interface for loading, persisting and storing users.
+ * @author Janne Jalkanen
+ * @author Andrew Jaquith
+ * @since 2.3
+ */
+public interface UserDatabase
+{
+
+    /**
+     * No-op method that in previous versions of JSPWiki was intended to
+     * atomically commit changes to the user database. Now, the {@link #rename(String, String)},
+     * {@link #save(UserProfile)} and {@link #deleteByLoginName(String)} methods
+     * are atomic themselves.
+     * @throws WikiSecurityException
+     * @deprecated there is no need to call this method because the save, rename and
+     * delete methods contain their own commit logic
+     */
+    public void commit() throws WikiSecurityException;
+
+    /**
+     * Looks up and deletes the first {@link UserProfile} in the user database
+     * that matches a profile having a given login name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}. This method is intended to be atomic;
+     * results cannot be partially committed. If the commit fails, it should
+     * roll back its state appropriately. Implementing classes that persist
+     * to the file system may wish to make this method <code>synchronized</code>.
+     * @param loginName the login name of the user profile that shall be deleted
+     */
+    public void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException;
+
+    /**
+     * <p>
+     * Looks up the Principals representing a user from the user database. These
+     * are defined as a set of Principals manufactured from the login name, full
+     * name, and wiki name. The order of the Principals returned is not
+     * significant. If the user database does not contain a user with the
+     * supplied identifier, throws a {@link NoSuchPrincipalException}.
+     * </p>
+     * <p>
+     * Note that if an implememtation wishes to mark one of the returned
+     * Principals as representing the user's common name, it should instantiate
+     * this Principal using
+     * {@link com.ecyrd.jspwiki.auth.WikiPrincipal#WikiPrincipal(String, String)}
+     * with the <code>type</code> parameter set to
+     * {@link com.ecyrd.jspwiki.auth.WikiPrincipal#WIKI_NAME}. The method
+     * {@link com.ecyrd.jspwiki.WikiSession#getUserPrincipal()} will return this
+     * principal as the "primary" principal. Note that this method can also be
+     * used to mark a WikiPrincipal as a login name or a wiki name.
+     * </p>
+     * @param identifier the name of the user to retrieve; this corresponds to
+     *            value returned by the user profile's
+     *            {@link UserProfile#getLoginName()} method.
+     * @return the array of Principals representing the user's identities
+     */
+    public Principal[] getPrincipals( String identifier ) throws NoSuchPrincipalException;
+
+    /**
+     * Returns all WikiNames that are stored in the UserDatabase
+     * as an array of Principal objects. If the database does not
+     * contain any profiles, this method will return a zero-length
+     * array.
+     * @return the WikiNames
+     */
+    public Principal[] getWikiNames() throws WikiSecurityException;
+
+    /**
+     * Looks up and returns the first {@link UserProfile} in the user database
+     * that whose login name, full name, or wiki name matches the supplied
+     * string. This method provides a "forgiving" search algorithm for resolving
+     * Principal names when the exact profile attribute that supplied the name
+     * is unknown.
+     * @param index the login name, full name, or wiki name
+     */
+    public UserProfile find( String index ) throws NoSuchPrincipalException;
+
+    /**
+     * Looks up and returns the first {@link UserProfile} in the user database
+     * that matches a profile having a given e-mail address. If the user
+     * database does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the e-mail address of the desired user profile
+     * @return the user profile
+     */
+    public UserProfile findByEmail( String index ) throws NoSuchPrincipalException;
+
+    /**
+     * Looks up and returns the first {@link UserProfile} in the user database
+     * that matches a profile having a given login name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the login name of the desired user profile
+     * @return the user profile
+     */
+    public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException;
+
+    /**
+     * Looks up and returns the first {@link UserProfile} in the user database
+     * that matches a profile having a given wiki name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the wiki name of the desired user profile
+     * @return the user profile
+     */
+    public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException;
+
+    /**
+     * Looks up and returns the first {@link UserProfile} in the user database
+     * that matches a profile having a given full name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the fill name of the desired user profile
+     * @return the user profile
+     */
+    public UserProfile findByFullName( String index ) throws NoSuchPrincipalException;
+
+    /**
+     * Initializes the user database based on values from a Properties object.
+     */
+    public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException;
+
+    /**
+     * Returns <code>true</code> if this user database shares user/password data with the
+     * web container; <code>false</false> otherwise.
+     * @return the result
+     */
+    public boolean isSharedWithContainer();
+
+    /**
+     * Factory method that instantiates a new user profile.
+     * The {@link UserProfile#isNew()} method of profiles created using
+     * this method should return <code>true</code>.
+     */
+    public UserProfile newProfile();
+
+    /**
+     * <p>Renames a {@link UserProfile} in the user database by changing
+     * the profile's login name. Because the login name is the profile's unique
+     * identifier, implementations should verify that the identifier is
+     * "safe" to change before actually changing it. Specifically: the profile
+     * with the supplied login name must already exist, and the proposed new
+     * name must not be in use by another profile.</p>
+     * <p>This method is intended to be atomic; results cannot be partially committed.
+     * If the commit fails, it should roll back its state appropriately.
+     * Implementing classes that persist to the file system may wish to make
+     * this method <code>synchronized</code>.</p>
+     * @param loginName the existing login name for the profile
+     * @param newName the proposed new login name
+     * @throws NoSuchPrincipalException if the user profile identified by
+     * <code>loginName</code> does not exist
+     * @throws DuplicateUserException if another user profile with the
+     * proposed new login name already exists
+     * @throws WikiSecurityException if the profile cannot be renamed for
+     * any reason, such as an I/O error, database connection failure
+     * or lack of support for renames.
+     */
+    public void rename( String loginName, String newName ) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException;
+
+    /**
+     * <p>
+     * Saves a {@link UserProfile}to the user database, overwriting the
+     * existing profile if it exists. The user name under which the profile
+     * should be saved is returned by the supplied profile's
+     * {@link UserProfile#getLoginName()} method.
+     * </p>
+     * <p>
+     * The database implementation is responsible for detecting potential
+     * duplicate user profiles; specifically, the login name, wiki name, and
+     * full name must be unique. The implementation is not required to check for
+     * validity of passwords or e-mail addresses. Special case: if the profile
+     * already exists and the password is null, it should retain its previous
+     * value, rather than being set to null.
+     * </p>
+     * <p>Implementations are <em>required</em> to time-stamp the creation
+     * or modification fields of the UserProfile./p>
+     * <p>This method is intended to be atomic; results cannot be partially committed.
+     * If the commit fails, it should roll back its state appropriately.
+     * Implementing classes that persist to the file system may wish to make
+     * this method <code>synchronized</code>.</p>
+     * @param profile the user profile to save
+     * @throws WikiSecurityException if the profile cannot be saved
+     */
+    public void save( UserProfile profile ) throws WikiSecurityException;
+
+    /**
+     * Determines whether a supplied user password is valid, given a login name
+     * and password. It is up to the implementing class to determine how the
+     * comparison should be made. For example, the password might be hashed
+     * before comparing it to the value persisted in the back-end data store.
+     * @param loginName the login name
+     * @param password the password
+     * @return <code>true</code> if the password is valid, <code>false</code>
+     *         otherwise
+     */
+    public boolean validatePassword( String loginName, String password );
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,151 @@
+/* 
+    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.auth.user;
+
+import java.util.Date;
+
+/**
+ * Class for representing wiki user information, such as the login name, full
+ * name, wiki name, and e-mail address. Note that since 2.6 the wiki name is
+ * required to be automatically computed from the full name.
+ * @author Andrew Jaquith
+ * @since 2.3
+ */
+public interface UserProfile
+{
+
+    /**
+     * Returns the creation date.
+     * @return the creation date
+     */
+    public Date getCreated();
+
+    /**
+     * Returns the user's e-mail address.
+     * @return the e-mail address
+     */
+    public String getEmail();
+
+    /**
+     * Returns the user's full name.
+     * @return the full name
+     */
+    public String getFullname();
+
+    /**
+     * Returns the last-modified date.
+     * @return the date and time of last modification
+     */
+    public Date getLastModified();
+
+    /**
+     * Returns the user's login name.
+     * @return the login name
+     */
+    public String getLoginName();
+
+    /**
+     * Returns the user password for use with custom authentication. Note that
+     * the password field is not meaningful for container authentication; the
+     * user's private credentials are generally stored elsewhere. While it
+     * depends on the {@link UserDatabase}implementation, in most cases the
+     * value returned by this method will be a password hash, not the password
+     * itself.
+     * @return the password
+     */
+    public String getPassword();
+
+    /**
+     * Returns the user's wiki name, based on the full name with all
+     * whitespace removed.
+     * @return the wiki name.
+     */
+    public String getWikiName();
+
+    /**
+     * Returns <code>true</code> if the profile has never been
+     * saved before. Implementing classes might check the
+     * last modified date, for example, to determine this.
+     * @return whether the profile is new
+     */
+    public boolean isNew();
+
+    /**
+     * Sets the created date.
+     * @param date the creation date
+     */
+    public void setCreated( Date date );
+
+    /**
+     * Sets the user's e-mail address.
+     * @param email the e-mail address
+     */
+    public void setEmail( String email );
+
+    /**
+     * Sets the user's full name. For example, "Janne Jalkanen."
+     * @param arg the full name
+     */
+    public void setFullname( String arg );
+
+    /**
+     * Sets the last-modified date
+     * @param date the last-modified date
+     */
+    public void setLastModified( Date date );
+
+    /**
+     * Sets the name by which the user logs in. The login name is used as the
+     * username for custom authentication (see
+     * {@link com.ecyrd.jspwiki.auth.AuthenticationManager#login(WikiSession, String, String)},
+     * {@link com.ecyrd.jspwiki.auth.login.UserDatabaseLoginModule}). The login
+     * name is typically a short name ("jannej"). In contrast, the wiki name is
+     * typically of type FirstnameLastName ("JanneJalkanen").
+     * @param name the login name
+     */
+    public void setLoginName( String name );
+
+    /**
+     * Sets the user's password for use with custom authentication. It is
+     * <em>not</em> the responsibility of implementing classes to hash the
+     * password; that responsibility is borne by the UserDatabase implementation
+     * during save operations (see {@link UserDatabase#save(UserProfile)}).
+     * Note that the password field is not meaningful for container
+     * authentication; the user's private credentials are generally stored
+     * elsewhere.
+     * @param arg the password
+     */
+    public void setPassword( String arg );
+
+    /**
+     * No-op method. In previous versions of JSPWiki, the method
+     * set the user's wiki name directly. Now, the wiki name is automatically
+     * calculated based on the full name.
+     * @param name the wiki name
+     * @deprecated This method will be removed in a future release.
+     */
+    public void setWikiName( String name );
+
+    /**
+     * Returns a string representation of this user profile.
+     * @return the string
+     */
+    public String toString();
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,671 @@
+/* 
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-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.auth.user;
+
+import java.io.*;
+import java.security.Principal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.ecyrd.jspwiki.NoRequiredPropertyException;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.WikiPrincipal;
+import com.ecyrd.jspwiki.auth.WikiSecurityException;
+
+/**
+ * <p>Manages {@link DefaultUserProfile} objects using XML files for persistence.
+ * Passwords are hashed using SHA1. User entries are simple <code>&lt;user&gt;</code>
+ * elements under the root. User profile properties are attributes of the
+ * element. For example:</p>
+ * <blockquote><code>
+ * &lt;users&gt;<br/>
+ * &nbsp;&nbsp;&lt;user loginName="janne" fullName="Janne Jalkanen"<br/> 
+ * &nbsp;&nbsp;&nbsp;&nbsp;wikiName="JanneJalkanen" email="janne@ecyrd.com"<br/>
+ * &nbsp;&nbsp;&nbsp;&nbsp;password="{SHA}457b08e825da547c3b77fbc1ff906a1d00a7daee"/&gt;<br/>
+ * &lt;/users&gt;
+ * </code></blockquote> 
+ * <p>In this example, the un-hashed password is <code>myP@5sw0rd</code>. Passwords are hashed without salt.</p>
+ * @author Andrew Jaquith
+ * @since 2.3
+ */
+
+// FIXME: If the DB is shared across multiple systems, it's possible to lose accounts
+//        if two people add new accounts right after each other from different wikis.
+public class XMLUserDatabase extends AbstractUserDatabase
+{
+
+    /**
+     * The jspwiki.properties property specifying the file system location of
+     * the user database.
+     */
+    public static final String  PROP_USERDATABASE = "jspwiki.xmlUserDatabaseFile";
+    
+    private static final String DEFAULT_USERDATABASE = "userdatabase.xml";
+
+    private static final String CREATED           = "created";
+    
+    private static final String EMAIL             = "email";
+
+    private static final String FULL_NAME         = "fullName";
+
+    private static final String LOGIN_NAME        = "loginName";
+
+    private static final String LAST_MODIFIED     = "lastModified";
+    
+    private static final String PASSWORD          = "password";
+
+    private static final String USER_TAG          = "user";
+
+    private static final String WIKI_NAME         = "wikiName";
+
+    private Document            c_dom             = null;
+
+    private DateFormat          c_defaultFormat   = DateFormat.getDateTimeInstance();
+
+    private DateFormat          c_format          = new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss:SSS z");
+    
+    private File                c_file            = null;
+
+    /**
+     * Looks up and deletes the first {@link UserProfile} in the user database
+     * that matches a profile having a given login name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param loginName the login name of the user profile that shall be deleted
+     */
+    public synchronized void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException
+    {
+        if ( c_dom == null )
+        {
+            throw new WikiSecurityException( "FATAL: database does not exist" );
+        }
+            
+        NodeList users = c_dom.getDocumentElement().getElementsByTagName( USER_TAG );
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element user = (Element) users.item( i );
+            if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) )
+            {
+                c_dom.getDocumentElement().removeChild(user);
+                
+                // Commit to disk
+                saveDOM();
+                return;
+            }
+        }
+        throw new NoSuchPrincipalException( "Not in database: " + loginName );
+    }        
+
+    /**
+     * Looks up and returns the first {@link UserProfile}in the user database
+     * that matches a profile having a given e-mail address. If the user
+     * database does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the e-mail address of the desired user profile
+     * @return the user profile
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByEmail(String)
+     */
+    public UserProfile findByEmail( String index ) throws NoSuchPrincipalException
+    {
+        UserProfile profile = findByAttribute( EMAIL, index );
+        if ( profile != null )
+        {
+            return profile;
+        }
+        throw new NoSuchPrincipalException( "Not in database: " + index );
+    }
+
+    /**
+     * Looks up and returns the first {@link UserProfile}in the user database
+     * that matches a profile having a given full name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the fill name of the desired user profile
+     * @return the user profile
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByFullName(java.lang.String)
+     */
+    public UserProfile findByFullName( String index ) throws NoSuchPrincipalException
+    {
+        UserProfile profile = findByAttribute( FULL_NAME, index );
+        if ( profile != null )
+        {
+            return profile;
+        }
+        throw new NoSuchPrincipalException( "Not in database: " + index );
+    }
+
+    /**
+     * Looks up and returns the first {@link UserProfile}in the user database
+     * that matches a profile having a given login name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the login name of the desired user profile
+     * @return the user profile
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
+     */
+    public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException
+    {
+        UserProfile profile = findByAttribute( LOGIN_NAME, index );
+        if ( profile != null )
+        {
+            return profile;
+        }
+        throw new NoSuchPrincipalException( "Not in database: " + index );
+    }
+
+    /**
+     * Looks up and returns the first {@link UserProfile}in the user database
+     * that matches a profile having a given wiki name. If the user database
+     * does not contain a user with a matching attribute, throws a
+     * {@link NoSuchPrincipalException}.
+     * @param index the wiki name of the desired user profile
+     * @return the user profile
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(java.lang.String)
+     */
+    public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException
+    {
+        UserProfile profile = findByAttribute( WIKI_NAME, index );
+        if ( profile != null )
+        {
+            return profile;
+        }
+        throw new NoSuchPrincipalException( "Not in database: " + index );
+    }
+
+    /**
+     * Returns all WikiNames that are stored in the UserDatabase
+     * as an array of WikiPrincipal objects. If the database does not
+     * contain any profiles, this method will return a zero-length
+     * array.
+     * @return the WikiNames
+     */
+    public Principal[] getWikiNames() throws WikiSecurityException
+    {
+        if ( c_dom == null )
+        {
+            throw new IllegalStateException( "FATAL: database does not exist" );
+        }
+        Set principals = new HashSet();
+        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element user = (Element) users.item( i );
+            String wikiName = user.getAttribute( WIKI_NAME );
+            if ( wikiName == null )
+            {
+                log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." );
+            }
+            else
+            {
+                Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
+                principals.add( principal );
+            }
+        }
+        return (Principal[])principals.toArray( new Principal[principals.size()] );
+    }
+    
+    /**
+     * Initializes the user database based on values from a Properties object.
+     * The properties object must contain a file path to the XML database file
+     * whose key is {@link #PROP_USERDATABASE}.
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#initialize(com.ecyrd.jspwiki.WikiEngine,
+     *      java.util.Properties)
+     * @throws NoRequiredPropertyException if the user database cannot be located, parsed, or opened
+     */
+    public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
+    {
+        File defaultFile = null;
+        if( engine.getRootPath() == null )
+        {
+            log.warn( "Cannot identify JSPWiki root path"  );
+            defaultFile = new File( "WEB-INF/" + DEFAULT_USERDATABASE ).getAbsoluteFile();
+        }
+        else
+        {
+            defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + DEFAULT_USERDATABASE );
+        }
+
+        // Get database file location
+        String file = props.getProperty( PROP_USERDATABASE );
+        if( file == null )
+        {
+            log.error( "XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile  );
+            c_file = defaultFile;
+        }
+        else 
+        {
+            c_file = new File( file );
+        }
+
+        log.info("XML user database at "+c_file.getAbsolutePath());
+        
+        buildDOM();
+        sanitizeDOM();
+    }
+    
+    private void buildDOM()
+    {
+        // Read DOM
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating( false );
+        factory.setExpandEntityReferences( false );
+        factory.setIgnoringComments( true );
+        factory.setNamespaceAware( false );
+        try
+        {
+            c_dom = factory.newDocumentBuilder().parse( c_file );
+            log.debug( "Database successfully initialized" );
+            c_lastModified = c_file.lastModified();
+            c_lastCheck    = System.currentTimeMillis();
+        }
+        catch( ParserConfigurationException e )
+        {
+            log.error( "Configuration error: " + e.getMessage() );
+        }
+        catch( SAXException e )
+        {
+            log.error( "SAX error: " + e.getMessage() );
+        }
+        catch( FileNotFoundException e )
+        {
+            log.info("User database not found; creating from scratch...");
+        }
+        catch( IOException e )
+        {
+            log.error( "IO error: " + e.getMessage() );
+        }
+        if ( c_dom == null )
+        {
+            try
+            {
+                //
+                //  Create the DOM from scratch
+                //
+                c_dom = factory.newDocumentBuilder().newDocument();
+                c_dom.appendChild( c_dom.createElement( "users") );
+            }
+            catch( ParserConfigurationException e )
+            {
+                log.fatal( "Could not create in-memory DOM" );
+            }
+        }
+    }
+    
+    private void saveDOM() throws WikiSecurityException
+    {
+        if ( c_dom == null )
+        {
+            log.fatal( "User database doesn't exist in memory." );
+        }
+
+        File newFile = new File( c_file.getAbsolutePath() + ".new" );
+        try
+        {
+            BufferedWriter io = new BufferedWriter( new OutputStreamWriter ( 
+                    new FileOutputStream( newFile ), "UTF-8" ) );
+            
+            // Write the file header and document root
+            io.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+            io.write("<users>\n");
+            
+            // Write each profile as a <user> node
+            Element root = c_dom.getDocumentElement();
+            NodeList nodes = root.getElementsByTagName( USER_TAG );  
+            for( int i = 0; i < nodes.getLength(); i++ )
+            {
+                Element user = (Element)nodes.item( i );
+                io.write( "<" + USER_TAG + " ");
+                io.write( LOGIN_NAME );
+                io.write( "=\"" + user.getAttribute( LOGIN_NAME ) + "\" " );
+                io.write( WIKI_NAME );
+                io.write( "=\"" + user.getAttribute( WIKI_NAME ) + "\" " );
+                io.write( FULL_NAME );
+                io.write( "=\"" + user.getAttribute( FULL_NAME ) + "\" " );
+                io.write( EMAIL );
+                io.write( "=\"" + user.getAttribute( EMAIL ) + "\" " );
+                io.write( PASSWORD );
+                io.write( "=\"" + user.getAttribute( PASSWORD ) + "\" " );
+                io.write( CREATED );
+                io.write( "=\"" + user.getAttribute( CREATED ) + "\" " );
+                io.write( LAST_MODIFIED );
+                io.write( "=\"" + user.getAttribute( LAST_MODIFIED ) + "\" " );
+                io.write(" />\n");
+            }
+            io.write("</users>");
+            io.close();
+        }
+        catch ( IOException e )
+        {
+            throw new WikiSecurityException( e.getLocalizedMessage() );
+        }
+
+        // Copy new file over old version
+        File backup = new File( c_file.getAbsolutePath() + ".old" );
+        if ( backup.exists() )
+        {
+            if ( !backup.delete() )
+            {
+                log.error( "Could not delete old user database backup: " + backup );
+            }
+        }
+        if ( !c_file.renameTo( backup ) )
+        {
+            log.error( "Could not create user database backup: " + backup );
+        }
+        if ( !newFile.renameTo( c_file ) )
+        {
+            log.error( "Could not save database: " + backup + " restoring backup." );
+            if ( !backup.renameTo( c_file ) )
+            {
+                log.error( "Restore failed. Check the file permissions." );
+            }
+            log.error( "Could not save database: " + c_file + ". Check the file permissions" );
+        }
+    }
+    
+    private long c_lastCheck    = 0;
+    private long c_lastModified = 0;
+    
+    private void checkForRefresh()
+    {
+        long time = System.currentTimeMillis();
+        
+        if( time - c_lastCheck > 60*1000L )
+        {
+            long lastModified = c_file.lastModified();
+            
+            if( lastModified > c_lastModified )
+            {
+                buildDOM();
+            }
+        }
+    }
+    
+    /**
+     * Determines whether the user database shares user/password data with the
+     * web container; always returns <code>false</code>.
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#isSharedWithContainer()
+     */
+    public boolean isSharedWithContainer()
+    {
+        return false;
+    }
+
+    /**
+     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#rename(String, String)
+     */
+    public synchronized void rename(String loginName, String newName) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException
+    {
+        if ( c_dom == null )
+        {
+            log.fatal( "Could not rename profile '" + loginName + "'; database does not exist" );
+            throw new IllegalStateException( "FATAL: database does not exist" );
+        }
+        checkForRefresh();
+        
+        // Get the existing user; if not found, throws NoSuchPrincipalException
+        UserProfile profile = findByLoginName( loginName );
+        
+        // Get user with the proposed name; if found, it's a collision
+        try 
+        {
+            UserProfile otherProfile = findByLoginName( newName );
+            if ( otherProfile != null )
+            {
+                throw ( new DuplicateUserException( "Cannot rename: the login name '" + newName + "' is already taken." ) );
+            }
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            // Good! That means it's safe to save using the new name
+        }
+        
+        // Find the user with the old login id attribute, and change it
+        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element user = (Element) users.item( i );
+            if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) )
+            {
+                Date modDate = new Date( System.currentTimeMillis() );
+                setAttribute( user, LOGIN_NAME, newName );
+                setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) );
+                profile.setLoginName( newName );
+                profile.setLastModified( modDate );
+                break;
+            }
+        }
+        
+        // Commit to disk
+        saveDOM();
+    }
+    
+    /**
+     * Saves a {@link UserProfile}to the user database, overwriting the
+     * existing profile if it exists. The user name under which the profile
+     * should be saved is returned by the supplied profile's
+     * {@link UserProfile#getLoginName()}method.
+     * @param profile the user profile to save
+     * @throws WikiSecurityException if the profile cannot be saved
+     */
+    public synchronized void save( UserProfile profile ) throws WikiSecurityException
+    {
+        if ( c_dom == null )
+        {
+            log.fatal( "Could not save profile " + profile + " database does not exist" );
+            throw new IllegalStateException( "FATAL: database does not exist" );
+        }
+        
+        checkForRefresh();
+        
+        String index = profile.getLoginName();
+        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        Element user = null;
+        boolean isNew = true;
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element currentUser = (Element) users.item( i );
+            if ( currentUser.getAttribute( LOGIN_NAME ).equals( index ) )
+            {
+                user = currentUser;
+                isNew = false;
+                break;
+            }
+        }
+        Date modDate = new Date( System.currentTimeMillis() );
+        if ( isNew )
+        {
+            profile.setCreated( modDate );
+            log.info( "Creating new user " + index );
+            user = c_dom.createElement( USER_TAG );
+            c_dom.getDocumentElement().appendChild( user );
+            setAttribute( user, CREATED, c_format.format( profile.getCreated() ) );
+        }
+        setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) );
+        setAttribute( user, LOGIN_NAME, profile.getLoginName() );
+        setAttribute( user, FULL_NAME, profile.getFullname() );
+        setAttribute( user, WIKI_NAME, profile.getWikiName() );
+        setAttribute( user, EMAIL, profile.getEmail() );
+
+        // Hash and save the new password if it's different from old one
+        String newPassword = profile.getPassword();
+        if ( newPassword != null && !newPassword.equals( "" ) )
+        {
+            String oldPassword = user.getAttribute( PASSWORD );
+            if ( !oldPassword.equals( newPassword ) )
+            {
+                setAttribute( user, PASSWORD, SHA_PREFIX + getHash( newPassword ) );
+            }
+        }
+
+        // Set the profile timestamps
+        if ( isNew )
+        {
+            profile.setCreated( modDate );
+        }
+        profile.setLastModified( modDate );
+        
+        // Commit to disk
+        saveDOM();
+    }
+
+    /**
+     * Private method that returns the first {@link UserProfile}matching a
+     * &lt;user&gt; element's supplied attribute.
+     * @param matchAttribute
+     * @param index
+     * @return the profile, or <code>null</code> if not found
+     */
+    private UserProfile findByAttribute( String matchAttribute, String index )
+    {
+        if ( c_dom == null )
+        {
+            throw new IllegalStateException( "FATAL: database does not exist" );
+        }
+        
+        checkForRefresh();
+        
+        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element user = (Element) users.item( i );
+            if ( user.getAttribute( matchAttribute ).equals( index ) )
+            {
+                UserProfile profile = new DefaultUserProfile();
+                profile.setLoginName( user.getAttribute( LOGIN_NAME ) );
+                profile.setFullname( user.getAttribute( FULL_NAME ) );
+                profile.setPassword( user.getAttribute( PASSWORD ) );
+                profile.setEmail( user.getAttribute( EMAIL ) );
+                String created = user.getAttribute( CREATED );
+                String modified = user.getAttribute( LAST_MODIFIED );
+                
+                profile.setCreated( parseDate( profile, created ) );                  
+                profile.setLastModified( parseDate( profile, modified ) );                  
+
+                return profile;
+            }
+        }
+        return null;
+    }
+
+    /**
+     *  Tries to parse a date using the default format - then, for backwards
+     *  compatibility reasons, tries the platform default.
+     *  
+     *  @param profile
+     *  @param date
+     *  @return A parsed date, or null, if both parse attempts fail.
+     */
+    private Date parseDate( UserProfile profile, String date )
+    {
+        try
+        {
+            return c_format.parse( date );
+        }
+        catch( ParseException e )
+        {
+            try
+            {
+                return c_defaultFormat.parse( date );
+            }
+            catch ( ParseException e2)
+            {
+                log.warn("Could not parse 'created' or 'lastModified' "
+                    + "attribute for "
+                    + " profile '" + profile.getLoginName() + "'."
+                    + " It may have been tampered with." );
+            }            
+        }
+        return null;
+    }
+    
+    /**
+     * After loading the DOM, this method sanity-checks the dates in the DOM and makes
+     * sure they are formatted properly. This is sort-of hacky, but it should work.
+     */
+    private void sanitizeDOM()
+    {
+        if ( c_dom == null )
+        {
+            throw new IllegalStateException( "FATAL: database does not exist" );
+        }
+        
+        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        for( int i = 0; i < users.getLength(); i++ )
+        {
+            Element user = (Element) users.item( i );
+            String loginName = user.getAttribute( LOGIN_NAME );
+            String created = user.getAttribute( CREATED );
+            String modified = user.getAttribute( LAST_MODIFIED );
+            try
+            {
+                created = c_format.format( c_format.parse( created ) );
+                modified = c_format.format( c_format.parse( modified ) );
+                user.setAttribute( CREATED,  created );
+                user.setAttribute( LAST_MODIFIED,  modified );
+            }
+            catch( ParseException e )
+            {
+                try
+                {
+                    created = c_format.format( c_defaultFormat.parse( created ) );
+                    modified = c_format.format( c_defaultFormat.parse( modified ) );
+                    user.setAttribute( CREATED,  created );
+                    user.setAttribute( LAST_MODIFIED,  modified );
+                }
+                catch ( ParseException e2)
+                {
+                    log.warn("Could not parse 'created' or 'lastModified' "
+                        + "attribute for "
+                        + " profile '" + loginName + "'."
+                        + " It may have been tampered with." );
+                }            
+            }
+        }
+    }
+    
+    /**
+     * Private method that sets an attibute value for a supplied DOM element.
+     * @param element the element whose attribute is to be set
+     * @param attribute the name of the attribute to set
+     * @param value the desired attribute value
+     */
+    private void setAttribute( Element element, String attribute, String value )
+    {
+        if ( value != null )
+        {
+            element.setAttribute( attribute, value );
+        }
+    }
+}
\ No newline at end of file

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/AttachmentDavProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/AttachmentDavProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/AttachmentDavProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/AttachmentDavProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,179 @@
+/*
+    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.dav;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiPage;
+import com.ecyrd.jspwiki.action.AttachActionBean;
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.ecyrd.jspwiki.dav.items.AttachmentItem;
+import com.ecyrd.jspwiki.dav.items.DavItem;
+import com.ecyrd.jspwiki.dav.items.DirectoryItem;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+public class AttachmentDavProvider implements DavProvider
+{
+    protected WikiEngine m_engine;
+    protected static final Logger log = Logger.getLogger( AttachmentDavProvider.class );
+
+    public AttachmentDavProvider( WikiEngine engine )
+    {
+        m_engine = engine;
+    }
+
+    public WikiEngine getEngine()
+    {
+        return m_engine;
+    }
+
+    private Collection listAllPagesWithAttachments()
+    {
+        ArrayList pageNames = new ArrayList();
+
+        try
+        {
+            Collection atts = m_engine.getAttachmentManager().getAllAttachments();
+
+            for( Iterator i = atts.iterator(); i.hasNext(); )
+            {
+                Attachment att = (Attachment)i.next();
+
+                String pageName = att.getParentName();
+
+                if( !pageNames.contains(pageName) )
+                    pageNames.add( pageName );
+            }
+        }
+        catch (ProviderException e)
+        {
+            log.error("Unable to get all attachments",e);
+        }
+
+        Collections.sort( pageNames );
+
+        ArrayList result = new ArrayList();
+
+        for( Iterator i = pageNames.iterator(); i.hasNext(); )
+        {
+            DirectoryItem di = new DirectoryItem( this, new DavPath( (String)i.next() ));
+
+            result.add( di );
+        }
+        return result;
+    }
+
+    protected Collection listAttachmentsOfPage( DavPath path )
+    {
+        String pageName = path.getName();
+
+        log.debug("Listing attachments for page "+pageName);
+
+        ArrayList result = new ArrayList();
+        try
+        {
+            WikiPage page = m_engine.getPage( pageName );
+            Collection attachments = m_engine.getAttachmentManager().listAttachments(page);
+
+            for( Iterator i = attachments.iterator(); i.hasNext(); )
+            {
+                Attachment att = (Attachment) i.next();
+
+                DavPath thisPath = new DavPath( "/" );
+
+                thisPath.append( att.getName() );
+
+                AttachmentItem ai = new AttachmentItem( this, thisPath, att );
+
+                result.add( ai );
+            }
+        }
+        catch( ProviderException e )
+        {
+            log.error("Unable to list attachments, returning what I got",e);
+            // FIXME: Not a good way to handle errors
+        }
+
+        return result;
+    }
+
+    public DavItem getItem(DavPath path)
+    {
+        if( path.isRoot() )
+        {
+            DirectoryItem di = new DirectoryItem( this, new DavPath("") );
+
+            di.addDavItems( listAllPagesWithAttachments() );
+            return di;
+        }
+        else if( path.isDirectory() )
+        {
+            DirectoryItem di = new DirectoryItem( this, path );
+
+            di.addDavItems( listAttachmentsOfPage(path) );
+
+            return di;
+        }
+        else
+        {
+            String attName = path.getPath();
+
+            try
+            {
+                Attachment att = m_engine.getAttachmentManager().getAttachmentInfo( attName );
+
+                if( att != null )
+                {
+                    AttachmentItem ai = new AttachmentItem( this, path, att );
+
+                    return ai;
+                }
+            }
+            catch( ProviderException e )
+            {
+                log.error("Unable to get the attachment data for "+attName,e);
+            }
+        }
+        return null;
+    }
+
+    public void setItem(DavPath path, DavItem item)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    public String getURL(DavPath path)
+    {
+        String p = path.getPath();
+
+        if( p.startsWith("/") ) p = p.substring( 1 );
+
+        String url = DavUtil.combineURL( DavUtil.combineURL( m_engine.getBaseURL() , "attach/"), path.getPath() );
+        return url;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/DavContext.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/DavContext.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/DavContext.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/dav/DavContext.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,61 @@
+/* 
+    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.dav;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+public class DavContext
+{
+    protected int    m_depth = -1;
+    protected DavPath m_path;
+    
+    public DavContext( HttpServletRequest req, DavPath dp )
+    {
+        m_path = dp;
+        
+        String depth = req.getHeader("Depth");
+        
+        if( depth == null )
+        {
+            m_depth = -1;
+        }
+        else
+        {
+            m_depth = Integer.parseInt( depth );
+        }
+    }
+
+    /**
+     * @return Returns the m_depth.
+     */
+    public int getDepth()
+    {
+        return m_depth;
+    }
+
+    /**
+     * @return Returns the m_path.
+     */
+    public DavPath getPath()
+    {
+        return m_path;
+    }
+}
\ No newline at end of file