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 [7/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/WikiPage.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiPage.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,414 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001 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;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.ecyrd.jspwiki.auth.acl.Acl;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.providers.WikiPageProvider;
+
+/**
+ *  Simple wrapper class for the Wiki page attributes.  The Wiki page
+ *  content is moved around in Strings, though.
+ */
+
+// FIXME: We need to rethink how metadata is being used - probably the 
+//        author, date, etc. should also be part of the metadata.  We also
+//        need to figure out the metadata lifecycle.
+
+public class WikiPage
+    implements Cloneable,
+               Comparable
+{
+    private static final long serialVersionUID = 1L;
+
+    private       String     m_name;
+    private       WikiEngine m_engine;
+    private       String     m_wiki;
+    private Date             m_lastModified;
+    private long             m_fileSize = -1;
+    private int              m_version = WikiPageProvider.LATEST_VERSION;
+    private String           m_author = null;
+    private final HashMap    m_attributes = new HashMap();
+    private String           m_qualifiedName;
+
+    /**
+     *  "Summary" is a short summary of the page.  It is a String.
+     */
+    public static final String DESCRIPTION = "summary";
+
+    /** A special variable name for storing a page alias. */
+    public static final String ALIAS = "alias";
+    
+    /** A special variable name for storing a redirect note */
+    public static final String REDIRECT = "redirect";
+
+    /** A special variable name for storing a changenote. */
+    public static final String CHANGENOTE = "changenote";
+    
+    private Acl m_accessList = null;
+    
+    /**
+     *  Create a new WikiPage using a given engine and name.
+     *  
+     *  @param engine The WikiEngine that owns this page.
+     *  @param name   The name of the page.
+     */
+    public WikiPage( WikiEngine engine, String name )
+    {
+        m_engine = engine;
+        m_name = name;
+        m_wiki = engine.getApplicationName();
+        m_qualifiedName =m_wiki + ":" + m_name;
+    }
+
+    /**
+     *  Returns the name of the page.
+     *  
+     *  @return The page name.
+     */
+    public String getName()
+    {
+        return m_name;
+    }
+    
+    public String getQualifiedName()
+    {
+        return m_qualifiedName;
+    }
+
+    /**
+     * Convenience method that returns the collection of 
+     * Attachment objects associated with this WikiPage.
+     * It simply delegates to
+     * {@link com.ecyrd.jspwiki.attachment.AttachmentManager#listAttachments(WikiPage)}.
+     * @return the collection
+     */
+    public Collection<Attachment> getAttachments() throws ProviderException
+    {
+        return m_engine.getAttachmentManager().listAttachments( this );
+    }
+    
+    /**
+     *  A WikiPage may have a number of attributes, which might or might not be 
+     *  available.  Typically attributes are things that do not need to be stored
+     *  with the wiki page to the page repository, but are generated
+     *  on-the-fly.  A provider is not required to save them, but they
+     *  can do that if they really want.
+     *
+     *  @param key The key using which the attribute is fetched
+     *  @return The attribute.  If the attribute has not been set, returns null.
+     */
+    public Object getAttribute( String key )
+    {
+        return m_attributes.get( key );
+    }
+
+    /**
+     *  Sets an metadata attribute.
+     *  
+     *  @see #getAttribute()
+     *  @param key The key for the attribute used to fetch the attribute later on.
+     *  @param attribute The attribute value
+     */
+    public void setAttribute( String key, Object attribute )
+    {
+        m_attributes.put( key, attribute );
+    }
+
+    /**
+     * Returns the full attributes Map, in case external code needs
+     * to iterate through the attributes.
+     * 
+     * @return The attribute Map.  Please note that this is a direct
+     *         reference, not a copy.
+     */
+    public Map getAttributes() 
+    {
+        return m_attributes;
+    }
+
+    /**
+     *  Removes an attribute from the page, if it exists.
+     *  
+     *  @param  key The key for the attribute
+     *  @return If the attribute existed, returns the object.
+     *  @since 2.1.111
+     */
+    public Object removeAttribute( String key )
+    {
+        return m_attributes.remove( key );
+    }
+
+    /**
+     *  Returns the date when this page was last modified.
+     *  
+     *  @return The last modification date
+     */
+    public Date getLastModified()
+    {
+        return m_lastModified;
+    }
+
+    /**
+     *  Sets the last modification date.  In general, this is only
+     *  changed by the provider.
+     *  
+     *  @param date The date
+     */
+    public void setLastModified( Date date )
+    {
+        m_lastModified = date;
+    }
+
+    /**
+     *  Sets the page version.  In general, this is only changed
+     *  by the provider.
+     *  
+     *  @param version The version number
+     */
+    public void setVersion( int version )
+    {
+        m_version = version;
+    }
+
+    /**
+     *  Returns the version that this WikiPage instance represents.
+     *  
+     *  @return the version number of this page.
+     */
+    public int getVersion()
+    {
+        return m_version;
+    }
+
+    /**
+     *  Returns the size of the page.
+     *  
+     *  @return the size of the page. 
+     *  @since 2.1.109
+     */
+    public long getSize()
+    {
+        return m_fileSize;
+    }
+
+    /**
+     *  Sets the size.  Typically called by the provider only.
+     *  
+     *  @param size The size of the page.
+     *  @since 2.1.109
+     */
+    public void setSize( long size )
+    {
+        m_fileSize = size;
+    }
+
+    /**
+     *  Returns the Acl for this page.  May return <code>null</code>, 
+     *  in case there is no Acl defined, or it has not
+     *  yet been set by {@link #setAcl(Acl)}.
+     *  
+     *  @return The access control list.  May return null, if there is 
+     *          no acl.
+     */
+    public Acl getAcl()
+    {
+        return m_accessList;
+    }
+
+    /**
+     * Sets the Acl for this page. Note that method does <em>not</em>
+     * persist the Acl itself to back-end storage or in page markup;
+     * it merely sets the internal field that stores the Acl. To
+     * persist the Acl, callers should invoke 
+     * {@link com.ecyrd.jspwiki.auth.acl.AclManager#setPermissions(WikiPage, Acl)}.
+     * @param acl The Acl to set
+     */
+    public void setAcl( Acl acl )
+    {
+        m_accessList = acl;
+    }
+
+    /**
+     *  Sets the author of the page.  Typically called only by the provider.
+     *  
+     *  @param author The author name.
+     */
+    public void setAuthor( String author )
+    {
+        m_author = author;
+    }
+
+    /**
+     *  Returns author name, or null, if no author has been defined.
+     *  
+     *  @return Author name, or possibly null.
+     */
+    public String getAuthor()
+    {
+        return m_author;
+    }
+    
+    /**
+     *  Returns the wiki nanme for this page
+     *  
+     *  @return The name of the wiki.
+     */
+    public String getWiki()
+    {
+        return m_wiki;
+    }
+
+    /**
+     *  This method will remove all metadata from the page.
+     */
+    public void invalidateMetadata()
+    {        
+        m_hasMetadata = false;
+        setAcl( null );
+        m_attributes.clear();
+    }
+
+    private boolean m_hasMetadata = false;
+
+    /**
+     *  Returns <code>true</code> if the page has valid metadata; that is, it has been parsed.
+     *  Note that this method is a kludge to support our pre-3.0 metadata system, and as such
+     *  will go away with the new API.
+     *  
+     *  @return true, if the page has metadata.
+     */
+    public boolean hasMetadata()
+    {
+        return m_hasMetadata;
+    }
+
+    /**
+     *  Sets the metadata flag to true.  Never call.
+     */
+    public void setHasMetadata()
+    {
+        m_hasMetadata = true;
+    }
+
+    /**
+     *  Returns a debug-suitable version of the page.
+     *  
+     *  @return A debug string.
+     */
+    public String toString()
+    {
+        return "WikiPage ["+m_wiki+":"+m_name+",ver="+m_version+",mod="+m_lastModified+"]";
+    }
+
+    /**
+     *  Creates a deep clone of a WikiPage.  Strings are not cloned, since
+     *  they're immutable.  Attributes are not cloned, only the internal
+     *  HashMap (so if you modify the contents of a value of an attribute,
+     *  these will reflect back to everyone).
+     *  
+     *  @return A deep clone of the WikiPage
+     */
+    public Object clone()
+    {
+        try
+        {
+            WikiPage p = (WikiPage)super.clone();
+       
+            p.m_engine = m_engine;
+            p.m_name   = m_name;
+            p.m_wiki   = m_wiki;
+            
+            p.m_author       = m_author;
+            p.m_version      = m_version;
+            p.m_lastModified = m_lastModified != null ? (Date)m_lastModified.clone() : null;
+
+            p.m_fileSize     = m_fileSize;
+                            
+            p.m_attributes.putAll(m_attributes);
+            
+            return p;
+        }
+        catch( CloneNotSupportedException e )
+        {}
+        
+        return null;
+    }
+    
+    /**
+     *  Compares a page with another.  The primary sorting order
+     *  is according to page name, and if they have the same name,
+     *  then according to the page version.
+     *  
+     *  @param o The object to compare against
+     *  @return -1, 0 or 1
+     */
+    public int compareTo( Object o )
+    {
+        int res = 0;
+        if( o instanceof WikiPage )
+        {
+            WikiPage c = (WikiPage)o;
+        
+            res = this.getName().compareTo(c.getName());
+            
+            if( res == 0 ) res = this.getVersion()-c.getVersion();
+        }
+            
+        return res;
+    }
+    
+    /**
+     *  A page is equal to another page if its name and version are equal.
+     *  
+     *  {@inheritDoc}
+     */
+    public boolean equals( Object o )
+    {
+        if( o != null && o instanceof WikiPage )
+        {
+            WikiPage oo = (WikiPage) o;
+        
+            if( oo.getName().equals( getName() ) )
+            {
+                if( oo.getVersion() == getVersion() )
+                {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+    
+    /**
+     *  {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return m_name.hashCode() * m_version;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiProvider.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;
+
+import java.util.Properties;
+import java.io.IOException;
+
+/**
+ *  A generic Wiki provider for all sorts of things that the Wiki can
+ *  store.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+public interface WikiProvider
+{
+    /**
+     *  Passing this to any method should get the latest version
+     */
+    public static final int LATEST_VERSION = -1;
+
+    /**
+     *  Initializes the page provider.
+     *  
+     *  @param engine WikiEngine to own this provider
+     *  @param properties A set of properties used to initialize this provider
+     *  @throws NoRequiredPropertyException If the provider needs a property which is not found in the property set
+     *  @throws IOException If there is an IO problem
+     */
+    public void initialize( WikiEngine engine, Properties properties ) 
+        throws NoRequiredPropertyException,
+               IOException;
+
+    /**
+     *  Return a valid HTML string for information.  May
+     *  be anything.
+     *  @since 1.6.4
+     *  @return A string describing the provider.
+     */
+
+    public String getProviderInfo();
+}
+
+

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiSession.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiSession.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiSession.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/WikiSession.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,1038 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2005 The 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;
+
+import java.security.AccessControlException;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.auth.*;
+import com.ecyrd.jspwiki.auth.authorize.Group;
+import com.ecyrd.jspwiki.auth.authorize.GroupManager;
+import com.ecyrd.jspwiki.auth.authorize.Role;
+import com.ecyrd.jspwiki.auth.login.CookieAssertionLoginModule;
+import com.ecyrd.jspwiki.auth.login.PrincipalWrapper;
+import com.ecyrd.jspwiki.auth.user.UserDatabase;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+import com.ecyrd.jspwiki.event.WikiEvent;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+import com.ecyrd.jspwiki.event.WikiSecurityEvent;
+
+/**
+ * <p>Represents a long-running wiki session, with an associated user Principal,
+ * user Subject, and authentication status. This class is initialized with
+ * minimal, default-deny values: authentication is set to <code>false</code>,
+ * and the user principal is set to <code>null</code>.</p>
+ * <p>The WikiSession class allows callers to:</p>
+ * <ul>
+ *   <li>Obtain the authentication status of the user via
+ *     {@link #isAnonymous()} and {@link #isAuthenticated()}</li>
+ *   <li>Query the session for Principals representing the
+ *     user's identity via {@link #getLoginPrincipal()},
+ *     {@link #getUserPrincipal()} and {@link #getPrincipals()}</li>
+ *   <li>Store, retrieve and clear UI messages via
+ *     {@link #addMessage(String)}, {@link #getMessages(String)}
+ *     and {@link #clearMessages(String)}</li>
+ * </ul>
+ * <p>To keep track of the Principals each user posseses, each WikiSession
+ * stores a JAAS Subject. Various login processes add or remove Principals
+ * when users authenticate or log out.</p>
+ * <p>WikiSession implements the {@link com.ecyrd.jspwiki.event.WikiEventListener}
+ * interface and listens for group add/change/delete events fired by
+ * event sources the WikiSession is registered with. Normally,
+ * {@link com.ecyrd.jspwiki.auth.AuthenticationManager} registers each WikiSession
+ * with the {@link com.ecyrd.jspwiki.auth.authorize.GroupManager}
+ * so it can catch group events. Thus, when a user is added to a
+ * {@link com.ecyrd.jspwiki.auth.authorize.Group}, a corresponding
+ * {@link com.ecyrd.jspwiki.auth.GroupPrincipal} is injected into
+ * the Subject's Principal set. Likewise, when the user is removed from
+ * the Group or the Group is deleted, the GroupPrincipal is removed
+ * from the Subject. The effect that this strategy produces is extremely
+ * beneficial: when someone adds a user to a wiki group, that user
+ * <em>immediately</em> gains the privileges associated with that
+ * group; he or she does not need to re-authenticate.
+ * </p>
+ * <p>In addition to methods for examining individual <code>WikiSession</code>
+ * objects, this class also contains a number of static methods for
+ * managing WikiSessions for an entire wiki. These methods allow callers
+ * to find, query and remove WikiSession objects, and
+ * to obtain a list of the current wiki session users.</p>
+ * <p>WikiSession encloses a protected static class, {@link SessionMonitor},
+ * to keep track of WikiSessions registered with each wiki.</p>
+ * @author Andrew R. Jaquith
+ */
+public final class WikiSession implements WikiEventListener
+{
+
+    /** An anonymous user's session status. */
+    public static final String  ANONYMOUS             = "anonymous";
+
+    /** An asserted user's session status. */
+    public static final String  ASSERTED              = "asserted";
+
+    /** An authenticated user's session status. */
+    public static final String  AUTHENTICATED         = "authenticated";
+
+    private static final int    ONE                   = 48;
+
+    private static final int    NINE                  = 57;
+
+    private static final int    DOT                   = 46;
+
+    private static final Logger log                   = Logger.getLogger( WikiSession.class );
+
+    private static final String ALL                   = "*";
+
+    private static ThreadLocal<WikiSession> c_guestSession  = new ThreadLocal<WikiSession>();
+
+    private final Subject       m_subject             = new Subject();
+
+    private final Map<String,Set<String>> m_messages = new HashMap<String,Set<String>>();
+
+    private String              m_cachedCookieIdentity= null;
+
+    private String              m_cachedRemoteUser    = null;
+
+    private Principal           m_cachedUserPrincipal = null;
+
+    /** The WikiEngine that created this session. */
+    private WikiEngine          m_engine              = null;
+
+    private String              m_status              = ANONYMOUS;
+
+    private Principal           m_userPrincipal       = WikiPrincipal.GUEST;
+
+    private Principal           m_loginPrincipal      = WikiPrincipal.GUEST;
+
+    private Locale              m_cachedLocale        = Locale.getDefault();
+
+    /**
+     * Returns <code>true</code> if one of this WikiSession's user Principals
+     * can be shown to belong to a particular wiki group. If the user is
+     * not authenticated, this method will always return <code>false</code>.
+     * @param group the group to test
+     * @return the result
+     */
+    protected final boolean isInGroup( Group group )
+    {
+        Principal[] principals = getPrincipals();
+        for ( int i = 0; i < principals.length; i++ )
+        {
+          if ( isAuthenticated() && group.isMember( principals[i] ) )
+          {
+              return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Private constructor to prevent WikiSession from being instantiated
+     * directly.
+     */
+    private WikiSession()
+    {
+    }
+
+    /**
+     * Returns <code>true</code> if the user is considered asserted via
+     * a session cookie; that is, the Subject contains the Principal
+     * Role.ASSERTED.
+     * @return Returns <code>true</code> if the user is asserted
+     */
+    public final boolean isAsserted()
+    {
+        return m_subject.getPrincipals().contains( Role.ASSERTED );
+    }
+
+    /**
+     * Returns the authentication status of the user's session. The user is
+     * considered authenticated if the Subject contains the Principal
+     * Role.AUTHENTICATED. If this method determines that an earlier
+     * LoginModule did not inject Role.AUTHENTICATED, it will inject one
+     * if the user is not anonymous <em>and</em> not asserted.
+     * @return Returns <code>true</code> if the user is authenticated
+     */
+    public final boolean isAuthenticated()
+    {
+        // If Role.AUTHENTICATED is in principals set, always return true.
+        if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) )
+        {
+            return true;
+        }
+
+        // With non-JSPWiki LoginModules, the role may not be there, so
+        // we need to add it if the user really is authenticated.
+        if ( !isAnonymous() && !isAsserted() )
+        {
+            // Inject AUTHENTICATED role
+            m_subject.getPrincipals().add( Role.AUTHENTICATED );
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * <p>Determines whether the current session is anonymous. This will be
+     * true if any of these conditions are true:</p>
+     * <ul>
+     *   <li>The session's Principal set contains
+     *       {@link com.ecyrd.jspwiki.auth.authorize.Role#ANONYMOUS}</li>
+     *   <li>The session's Principal set contains
+     *       {@link com.ecyrd.jspwiki.auth.WikiPrincipal#GUEST}</li>
+     *   <li>The Principal returned by {@link #getUserPrincipal()} evaluates
+     *       to an IP address.</li>
+     * </ul>
+     * <p>The criteria above are listed in the order in which they are
+     * evaluated.</p>
+     * @return whether the current user's identity is equivalent to an IP
+     * address
+     */
+    public final boolean isAnonymous()
+    {
+        Set principals = m_subject.getPrincipals();
+        return principals.contains( Role.ANONYMOUS ) ||
+                 principals.contains( WikiPrincipal.GUEST ) ||
+                 isIPV4Address( getUserPrincipal().getName() );
+    }
+
+    /**
+     * Creates and returns a new login context for this wiki session.
+     * This method is called by
+     * {@link com.ecyrd.jspwiki.auth.AuthenticationManager}.
+     * @param application the name of the application
+     * @param handler the callback handler
+     * @return A new login context
+     * @throws LoginException If the login context cannot be created
+     */
+    public final LoginContext getLoginContext( String application, CallbackHandler handler ) throws LoginException
+    {
+        return new LoginContext( application, m_subject, handler );
+    }
+
+    /**
+     * <p> Returns the Principal used to log in to an authenticated session. The
+     * login principal is determined by examining the Subject's Principal set
+     * for PrincipalWrappers or WikiPrincipals with type designator
+     * <code>LOGIN_NAME</code>; the first one found is the login principal.
+     * If one is not found, this method returns the first principal that isn't
+     * of type Role or GroupPrincipal. If neither of these conditions hold, this method returns
+     * {@link com.ecyrd.jspwiki.auth.WikiPrincipal#GUEST}.
+     * @return the login Principal. If it is a PrincipalWrapper containing an
+     * externally-provided Principal, the object returned is the Principal, not
+     * the wrapper around it.
+     */
+    public final Principal getLoginPrincipal()
+    {
+        if ( m_loginPrincipal instanceof PrincipalWrapper )
+        {
+            return ((PrincipalWrapper)m_loginPrincipal).getPrincipal();
+        }
+        return m_loginPrincipal;
+    }
+
+    /**
+     * <p>Returns the primary user Principal associated with this session. The
+     * primary user principal is determined as follows:</p> <ol> <li>If the
+     * Subject's Principal set contains WikiPrincipals, the first WikiPrincipal
+     * with type designator <code>WIKI_NAME</code> or (alternatively)
+     * <code>FULL_NAME</code> is the primary Principal.</li>
+     *   <li>For all other cases, the first Principal in the Subject's principal
+     *       collection that that isn't of type Role or GroupPrincipal is the primary.</li>
+     * </ol>
+     * If no primary user Principal is found, this method returns
+     * {@link com.ecyrd.jspwiki.auth.WikiPrincipal#GUEST}.
+     * @return the primary user Principal
+     */
+    public final Principal getUserPrincipal()
+    {
+        if ( m_loginPrincipal instanceof PrincipalWrapper )
+        {
+            return ((PrincipalWrapper)m_userPrincipal).getPrincipal();
+        }
+        return m_userPrincipal;
+    }
+
+    /**
+     *  Returns a cached Locale object for this user.  It's better to use
+     *  WikiContext's corresponding getBundle() method, since that will actually
+     *  react if the user changes the locale in the middle, but if that's not
+     *  available (or, for some reason, you need the speed), this method can
+     *  also be used.  The Locale expires when the WikiSession expires, and
+     *  currently there is no way to reset the Locale.
+     *
+     *  @return A cached Locale object
+     *  @since 2.5.96
+     */
+    public final Locale getLocale()
+    {
+        return m_cachedLocale;
+    }
+
+    /**
+     * Adds a message to the generic list of messages associated with the
+     * session. These messages retain their order of insertion and remain until
+     * the {@link #clearMessages()} method is called.
+     * @param message the message to add; if <code>null</code> it is ignored.
+     */
+    public final void addMessage(String message)
+    {
+        addMessage( ALL, message );
+    }
+
+
+    /**
+     * Adds a message to the specific set of messages associated with the
+     * session. These messages retain their order of insertion and remain until
+     * the {@link #clearMessages()} method is called.
+     * @param topic the topic to associate the message to;
+     * @param message the message to add
+     */
+    public final void addMessage(String topic, String message)
+    {
+        if ( topic == null )
+        {
+            throw new IllegalArgumentException( "addMessage: topic cannot be null." );
+        }
+        if ( message == null )
+        {
+            message = "";
+        }
+        Set<String> messages = m_messages.get( topic );
+        if (messages == null )
+        {
+            messages = new LinkedHashSet<String>();
+            m_messages.put( topic, messages );
+        }
+        messages.add( message );
+    }
+
+    /**
+     * Clears all messages associated with this session.
+     */
+    public final void clearMessages()
+    {
+        m_messages.clear();
+    }
+
+    /**
+     * Clears all messages associated with a session topic.
+     * @param topic the topic whose messages should be cleared.
+     */
+    public final void clearMessages( String topic )
+    {
+        Set<String> messages = m_messages.get( topic );
+        if ( messages != null )
+        {
+            m_messages.clear();
+        }
+    }
+
+    /**
+     * Returns all generic messages associated with this session.
+     * The messages stored with the session persist throughout the
+     * session unless they have been reset with {@link #clearMessages()}.
+     * @return the current messsages.
+     */
+    public final String[] getMessages()
+    {
+        return getMessages( ALL );
+    }
+
+    /**
+     * Returns all messages associated with a session topic.
+     * The messages stored with the session persist throughout the
+     * session unless they have been reset with {@link #clearMessages(String)}.
+     * @return the current messsages.
+     * @param topic The topic
+     */
+    public final String[] getMessages( String topic )
+    {
+        Set<String> messages = m_messages.get( topic );
+        if ( messages == null || messages.size() == 0 )
+        {
+            return new String[0];
+        }
+        return messages.toArray( new String[messages.size()] );
+    }
+
+    /**
+     * Returns all user Principals associated with this session. User principals
+     * are those in the Subject's principal collection that aren't of type Role or
+     * of type GroupPrincipal. This is a defensive copy.
+     * @return Returns the user principal
+     * @see com.ecyrd.jspwiki.auth.AuthenticationManager#isUserPrincipal(Principal)
+     */
+    public final Principal[] getPrincipals()
+    {
+        List<Principal> principals = new ArrayList<Principal>();
+
+        // Take the first non Role as the main Principal
+        for( Principal principal : m_subject.getPrincipals() )
+        {
+        	if ( AuthenticationManager.isUserPrincipal( principal ) )
+            {
+            	principals.add( principal );
+			}
+		}
+
+        return principals.toArray( new Principal[principals.size()] );
+    }
+
+    /**
+     * Returns an array of Principal objects that represents the groups and
+     * roles that the user associated with a WikiSession possesses. The array is
+     * built by iterating through the Subject's Principal set and extracting all
+     * Role and GroupPrincipal objects into a list. The list is returned as an
+     * array sorted in the natural order implied by each Principal's
+     * <code>getName</code> method. Note that this method does <em>not</em>
+     * consult the external Authorizer or GroupManager; it relies on the
+     * Principals that have been injected into the user's Subject at login time,
+     * or after group creation/modification/deletion.
+     * @return an array of Principal objects corresponding to the roles the
+     *         Subject possesses
+     */
+    public final Principal[] getRoles()
+    {
+        Set<Principal> roles = new HashSet<Principal>();
+
+        // Add all of the Roles possessed by the Subject directly
+        roles.addAll( m_subject.getPrincipals( Role.class ) );
+
+        // Add all of the GroupPrincipals possessed by the Subject directly
+        roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) );
+
+        // Return a defensive copy
+        Principal[] roleArray = roles.toArray( new Principal[roles.size()] );
+        Arrays.sort( roleArray, WikiPrincipal.COMPARATOR );
+        return roleArray;
+    }
+
+    /**
+     * Removes the wiki session associated with the user's HTTP request
+     * from the cache of wiki sessions, typically as part of a logout
+     * process.
+     * @param engine the wiki engine
+     * @param request the users's HTTP request
+     */
+    public static final void removeWikiSession( WikiEngine engine, HttpServletRequest request )
+    {
+        if ( engine == null || request == null )
+        {
+            throw new IllegalArgumentException( "Request or engine cannot be null." );
+        }
+        SessionMonitor monitor = SessionMonitor.getInstance( engine );
+        monitor.remove( request.getSession() );
+    }
+
+    /**
+     * Returns <code>true</code> if the WikiSession's Subject
+     * possess a supplied Principal. This method eliminates the need
+     * to externally request and inspect the JAAS subject.
+     * @param principal the Principal to test
+     * @return the result
+     */
+    public final boolean hasPrincipal( Principal principal )
+    {
+        return m_subject.getPrincipals().contains( principal );
+
+    }
+
+    /**
+     * Listens for WikiEvents generated by source objects such as the
+     * GroupManager. This method adds Principals to the private Subject managed
+     * by the WikiSession.
+     * @see com.ecyrd.jspwiki.event.WikiEventListener#actionPerformed(com.ecyrd.jspwiki.event.WikiEvent)
+     * {@inheritDoc}
+     */
+    public final void actionPerformed( WikiEvent event )
+    {
+        if ( event instanceof WikiSecurityEvent )
+        {
+            WikiSecurityEvent e = (WikiSecurityEvent)event;
+            if ( e.getTarget() != null )
+            {
+                switch (e.getType() )
+                {
+                    case WikiSecurityEvent.GROUP_ADD:
+                    {
+                        Group group = (Group)e.getTarget();
+                        if ( isInGroup( group ) )
+                        {
+                            m_subject.getPrincipals().add( group.getPrincipal() );
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.GROUP_REMOVE:
+                    {
+                        Group group = (Group)e.getTarget();
+                        if ( m_subject.getPrincipals().contains( group.getPrincipal() ) )
+                        {
+                            m_subject.getPrincipals().remove( group.getPrincipal() );
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.GROUP_CLEAR_GROUPS:
+                    {
+                        m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) );
+                        break;
+                    }
+                    case WikiSecurityEvent.LOGIN_INITIATED:
+                    {
+                        WikiSession target = (WikiSession)e.getTarget();
+                        if ( this.equals( target ) )
+                        {
+                            updatePrincipals();
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.LOGIN_ASSERTED:
+                    {
+                        WikiSession target = (WikiSession)e.getTarget();
+                        if ( this.equals( target ) )
+                        {
+                            m_status = ASSERTED;
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.LOGIN_AUTHENTICATED:
+                    {
+                        WikiSession target = (WikiSession)e.getTarget();
+                        if ( this.equals( target ) )
+                        {
+                            m_status = AUTHENTICATED;
+                            injectUserProfilePrincipals();  // Add principals for the user profile
+                            injectRolePrincipals();  // Inject role and group principals
+                            updatePrincipals();
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.PROFILE_SAVE:
+                    {
+                        WikiSession source = (WikiSession)e.getSource();
+                        if ( this.equals( source ) )
+                        {
+                            injectUserProfilePrincipals();  // Add principals for the user profile
+                            updatePrincipals();
+                        }
+                        break;
+                    }
+                    case WikiSecurityEvent.PROFILE_NAME_CHANGED:
+                    {
+                        // Refresh user principals based on new user profile
+                        WikiSession source = (WikiSession)e.getSource();
+                        if ( this.equals( source ) )
+                        {
+                            // To prepare for refresh, set the new full name as the primary principal
+                            UserProfile[] profiles = (UserProfile[])e.getTarget();
+                            UserProfile newProfile = profiles[1];
+                            if ( newProfile.getFullname() == null )
+                            {
+                                throw new IllegalStateException( "User profile FullName cannot be null." );
+                            }
+                            m_userPrincipal = new WikiPrincipal( newProfile.getFullname() );
+
+                            // Refresh principals for the user profile
+                            injectUserProfilePrincipals();
+                            updatePrincipals();
+                        }
+                        break;
+                    }
+
+                    //
+                    //  No action, if the event is not recognized.
+                    //
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Invalidates the WikiSession and resets its Subject's
+     * Principals to the equivalent of a "guest session".
+     */
+    public final void invalidate()
+    {
+        m_subject.getPrincipals().clear();
+        m_subject.getPrincipals().add( WikiPrincipal.GUEST );
+        m_subject.getPrincipals().add( Role.ANONYMOUS );
+        m_subject.getPrincipals().add( Role.ALL );
+        m_cachedCookieIdentity = null;
+        m_cachedRemoteUser = null;
+        m_cachedUserPrincipal = null;
+    }
+
+    /**
+     * Returns whether the HTTP servlet container's authentication status has
+     * changed. Used to detect whether the container has logged in a user since
+     * the last call to this function. This method is stateful. After calling
+     * this function, the cached values are set to those in the current request.
+     * If the servlet request is <code>null</code>, this method always returns
+     * <code>false</code>. Note that once a user authenticates, the container
+     * status for the session will <em>never</em> change again, unless the
+     * session is invalidated by timeout or logout.
+     * @param request the servlet request
+     * @return <code>true</code> if the status has changed, <code>false</code>
+     * otherwise
+     */
+    protected final boolean isContainerStatusChanged( HttpServletRequest request )
+    {
+        if ( request == null || m_status.equals( AUTHENTICATED ) )
+        {
+            return false;
+        }
+
+        String remoteUser = request.getRemoteUser();
+        Principal userPrincipal = request.getUserPrincipal();
+        String cookieIdentity = CookieAssertionLoginModule.getUserCookie( request );
+        boolean changed = false;
+
+        // If request contains non-null remote user, update cached value if changed
+        if ( remoteUser != null && !remoteUser.equals( m_cachedRemoteUser) )
+        {
+            m_cachedRemoteUser = remoteUser;
+            log.info( "Remote user changed to " + remoteUser );
+            changed = true;
+        }
+
+        // If request contains non-null user principal, updated cached value if changed
+        if ( userPrincipal != null && !userPrincipal.equals( m_cachedUserPrincipal ) )
+        {
+            m_cachedUserPrincipal = userPrincipal;
+            log.info( "User principal changed to " + userPrincipal.getName() );
+            changed = true;
+        }
+
+        // If cookie identity changed (to a different value or back to null), update cache
+        if ( ( cookieIdentity != null && !cookieIdentity.equals( m_cachedCookieIdentity ) )
+               || ( cookieIdentity == null && m_cachedCookieIdentity != null ) )
+        {
+            m_cachedCookieIdentity = cookieIdentity;
+            log.info( "Cookie changed to " + cookieIdentity );
+            changed = true;
+        }
+        return changed;
+    }
+
+    /**
+     * Injects GroupPrincipal and Role objects into the user's Principal set
+     * based on the groups and roles the user belongs to.
+     * For Roles, the algorithm first calls the
+     * {@link Authorizer#getRoles()} to obtain the array of
+     * Principals the authorizer knows about. Then, the method
+     * {@link Authorizer#isUserInRole(WikiSession, Principal)} is
+     * called for each Principal. If the user possesses the role,
+     * an equivalent role Principal is injected into the user's
+     * principal set.
+     * Reloads user Principals into the suppplied WikiSession's Subject.
+     * Existing Role principals are preserved; all other Principal
+     * types are flushed and replaced by those returned by
+     * {@link com.ecyrd.jspwiki.auth.user.UserDatabase#getPrincipals(String)}.
+     * This method should generally be called after a user's {@link com.ecyrd.jspwiki.auth.user.UserProfile}
+     * is saved. If the wiki session is null, or there is no matching user profile, the
+     * method returns silently.
+     */
+    protected final void injectRolePrincipals()
+    {
+        // Get the GroupManager and test for each Group
+        GroupManager manager = m_engine.getGroupManager();
+        Principal[] groups = manager.getRoles();
+        for ( int i = 0; i < groups.length; i++ )
+        {
+            if ( manager.isUserInRole( this, groups[i] ) )
+            {
+                m_subject.getPrincipals().add( groups[i] );
+            }
+        }
+
+        // Get the authorizer's known roles, then test for each
+        try
+        {
+            Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
+            Principal[] roles = authorizer.getRoles();
+            for ( int i = 0; i < roles.length; i++ )
+            {
+                Principal role = roles[i];
+                if ( authorizer.isUserInRole( this, role ) )
+                {
+                    String roleName = role.getName();
+                    if ( !Role.isReservedName( roleName ) )
+                    {
+                        m_subject.getPrincipals().add( new Role( roleName ) );
+                    }
+                }
+            }
+        }
+        catch ( WikiException e )
+        {
+            log.error( "Could not refresh role principals: " + e.getMessage() );
+        }
+    }
+
+    /**
+     * Adds Principal objects to the Subject that correspond to the
+     * logged-in user's profile attributes for the wiki name, full name
+     * and login name. These Principals will be WikiPrincipals, and they
+     * will replace all other WikiPrincipals in the Subject. <em>Note:
+     * this method is never called during anonymous or asserted sessions.</em>
+     */
+    protected final void injectUserProfilePrincipals()
+    {
+        // Copy all Role and GroupPrincipal principals into a temporary cache
+        Set<Principal> oldPrincipals = m_subject.getPrincipals();
+        Set<Principal> newPrincipals = new HashSet<Principal>();
+        for ( Principal principal : oldPrincipals )
+        {
+            if ( AuthenticationManager.isRolePrincipal( principal ) )
+            {
+                newPrincipals.add( principal );
+            }
+        }
+        String searchId = getUserPrincipal().getName();
+        if ( searchId == null )
+        {
+            // Oh dear, this wasn't an authenticated user after all
+            log.info("Refresh principals failed because WikiSession had no user Principal; maybe not logged in?");
+            return;
+        }
+
+        // Look up the user and go get the new Principals
+        UserDatabase database = m_engine.getUserManager().getUserDatabase();
+        if ( database == null )
+        {
+            throw new IllegalStateException( "User database cannot be null." );
+        }
+        try
+        {
+            UserProfile profile = database.find( searchId );
+            Principal[] principals = database.getPrincipals( profile.getLoginName() );
+            for (int i = 0; i < principals.length; i++)
+            {
+                Principal principal = principals[i];
+                newPrincipals.add( principal );
+            }
+
+            // Replace the Subject's old Principals with the new ones
+            oldPrincipals.clear();
+            oldPrincipals.addAll( newPrincipals );
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            // We will get here if the user has a principal but not a profile
+            // For example, it's a container-managed user who hasn't set up a profile yet
+            log.warn("User profile '" + searchId + "' not found. This is normal for container-auth users who haven't set up a profile yet.");
+        }
+    }
+
+    /**
+     * Updates the internally cached principals returned by {@link #getUserPrincipal()} and
+     * {@link #getLoginPrincipal()}. This method is called when the WikiSession receives
+     * the {@link com.ecyrd.jspwiki.event.WikiSecurityEvent#LOGIN_INITIATED} message.
+     */
+    protected final void updatePrincipals()
+    {
+        Set<Principal> principals = m_subject.getPrincipals();
+        m_loginPrincipal = null;
+        m_userPrincipal = null;
+        Principal wikinamePrincipal = null;
+        Principal fullnamePrincipal = null;
+        Principal otherPrincipal = null;
+
+        for( Principal currentPrincipal : principals )
+        {
+            if ( !( currentPrincipal instanceof Role || currentPrincipal instanceof GroupPrincipal ) )
+            {
+                // For login principal, take the first PrincipalWrapper or WikiPrincipal of type LOGIN_NAME
+                // For user principal take the  first WikiPrincipal of type WIKI_NAME
+                if ( currentPrincipal instanceof PrincipalWrapper )
+                {
+                    m_loginPrincipal = currentPrincipal;
+                }
+                else if ( currentPrincipal instanceof WikiPrincipal )
+                {
+                    WikiPrincipal wp = (WikiPrincipal) currentPrincipal;
+                    if ( wp.getType().equals( WikiPrincipal.LOGIN_NAME ) )
+                    {
+                        m_loginPrincipal = wp;
+                    }
+                    else if ( wp.getType().equals( WikiPrincipal.WIKI_NAME ) )
+                    {
+                        wikinamePrincipal = currentPrincipal;
+                        m_userPrincipal = wp;
+                    }
+                    else if ( wp.getType().equals( WikiPrincipal.FULL_NAME ) )
+                    {
+                        fullnamePrincipal = currentPrincipal;
+                    }
+                    else
+                    {
+                        otherPrincipal = currentPrincipal;
+                    }
+                }
+                else if ( otherPrincipal == null )
+                {
+                    otherPrincipal = currentPrincipal;
+                }
+            }
+        }
+        if ( otherPrincipal == null )
+        {
+            otherPrincipal = WikiPrincipal.GUEST;
+        }
+
+        // If no login principal, use wiki name, full name or other principal (in that order)
+        if ( m_loginPrincipal == null )
+        {
+            m_loginPrincipal = wikinamePrincipal;
+            if ( m_loginPrincipal == null )
+            {
+                m_loginPrincipal = fullnamePrincipal;
+                if ( m_loginPrincipal == null )
+                {
+                    m_loginPrincipal = otherPrincipal;
+                }
+            }
+        }
+
+        // If no user principal, use wiki name, full name or login principal (in that order)
+        if ( m_userPrincipal == null )
+        {
+            m_userPrincipal = wikinamePrincipal;
+            if ( m_userPrincipal == null )
+            {
+                m_userPrincipal = fullnamePrincipal;
+                if ( m_userPrincipal == null )
+                {
+                    m_userPrincipal = m_loginPrincipal;
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>Returns the status of the wiki session as a text string. Valid values are:</p>
+     * <ul>
+     *   <li>{@link #AUTHENTICATED}</li>
+     *   <li>{@link #ASSERTED}</li>
+     *   <li>{@link #ANONYMOUS}</li>
+     * </ul>
+     * @return the user's session status
+     */
+    public final String getStatus()
+    {
+        return m_status;
+    }
+
+    /**
+     * <p>Static factory method that returns the WikiSession object associated with
+     * the current HTTP request. This method looks up the associated HttpSession
+     * in an internal WeakHashMap and attempts to retrieve the WikiSession. If
+     * not found, one is created. This method is guaranteed to always return a
+     * WikiSession, although the authentication status is unpredictable until
+     * the user attempts to log in. If the servlet request parameter is
+     * <code>null</code>, a synthetic {@link #guestSession(WikiEngine)}is returned.</p>
+     * <p>When a session is created, this method attaches a WikiEventListener
+     * to the GroupManager so that changes to groups are detected automatically.</p>
+     * @param engine the wiki engine
+     * @param request the servlet request object
+     * @return the existing (or newly created) wiki session
+     */
+    public static final WikiSession getWikiSession( WikiEngine engine, HttpServletRequest request )
+    {
+        // If request is null, return guest session
+        if ( request == null )
+        {
+            if ( log.isDebugEnabled() )
+            {
+                log.debug( "Looking up WikiSession for NULL HttpRequest: returning guestSession()" );
+            }
+            return staticGuestSession( engine );
+        }
+
+        // Look for a WikiSession associated with the user's Http Session
+        // and create one if it isn't there yet.
+        HttpSession session = request.getSession();
+        SessionMonitor monitor = SessionMonitor.getInstance( engine );
+        WikiSession wikiSession = monitor.find( session );
+
+        // Attach reference to wiki engine
+        wikiSession.m_engine = engine;
+
+        wikiSession.m_cachedLocale = request.getLocale();
+
+        return wikiSession;
+    }
+
+    /**
+     * Static factory method that creates a new "guest" session containing a single
+     * user Principal {@link com.ecyrd.jspwiki.auth.WikiPrincipal#GUEST},
+     * plus the role principals {@link Role#ALL} and
+     * {@link Role#ANONYMOUS}. This method also adds the session as a listener
+     * for GroupManager, AuthenticationManager and UserManager events.
+     * @param engine the wiki engine
+     * @return the guest wiki session
+     */
+    public static final WikiSession guestSession( WikiEngine engine )
+    {
+        WikiSession session = new WikiSession();
+        session.m_engine = engine;
+        session.invalidate();
+
+        // Add the session as listener for GroupManager, AuthManager, UserManager events
+        GroupManager groupMgr = engine.getGroupManager();
+        AuthenticationManager authMgr = engine.getAuthenticationManager();
+        UserManager userMgr = engine.getUserManager();
+        groupMgr.addWikiEventListener( session );
+        authMgr.addWikiEventListener( session );
+        userMgr.addWikiEventListener( session );
+
+        return session;
+    }
+
+    /**
+     *  Returns a static guest session, which is available for this
+     *  thread only.  This guest session is used internally whenever
+     *  there is no HttpServletRequest involved, but the request is
+     *  done e.g. when embedding JSPWiki code.
+     *
+     *  @param engine WikiEngine for this session
+     *  @return A static WikiSession which is shared by all in this
+     *          same Thread.
+     */
+    // FIXME: Should really use WeakReferences to clean away unused sessions.
+
+    private static WikiSession staticGuestSession( WikiEngine engine )
+    {
+        WikiSession session = c_guestSession.get();
+
+        if( session == null )
+        {
+            session = guestSession( engine );
+
+            c_guestSession.set( session );
+        }
+
+        return session;
+    }
+
+    /**
+     * Returns the total number of active wiki sessions for a
+     * particular wiki. This method delegates to the wiki's
+     * {@link SessionMonitor#sessions()} method.
+     * @param engine the wiki session
+     * @return the number of sessions
+     */
+    public static final int sessions( WikiEngine engine )
+    {
+        SessionMonitor monitor = SessionMonitor.getInstance( engine );
+        return monitor.sessions();
+    }
+
+    /**
+     * Returns Principals representing the current users known
+     * to a particular wiki. Each Principal will correspond to the
+     * value returned by each WikiSession's {@link #getUserPrincipal()}
+     * method. This method delegates to {@link SessionMonitor#userPrincipals()}.
+     * @param engine the wiki engine
+     * @return an array of Principal objects, sorted by name
+     */
+    public static final Principal[] userPrincipals( WikiEngine engine )
+    {
+        SessionMonitor monitor = SessionMonitor.getInstance( engine );
+        return monitor.userPrincipals();
+    }
+
+    /**
+     * Wrapper for
+     * {@link javax.security.auth.Subject#doAsPrivileged(Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)}
+     * that executes an action with the privileges posssessed by a
+     * WikiSession's Subject. The action executes with a <code>null</code>
+     * AccessControlContext, which has the effect of running it "cleanly"
+     * without the AccessControlContexts of the caller.
+     * @param session the wiki session
+     * @param action the privileged action
+     * @return the result of the privileged action; may be <code>null</code>
+     * @throws java.security.AccessControlException if the action is not permitted
+     * by the security policy
+     */
+    public static final Object doPrivileged( WikiSession session, PrivilegedAction action ) throws AccessControlException
+    {
+        return Subject.doAsPrivileged( session.m_subject, action, null );
+    }
+
+    /**
+     * Verifies whether a String represents an IPv4 address. The algorithm is
+     * extremely efficient and does not allocate any objects.
+     * @param name the address to test
+     * @return the result
+     */
+    protected static final boolean isIPV4Address( String name )
+    {
+        if ( name.charAt( 0 ) == DOT || name.charAt( name.length() - 1 ) == DOT )
+        {
+            return false;
+        }
+
+        int[] addr = new int[]
+        { 0, 0, 0, 0 };
+        int currentOctet = 0;
+        for( int i = 0; i < name.length(); i++ )
+        {
+            int ch = name.charAt( i );
+            boolean isDigit = ch >= ONE && ch <= NINE;
+            boolean isDot = ch == DOT;
+            if ( !isDigit && !isDot )
+            {
+                return false;
+            }
+            if ( isDigit )
+            {
+                addr[currentOctet] = 10 * addr[currentOctet] + ( ch - ONE );
+                if ( addr[currentOctet] > 255 )
+                {
+                    return false;
+                }
+            }
+            else if ( name.charAt( i - 1 ) == DOT )
+            {
+                return false;
+            }
+            else
+            {
+                currentOctet++;
+            }
+        }
+        return  currentOctet == 3;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AbstractActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AbstractActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AbstractActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AbstractActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,238 @@
+package com.ecyrd.jspwiki.action;
+
+import java.security.Principal;
+import java.util.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+import net.sourceforge.stripes.action.ActionBeanContext;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiSession;
+
+/**
+ * <p>Abstract ActionBean superclass for all wiki actions, such as page actions ({@link com.ecyrd.jspwiki.WikiContext} and subclasses),
+ * group actions (e.g., {@link ViewGroupActionBean}), user actions (e.g., {@link UserPreferencesActionBean}) and others.</p>
+ * 
+ * 
+ * @author Andrew Jaquith
+ */
+@WikiRequestContext("none")
+public abstract class AbstractActionBean implements WikiActionBean
+{
+    protected  Map<String,Object> m_variableMap = new HashMap<String,Object>();
+    
+    private WikiActionBeanContext m_actionBeanContext = null;
+
+    /**
+     * The JSP for this WikiContext.
+     */
+    private String m_skin = null;
+
+    private String m_template = null;
+    
+    private static final String DEFAULT_TEMPLATE = "default";
+    
+    private final String m_requestContext;
+    
+    /**
+     * Creates a new instance of this class, without a WikiEngine, Request or
+     * WikiPage.
+     */
+    protected AbstractActionBean()
+    {
+        super();
+        m_requestContext = getClass().getAnnotation( WikiRequestContext.class ).value();
+    }
+
+    /**
+     * Returns the content template for this ActionBean.
+     */
+    public final String getContentTemplate()
+    {
+        return this.getClass().getAnnotation(WikiRequestContext.class).value();
+    }
+
+    /**
+     * Returns the Stripes ActionBeanContext associated this WikiContext, lazily
+     * creating one if necessary.
+     * 
+     * @throws IllegalStateException
+     */
+    public WikiActionBeanContext getContext()
+    {
+        if (m_actionBeanContext == null)
+        {
+            setContext(new WikiActionBeanContext());
+        }
+        return m_actionBeanContext;
+    }
+
+    /**
+     * Convenience method that gets the current user. Delegates the lookup to
+     * the WikiSession associated with this WikiContect. May return null, in
+     * case the current user has not yet been determined; or this is an internal
+     * system. If the WikiSession has not been set, <em>always</em> returns
+     * null.
+     */
+    public Principal getCurrentUser()
+    {
+        return getWikiSession().getUserPrincipal();
+    }
+
+    /**
+     * Returns the WikiEngine, which may be <code>null</code> if this instance
+     * was created without invoking the WikiActionBeanContext methods
+     * {@link WikiActionBeanContext#setRequest(HttpServletRequest)} or
+     * {@link WikiActionBeanContext#setServletContext(javax.servlet.ServletContext)}.
+     */
+    public WikiEngine getEngine()
+    {
+        return getContext().getWikiEngine();
+    }
+
+    /**
+     * Returns the request context for this ActionBean by looking up the 
+     * value of the annotation {@link WikiRequestContext}. Note that if the
+     * annotation is not present, this method will return {@link ecyrd.jspwiki.WikiContext#NONE}.
+     */
+    public String getRequestContext()
+    {
+        return m_requestContext;
+    }
+    
+    /**
+     * Returns the "skin" to be used for this ActionBean.
+     * 
+     * @return the skin
+     */
+    public String getSkin()
+    {
+        return m_skin;
+    }
+
+    /**
+     * <p>
+     * Gets the template that is to be used throughout this request. The value
+     * returned depends on the whether the current HTTP request has supplied a
+     * custom skin or template name. In order of preference:
+     * </p>
+     * <ul>
+     * <li>The "skin", if set by {@link #setSkin(String)} or if the HTTP
+     * parameter <code>skin</code> was bound by Stripes</li>
+     * <li>The template, if set by {@link #setTemplate(String)} or if the HTTP
+     * parameter <code>template</code> was bound by Stripes</li>
+     * <li>The WikiEngine's default template, as returned by
+     * {@link WikiEngine#getTemplateDir()}</li>
+     * <li>The value <code>default</code></li>
+     * </ul>
+     * 
+     * @since 2.1.15.
+     */
+    public String getTemplate()
+    {
+        if ( m_skin != null ) 
+        {
+            return m_skin;
+        }
+        else if ( m_template != null )
+        {
+            return m_template;
+        }
+        else if ( getEngine() != null ) 
+        {
+            return getEngine().getTemplateDir();
+        }
+        return DEFAULT_TEMPLATE;
+    }
+
+    /**
+     * Returns the WikiSession associated with the context. This method is
+     * guaranteed to always return a valid WikiSession. If this context was
+     * constructed without an associated HttpServletRequest, it will return
+     * {@link WikiSession#guestSession(WikiEngine)}.
+     */
+    public WikiSession getWikiSession()
+    {
+        return getContext().getWikiSession();
+    }
+
+    /**
+     *  Gets a previously set variable.
+     *
+     *  @param key The variable name.
+     *  @return The variable contents.
+     */
+    public Object getVariable( String key )
+    {
+        return m_variableMap.get( key );
+    }
+
+    /**
+     *  Sets a variable.  The variable is valid while the WikiContext is valid,
+     *  i.e. while page processing continues.  The variable data is discarded
+     *  once the page processing is finished.
+     *
+     *  @param key The variable name.
+     *  @param data The variable value.
+     */
+    public void setVariable( String key, Object data )
+    {
+        m_variableMap.put( key, data );
+    }
+
+    /**
+     * Sets the Stripes ActionBeanContext associated with this WikiContext. It
+     * will also update the cached HttpRequest.
+     */
+    public void setContext(ActionBeanContext context)
+    {
+        m_actionBeanContext = ((WikiActionBeanContext) context);
+    }
+
+    /**
+     * Sets the skin to be used with this ActionBean. This value will override
+     * the template returned by {@link #getTemplate()}. Normally, this method
+     * is invoked by Stripes when binding request parameters to the ActionBean.
+     * 
+     * @param skin
+     *            the skin to use
+     */
+    public void setSkin(String skin)
+    {
+        m_skin = skin;
+    }
+
+    /**
+     * Sets the template to be used for this request.
+     * 
+     * @since 2.1.15.
+     */
+    public void setTemplate(String dir)
+    {
+        m_template = dir;
+    }
+    
+    /**
+     *  Locates the i18n ResourceBundle given.  This method interprets
+     *  the request locale, and uses that to figure out which language the
+     *  user wants.
+     *  @see com.ecyrd.jspwiki.i18n.InternationalizationManager
+     *  @param bundle The name of the bundle you are looking for.
+     *  @return A resource bundle object
+     *  @throws MissingResourceException If the bundle cannot be found
+     */
+    // FIXME: This method should really cache the ResourceBundles or something...
+    public ResourceBundle getBundle( String bundle ) throws MissingResourceException
+    {
+        Locale loc = null;
+
+        if( m_actionBeanContext != null && m_actionBeanContext.getRequest() != null )
+        {
+            loc = m_actionBeanContext.getRequest().getLocale();
+        }
+        ResourceBundle b = getEngine().getInternationalizationManager().getBundle(bundle, loc);
+
+        return b;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdminActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdminActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdminActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdminActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,12 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+
+@WikiRequestContext("admin")
+@UrlBinding("/admin/Admin.action")
+public class AdminActionBean extends WikiContext
+{
+    
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdministerProfilesActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdministerProfilesActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdministerProfilesActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AdministerProfilesActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,87 @@
+package com.ecyrd.jspwiki.action;
+
+import java.util.List;
+
+import net.sourceforge.stripes.action.*;
+import net.sourceforge.stripes.validation.EmailTypeConverter;
+import net.sourceforge.stripes.validation.Validate;
+import net.sourceforge.stripes.validation.ValidateNestedProperties;
+
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.WikiSecurityException;
+import com.ecyrd.jspwiki.auth.user.DefaultUserProfile;
+import com.ecyrd.jspwiki.auth.user.UserDatabase;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+
+/**
+ * Manages the administration of UserProfiles, from the Administer Profiles page. Receives a List
+ * of UserProfiles, which may include a new profile, and persists the changes. Also receives an
+ * Array of Strings (login names) for UserProfiles that are to be deleted, and deletes them.
+ *
+ * @author Andrew Jaquith
+ */
+@WikiRequestContext("adminProfiles")
+@UrlBinding("/AdministerProfiles.action")
+public class AdministerProfilesActionBean extends AbstractActionBean {
+    
+    private String[] m_deleteLoginNames;
+    private List<UserProfile> m_profiles;
+    
+    @ValidateNestedProperties ({
+        @Validate(field="loginName", required=true, minlength=3, maxlength=50),
+        @Validate(field="password", required=true, minlength=6, maxlength=128),
+        @Validate(field="fullName", required=true, minlength=3, maxlength=50),
+        @Validate(field="wikiName", required=true, minlength=3, maxlength=50),
+        @Validate(field="email", required=false, converter=EmailTypeConverter.class)
+    })
+
+    public String[] getDeleteLoginNames() { return m_deleteLoginNames; }
+    public void setDeleteLoginNames(String[] deleteLoginNames) { m_deleteLoginNames = deleteLoginNames; }
+
+    public List<UserProfile> getUserProfiles() { return m_profiles; }
+    
+    public void setUserProfiles(List<UserProfile> profiles) { this.m_profiles = profiles; }
+
+    @HandlesEvent("Save") @DefaultHandler
+    public Resolution saveChanges() throws WikiSecurityException {
+        UserDatabase db = super.getContext().getWikiEngine().getUserManager().getUserDatabase();
+
+        // Apply any changes to existing profiles (and create new ones)
+        for (UserProfile profile : m_profiles) {
+            
+            // Look up profile; create new if not found
+            UserProfile existingProfile;
+            try {
+                existingProfile = db.findByLoginName(profile.getLoginName());
+            }
+            catch (NoSuchPrincipalException e)
+            {
+                existingProfile = new DefaultUserProfile();
+            }
+
+            // Make changes to things that have changed
+            existingProfile.setLoginName(profile.getLoginName());
+            existingProfile.setFullname(profile.getFullname());
+            existingProfile.setEmail(profile.getEmail());
+            if (profile.getPassword() != null && profile.getPassword().length() > 0) {
+                existingProfile.setPassword(profile.getPassword());
+            }
+            db.save(existingProfile);
+        }
+
+        // Then, if the user checked anyone off to be deleted, delete them
+        if (m_deleteLoginNames != null) {
+            for (String loginName : m_deleteLoginNames) {
+                try {
+                    db.deleteByLoginName(loginName);
+                }
+                catch (NoSuchPrincipalException e)
+                {
+                    throw new WikiSecurityException(e.getMessage());
+                }
+            }
+        }
+
+        return new RedirectResolution("/AdministerProfiles.jsp");
+    }
+}
\ No newline at end of file

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AttachActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AttachActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AttachActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/AttachActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,20 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.HandlesEvent;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+
+@WikiRequestContext("att")
+@UrlBinding("/attach/")
+public class AttachActionBean extends WikiContext
+{
+    @HandlesEvent("upload")
+    @EventPermission(permissionClass=PagePermission.class,target="${page.qualifiedName}",actions=PagePermission.UPLOAD_ACTION)
+    public Resolution upload()
+    {
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/CommentActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/CommentActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/CommentActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/CommentActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,20 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.HandlesEvent;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+
+@WikiRequestContext("comment")
+@UrlBinding("/Comment.action")
+public class CommentActionBean extends WikiContext
+{
+    @HandlesEvent("comment")
+    @EventPermission(permissionClass=PagePermission.class, target="${page.qualifiedName}", actions=PagePermission.COMMENT_ACTION)
+    public Resolution comment()
+    {
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DeleteActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DeleteActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DeleteActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DeleteActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,20 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.HandlesEvent;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+
+@WikiRequestContext("del")
+@UrlBinding("/Delete.action")
+public class DeleteActionBean extends WikiContext
+{
+    @HandlesEvent("delete")
+    @EventPermission(permissionClass=PagePermission.class, target="${page.qualifiedName}", actions=PagePermission.DELETE_ACTION)
+    public Resolution delete()
+    {
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DiffActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DiffActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DiffActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/DiffActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,20 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.HandlesEvent;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+
+@WikiRequestContext("diff")
+@UrlBinding("/Diff.action")
+public class DiffActionBean extends WikiContext
+{
+    @HandlesEvent("diff")
+    @EventPermission(permissionClass=PagePermission.class, target="${page.qualifiedName}", actions=PagePermission.VIEW_ACTION)
+    public Resolution diff()
+    {
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EditActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EditActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EditActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EditActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,20 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.HandlesEvent;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.action.UrlBinding;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+
+@WikiRequestContext("edit")
+@UrlBinding("/Edit.action")
+public class EditActionBean extends WikiContext
+{
+    @HandlesEvent("edit")
+    @EventPermission(permissionClass=PagePermission.class, target="${page.qualifiedName}", actions=PagePermission.EDIT_ACTION)
+    public Resolution edit()
+    {
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/ErrorActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/ErrorActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/ErrorActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/ErrorActionBean.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,9 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.UrlBinding;
+
+@WikiRequestContext("error")
+@UrlBinding("/Error.action")
+public class ErrorActionBean extends AbstractActionBean
+{
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EventPermission.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EventPermission.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EventPermission.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/EventPermission.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,76 @@
+/**
+ * 
+ */
+package com.ecyrd.jspwiki.action;
+
+import java.lang.annotation.*;
+import java.security.Permission;
+
+/**
+ * <p>Permission annotation that specifies the the class, target and actions of a
+ * Permission required to execute a Stripes event method. Annotations are
+ * specified as follows:</p>
+ * <blockquote>
+ * <code>
+ * &#64;EventPermission(permissionClass="<var>className</var>",target="<var>target</var>",actions="<var>actions</var>")
+ * </code>
+ * </blockquote>
+ * </p>
+ * <p>The target and actions can be expression language (EL) expressions;
+ * if so, they must start with <code>${</code> and end with <code>}</code>.
+ * The expression is evaluated with the ActionBean as the "root." Thus, the expression
+ * <code>${foo}</code> evaluates to the value of the bean's <code>foo</code>
+ * property, typically as returned by an accessor such as <code>getFoo()</code>.
+ *  Mapped properties can also be used. For example, if the <code>Map</code>
+ *  property's accessor method <code>getBarMap()</code> returns a Map, the expression
+ * <code>${barMap['Alice']}</code> evaluates to the Map value whose key is
+ * <code>Alice</code>. <em>EL expressions that do not evaluate to valid values
+ * should be discarded by callers.</em></p>
+ * <p>When the target or actions parameter values are <em>not</em> EL expressions, they
+ * are interpreted as literals.</p>
+ * <p>For example, the following are all valid annotations:</p>
+ * <blockquote>
+ * <code>
+ * &#64;EventPermission(permissionClass="java.io.FilePermission", target="/tmp/-", actions="read,write")
+ * <br/><br/>
+ * &#64;EventPermission(permissionClass="com.ecyrd.jspwiki.auth.permissions.PagePermission", target="${page.name}", actions = "edit")
+ * <br/><br/>
+ * &#64;EventPermission(permissionClass="com.ecyrd.jspwiki.auth.permissions.GroupPermission", target="${context.request.parameterMap['group'][0]}",actions="view")
+ * </code>
+ * </blockquote>
+ * <p>These examples assume that the ActionBeans they annotate contain the appropriate properties;
+ * in this case, <code>page</code> is assumed to be a {@link com.ecyrd.jspwiki.WikiPage} property,
+ * and <code>context</code> is assumed to be a {@link com.ecyrd.jspwiki.action.WikiActionBeanContext}
+ * property.</p>
+ * <p>This Annotation class does not parse, process or instantiate Permissions; it merely specifies
+ * the syntax for annotating event handler methods. The collaborating class {@link EventPermissionInfo}
+ * actually does the heavy lifting.</p>
+ * @author Andrew Jaquith
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target( { ElementType.METHOD })
+@Documented
+@Inherited
+public @interface EventPermission
+{
+    /**
+     * The class of the Permission.
+     */
+    Class<? extends Permission> permissionClass();
+
+    /**
+     * The Permission target, supplied as a static String or as an EL
+     * expression.
+     * 
+     * @return the target
+     */
+    String target();
+
+    /**
+     * The Permission actions, supplied as a static String or as an EL
+     * expression.
+     * 
+     * @return the actions
+     */
+    String actions();
+}