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/23 19:33:12 UTC

svn commit: r630500 [2/2] - /incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanContext.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanContext.java?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanContext.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanContext.java Sat Feb 23 10:33:08 2008
@@ -0,0 +1,253 @@
+package com.ecyrd.jspwiki.action;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.action.UrlBinding;
+import net.sourceforge.stripes.util.UrlBuilder;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiSession;
+
+/**
+ * {@link net.sourceforge.stripes.action.ActionBeanContext} subclass that
+ * contains a convenient reference to the current JSPWiki WikiEngine and the
+ * user's HttpServletRequest. The WikiEngine reference is lazily initialized
+ * when either {@link #setServletContext(ServletContext)} or
+ * {@link #setRequest(HttpServletRequest)} is invoked. The HttpServletRequest
+ * reference is set via {@link #setRequest(HttpServletRequest)}.
+ * 
+ * @author Andrew Jaquith
+ */
+public class WikiActionBeanContext extends ActionBeanContext
+{
+    private volatile WikiEngine m_engine = null;
+    private Map<Method,EventPermissionInfo> m_methodPermissions = new HashMap<Method,EventPermissionInfo>();
+
+    /**
+     * Sets the Permission metadata for an event handler method. If the metadata has already
+     * been set for the supplied hander, this method throws an {@linkplain IllegalArgumentException}.
+     * @param method the handler method
+     * @param permInfo the Permission metadata
+     */
+    public void addPermissionInfo(Method method, EventPermissionInfo permInfo)
+    {
+        if ( m_methodPermissions.containsKey(method))
+        {
+            throw new IllegalArgumentException("Already added permission info for " + method.getName());
+        }
+        m_methodPermissions.put(method,permInfo);
+    }
+    
+    /**
+     * Returns the Permission metdata for a supplied event handler method. If the
+     * handler does not have any metadata, this method will return <code>null</code>
+     * <em>and calling classes should check for this value</em>.
+     * @param method the handler method to look up
+     * @return the Permission metadata
+     */
+    public EventPermissionInfo getPermissionInfo(Method method)
+    {
+        return m_methodPermissions.get(method);
+    }
+    
+    public WikiSession getWikiSession()
+    {
+        return WikiSession.getWikiSession(m_engine, getRequest());
+    }
+
+    /**
+     * Sets the WikiEngine associated with this WikiActionBeanContext.
+     * 
+     * @param engine
+     *            the wiki engine
+     */
+    public void setWikiEngine(WikiEngine engine)
+    {
+        m_engine = engine;
+    }
+
+    /**
+     * Returns the WikiEngine associated with this WikiActionBeanContext.
+     * 
+     * @return the wiki engine
+     */
+    public WikiEngine getWikiEngine()
+    {
+        return m_engine;
+    }
+
+    /**
+     * Calls the superclass
+     * {@link ActionBeanContext#setRequest(HttpServletRequest)} and lazily sets
+     * the internal WikiEngine reference, if still <code>null</code>.
+     * 
+     * @param request
+     *            the HTTP request
+     */
+    @Override
+    public void setRequest(HttpServletRequest request)
+    {
+        super.setRequest(request);
+        if (request != null)
+        {
+            // Lazily set the WikiEngine reference
+            if (m_engine == null)
+            {
+                ServletContext servletContext = request.getSession().getServletContext();
+                m_engine = WikiEngine.getInstance(servletContext, null);
+            }
+        }
+        
+        // Retrieve the WikiSession, which ensures we log the user in (if needed)
+        WikiSession.getWikiSession( m_engine, request );
+    }
+
+    /**
+     * Calls the superclass
+     * {@link ActionBeanContext#setServletContext(ServletContext)} and lazily
+     * sets the internal WikiEngine reference, if still <code>null</code>.
+     * 
+     * @param servletContext
+     *            the servlet context
+     */
+    @Override
+    public void setServletContext(ServletContext servletContext)
+    {
+        super.setServletContext(servletContext);
+        if (m_engine == null)
+        {
+            WikiEngine engine = WikiEngine.getInstance(servletContext, null);
+            setWikiEngine(engine);
+        }
+    }
+
+    /**
+     * Returns the URL for accessing a named wiki page via a particular ActionBean, 
+     * with a set of appended parameters. The URL will be <em>not</em> be encoded via 
+     * the HttpServletResponse object, so callers that need URL re-writing should 
+     * use {@link WikiContext#getURL(Class, String, String, boolean)} instead. If the 
+     * ActionBean class is not a WikiContext subclass, the value of the 
+     * <code>page</code> parameter is ignored.
+     * @param beanClass the WikiActionBean subclass to use
+     * @param page the wiki page; if <code>null</code>, the front page will be used
+     * @param params the query parameters to append to the end of the URL; may be <code>null</code> if no parameters
+     * @param absolute If <code>true</code>, will generate an absolute URL regardless of properties setting.
+     * @return the URL
+     */
+    public String getURL( Class<? extends WikiActionBean> beanClass, String page, Map<String,String> params, boolean absolute )
+    {
+        // Set the page to the default (front page) if not supplied
+        if( page == null )
+        {
+            page = m_engine.getFrontPage();
+        }
+        
+        // Get the path prefix
+        String pathPrefix;
+        if ( absolute | m_engine.useAbsoluteUrls() )
+        {
+            pathPrefix = m_engine.getBaseURL();
+        }
+        else
+        {
+            pathPrefix = m_engine.getWikiActionBeanFactory().getPathPrefix();
+        }
+        boolean addParams = false;
+        
+        // Get the relative URL
+        String url;
+        if (AttachActionBean.class.equals(beanClass))              // Is this an attachment?
+        {
+            url = "attach/" + page;
+        }
+        else if (NoneActionBean.class.equals(beanClass))        // Is this a 'none' context?
+        {
+            url = page;
+        }
+        else                                                                                         // Otherwise, chop off the .action and add .jsp
+        {
+            url = beanClass.getAnnotation(UrlBinding.class).value();
+            if ( url.endsWith(".action") )
+            {
+                url = url.substring( 1, url.length() - 7 ) + ".jsp";
+            }
+            addParams = true;
+        }
+        
+        // Build the URL with any parameters we need
+        UrlBuilder urlBuilder = new UrlBuilder( pathPrefix + url, false );
+        
+        if ( addParams )
+        {
+            // If the ActionBean is a WikiContext, add a page parameter
+            if ( WikiContext.class.isAssignableFrom( beanClass ) && page != null )
+            {
+                urlBuilder.addParameter( "page", page + "" );
+            }
+            
+            // Append the other parameters
+            if ( params != null )
+            {
+                for ( Entry<String,String> param : params.entrySet() )
+                {
+                    urlBuilder.addParameter( param.getKey(), param.getValue() );
+                }
+            }
+        }
+        url = urlBuilder.toString();
+        url = StringUtils.replace( url, "+", "%20" );
+        url = StringUtils.replace( url, "%2F", "/" );
+        
+        // Encode the URL via the response object, if the response was set
+        if ( getResponse() != null )
+        {
+            return getResponse().encodeURL( url );
+        }
+        return url;
+    }
+    
+    /**
+     * Returns the URL for accessing a named wiki page via a particular ActionBean, 
+     * without parameters. The URL will be encoded via the HttpServletResponse object, 
+     * which means that any outbound filters will be able to transform 
+     * it as needed. The URL returned will be absolute if the WikiEngine
+     * was configured to return absolute URLs; otherwise, the URL will be
+     * relative to the webapp context root. If the ActionBean class is not a
+     * WikiContext subclass, the value of the <code>page</code> parameter is
+     * ignored.
+     * @param beanClass the WikiActionBean subclass to use
+     * @param page the wiki page; if <code>null</code>, the front page will be used
+     * @return the URL
+     */
+    public String getURL( Class<? extends WikiActionBean> beanClass, String page )
+    {
+        return getURL( beanClass, page, null, m_engine.useAbsoluteUrls() );
+    }
+
+    /**
+     * Returns the URL for accessing a named wiki page via a particular ActionBean, 
+     * without parameters. The URL will be encoded via the HttpServletResponse object, 
+     * which means that any outbound filters will be able to transform it as needed. 
+     * If the ActionBean class is not a WikiContext subclass, the value of the
+     * <code>page</code> parameter is ignored.
+     * @param beanClass the WikiActionBean subclass to use
+     * @param page the wiki page; if <code>null</code>, the front page will be used.
+     * @param params the query parameters to append to the end of the URL; may be <code>null</code> if no parameters
+     * @return the URL
+     */
+    public String getURL( Class<? extends WikiActionBean> beanClass, String page, Map<String,String> params )
+    {
+        return getURL( beanClass, page, params, m_engine.useAbsoluteUrls() );
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java Sat Feb 23 10:33:08 2008
@@ -0,0 +1,596 @@
+package com.ecyrd.jspwiki.action;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+
+import net.sourceforge.stripes.action.RedirectResolution;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.parser.MarkupParser;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.ui.WikiInterceptor;
+
+/**
+ * <p>
+ * Class that resolves special pages and JSPs on behalf of a WikiEngine.
+ * WikiActionBeanResolver will automatically resolve page names with
+ * singular/plural variants. It can also detect the correct Command based on
+ * parameters supplied in an HTTP request, or due to the JSP being accessed.
+ * </p>
+ * <p>
+ * <p>
+ * WikiActionBeanResolver's static {@link #findCommand(String)} method is the
+ * simplest method; it looks up and returns the Command matching a supplied wiki
+ * context. For example, looking up the request context <code>view</code>
+ * returns {@link PageCommand#VIEW}. Use this method to obtain static Command
+ * instances that aren't targeted at a particular page or group.
+ * </p>
+ * <p>
+ * For more complex lookups in which the caller supplies an HTTP request,
+ * {@link #findCommand(HttpServletRequest, String)} will look up and return the
+ * correct Command. The String parameter <code>defaultContext</code> supplies
+ * the request context to use if it cannot be detected. However, note that the
+ * default wiki context may be over-ridden if the request was for a "special
+ * page."
+ * </p>
+ * <p>
+ * For example, suppose the WikiEngine's properties specify a special page
+ * called <code>UserPrefs</code> that redirects to
+ * <code>UserPreferences.jsp</code>. The ordinary lookup method
+ * {@linkplain #findCommand(String)} using a supplied context <code>view</code>
+ * would return {@link PageCommand#VIEW}. But the
+ * {@linkplain #findCommand(HttpServletRequest, String)} method, when passed the
+ * same context (<code>view</code>) and an HTTP request containing the page
+ * parameter value <code>UserPrefs</code>, will instead return
+ * {@link WikiCommand#PREFS}.
+ * </p>
+ * 
+ * @author Andrew Jaquith
+ * @param <T>
+ * @since 2.4.22
+ */
+public final class WikiActionBeanFactory
+{
+    private static final Logger log = Logger.getLogger(WikiActionBeanFactory.class);
+    
+    private static final long serialVersionUID = 1L;
+
+    /** Prefix in jspwiki.properties signifying special page keys. */
+    private static final String PROP_SPECIALPAGE = "jspwiki.specialPage.";
+
+    /** Private map with JSPs as keys, Resolutions as values */
+    private final Map<String, RedirectResolution> m_specialRedirects;
+
+    private final WikiEngine m_engine;
+
+    /** If true, we'll also consider english plurals (+s) a match. */
+    private boolean m_matchEnglishPlurals;
+
+    /**
+     *  Contains the absolute path of the JSPWiki Web application without the
+     *  actual servlet; in other words, the absolute or relative path to
+     *  this webapp's root path. If no base URL is specified in
+     *  <code>jspwiki.properties</code>, the value will be an empty string.
+     */
+    private final String m_pathPrefix;
+
+    /**
+     * Constructs a WikiActionBeanResolver for a given WikiEngine. This
+     * constructor will extract the special page references for this wiki and
+     * store them in a cache used for resolution.
+     * 
+     * @param engine
+     *            the wiki engine
+     * @param properties
+     *            the properties used to initialize the wiki
+     */
+    public WikiActionBeanFactory(WikiEngine engine, Properties properties)
+    {
+        super();
+        m_engine = engine;
+        m_specialRedirects = new HashMap<String, RedirectResolution>();
+
+        // Skim through the properties and look for anything with
+        // the "special page" prefix. Create maps that allow us
+        // look up the correct ActionBean based on special page name.
+        // If a matching command isn't found, create a RedirectCommand.
+        for (Map.Entry entry : properties.entrySet())
+        {
+            String key = (String) entry.getKey();
+            if (key.startsWith(PROP_SPECIALPAGE))
+            {
+                String specialPage = key.substring(PROP_SPECIALPAGE.length());
+                String redirectUrl = (String) entry.getValue();
+                if (specialPage != null && redirectUrl != null)
+                {
+                    specialPage = specialPage.trim();
+                    redirectUrl = redirectUrl.trim();
+                    RedirectResolution resolution = m_specialRedirects.get(specialPage);
+                    if (resolution == null)
+                    {
+                        resolution = new RedirectResolution(redirectUrl);
+                        m_specialRedirects.put(specialPage, resolution);
+                    }
+                }
+            }
+        }
+
+        // Do we match plurals?
+        m_matchEnglishPlurals = TextUtil.getBooleanProperty(properties, WikiEngine.PROP_MATCHPLURALS, true);
+        
+        // Initialize the path prefix for building URLs
+        // NB this was stolen and adapted from the old URLConstructor code...
+        String baseurl = engine.getBaseURL();
+        String tempPath = "";
+        if( baseurl != null && baseurl.length() > 0 )
+        {
+            try
+            {
+                URL url = new URL( baseurl );
+                tempPath = url.getPath();
+            }
+            catch( MalformedURLException e )
+            {
+                tempPath = "/JSPWiki"; // Just a guess.
+            }
+        }
+        m_pathPrefix = tempPath;
+    }
+    
+    /**
+     * Returns the path prefix for building URLs. Should be deprecated as we move
+     * to Stripes for URL generation.
+     * @return
+     */
+    public String getPathPrefix()
+    {
+        return m_pathPrefix;
+    }
+
+    /**
+     * <p>
+     * Returns the correct page name, or <code>null</code>, if no such page
+     * can be found. Aliases are considered.
+     * </p>
+     * <p>
+     * In some cases, page names can refer to other pages. For example, when you
+     * have matchEnglishPlurals set, then a page name "Foobars" will be
+     * transformed into "Foobar", should a page "Foobars" not exist, but the
+     * page "Foobar" would. This method gives you the correct page name to refer
+     * to.
+     * </p>
+     * <p>
+     * This facility can also be used to rewrite any page name, for example, by
+     * using aliases. It can also be used to check the existence of any page.
+     * </p>
+     * 
+     * @since 2.4.20
+     * @param page
+     *            the page name.
+     * @return The rewritten page name, or <code>null</code>, if the page
+     *         does not exist.
+     */
+    public final String getFinalPageName(String page) throws ProviderException
+    {
+        boolean isThere = simplePageExists(page);
+        String finalName = page;
+
+        if (!isThere && m_matchEnglishPlurals)
+        {
+            if (page.endsWith("s"))
+            {
+                finalName = page.substring(0, page.length() - 1);
+            }
+            else
+            {
+                finalName += "s";
+            }
+
+            isThere = simplePageExists(finalName);
+        }
+
+        if (!isThere)
+        {
+            finalName = MarkupParser.wikifyLink(page);
+            isThere = simplePageExists(finalName);
+
+            if (!isThere && m_matchEnglishPlurals)
+            {
+                if (finalName.endsWith("s"))
+                {
+                    finalName = finalName.substring(0, finalName.length() - 1);
+                }
+                else
+                {
+                    finalName += "s";
+                }
+
+                isThere = simplePageExists(finalName);
+            }
+        }
+
+        return isThere ? finalName : null;
+    }
+
+    /**
+     * <p>
+     * If the page is a special page, this method returns a direct URL to that
+     * page; otherwise, it returns <code>null</code>.
+     * </p>
+     * <p>
+     * Special pages are non-existant references to other pages. For example,
+     * you could define a special page reference "RecentChanges" which would
+     * always be redirected to "RecentChanges.jsp" instead of trying to find a
+     * Wiki page called "RecentChanges".
+     * </p>
+     * TODO: fix this algorithm
+     */
+    public final String getSpecialPageReference(String page)
+    {
+        RedirectResolution resolution = m_specialRedirects.get(page);
+
+        if (resolution != null)
+        {
+            return resolution.getUrl();
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Creates a WikiActionBean instance, associates an HTTP request and
+     * response with it, and incorporates the correct WikiPage into the bean if
+     * required. This method will determine what page the user requested by
+     * delegating to
+     * {@link #extractPageFromParameter(String, HttpServletRequest)}.
+     * </p>
+     * <p>
+     * This method will <em>always</em>return a WikiActionBean that is
+     * properly instantiated. It will also create a new {@WikiActionBeanContext}
+     * and associate it with the action bean. The supplied request and response
+     * objects will be associated with the WikiActionBeanContext. All three
+     * parameters are required, and may not be <code>null</code>.
+     * </p>
+     * <p>
+     * This method performs a similar role to the &lt;stripes:useActionBean&gt;
+     * tag, in the sense that it will instantiate an arbitrary WikiActionBean
+     * class and, in the case of WikiContext subclasses, bind a WikiPage to it.
+     * However, it lacks some of the capabilities the JSP tag possesses. For
+     * example, although this method will correctly identity the page requested
+     * by the user (by inspecting request parameters), it will not do anything
+     * special if the page is a "special page." If special page resolution and
+     * redirection is required, use the &lt;stripes:useActionBean&gt; JSP tag
+     * instead.
+     * </p>
+     * 
+     * @param request
+     *            the HTTP request
+     * @param response
+     *            the HTTP request
+     * @param beanClass
+     *            the request context to use by default</code>
+     * @return the resolved wiki action bean
+     */
+    public WikiActionBean newActionBean(HttpServletRequest request, HttpServletResponse response,
+                                        Class<? extends WikiActionBean> beanClass) throws WikiException
+    {
+        if (request == null || response == null)
+        {
+            throw new IllegalArgumentException("Request or response cannot be null");
+        }
+
+        // Try creating a new ActionBean by looking up the request context
+        WikiActionBean bean = newInstance(beanClass);
+
+        // OK: we have the correct AbstractActionBean; inject into request scope
+        WikiActionBeanContext actionBeanContext = new WikiActionBeanContext();
+        actionBeanContext.setRequest(request);
+        actionBeanContext.setResponse(response);
+        actionBeanContext.setWikiEngine(m_engine);
+        bean.setContext(actionBeanContext);
+
+        // If the ActionBean is a WikiContext, extract and set the page (if not
+        // null)
+        if (bean instanceof WikiContext)
+        {
+            String page = extractPageFromParameter(request);
+
+            // For view action, default to front page
+            if (page == null && bean instanceof ViewActionBean)
+            {
+                page = m_engine.getFrontPage();
+            }
+            if (page != null)
+            {
+                WikiPage wikiPage = resolvePage(request, page);
+                ((WikiContext) bean).setPage(wikiPage);
+            }
+        }
+        return bean;
+    }
+
+    /**
+     * Creates a new ViewActionBean for the given WikiEngine, WikiPage and
+     * HttpServletRequest.
+     * 
+     * @param request
+     *            The HttpServletRequest that should be associated with this
+     *            context. This parameter may be <code>null</code>.
+     * @param response
+     *            The HttpServletResponse that should be associated with this
+     *            context. This parameter may be <code>null</code>.
+     * @param page
+     *            The WikiPage. If you want to create a WikiContext for an older
+     *            version of a page, you must supply this parameter
+     */
+    public WikiContext newViewActionBean(HttpServletRequest request, HttpServletResponse response, WikiPage page)
+    {
+        // Create a new WikiActionBean and set its sevlet context; this will set
+        // the WikiEngine too
+        WikiActionBeanContext context = new WikiActionBeanContext();
+        context.setServletContext(m_engine.getServletContext());
+
+        // If a request or response was passed along, set these references also
+        if (request != null)
+        {
+            context.setRequest(request);
+        }
+        if (response != null)
+        {
+            context.setResponse(response);
+        }
+
+        // Create a 'view' ActionBean and set the wiki page if passed
+        ViewActionBean bean = new ViewActionBean();
+        bean.setContext(context);
+
+        // If the page supplied was blank, default to the front page to avoid
+        // NPEs
+        if (page == null)
+        {
+            page = m_engine.getPage(m_engine.getFrontPage());
+
+            // Front page does not exist?
+            if (page == null)
+            {
+                page = new WikiPage(m_engine, m_engine.getFrontPage());
+            }
+        }
+
+        if (page != null)
+        {
+            bean.setPage(page);
+        }
+        return bean;
+    }
+
+    /**
+     * Create a new ViewActionBean for the given WikiPage.
+     * 
+     * @param page
+     *            The WikiPage. If you want to create a WikiContext for an older
+     *            version of a page, you must use this constructor.
+     */
+    public WikiContext newViewActionBean(WikiPage page)
+    {
+        return newViewActionBean(null, null, page);
+    }
+
+    /**
+     * <p>
+     * Determines the correct wiki page based on a supplied HTTP request. This
+     * method attempts to determine the page requested by a user, taking into
+     * account special pages. The resolution algorithm will extract the page
+     * name from the URL by looking for the first parameter value returned for
+     * the <code>page</code> parameter. If a page name was, in fact, passed in
+     * the request, this method the correct name after taking into account
+     * potential plural matches.
+     * </p>
+     * <p>
+     * If neither of these methods work, or if the request is <code>null</code>
+     * this method returns <code>null</code>
+     * </p>.
+     * 
+     * @param request
+     *            the HTTP request
+     * @return the resolved page name
+     */
+    protected final String extractPageFromParameter(HttpServletRequest request)
+    {
+        // Corner case when request == null
+        if (request == null)
+        {
+            return null;
+        }
+
+        // Extract the page name from the URL directly
+        String[] pages = request.getParameterValues("page");
+        String page = null;
+        if (pages != null && pages.length > 0)
+        {
+            page = pages[0];
+            try
+            {
+                // Look for singular/plural variants; if one
+                // not found, take the one the user supplied
+                String finalPage = getFinalPageName(page);
+                if (finalPage != null)
+                {
+                    page = finalPage;
+                }
+            }
+            catch (ProviderException e)
+            {
+                // FIXME: Should not ignore!
+            }
+            return page;
+        }
+
+        // Didn't resolve; return null
+        return page;
+    }
+
+    /**
+     * Given an instance of an ActionBean, this method returns a new instance of
+     * the same class.
+     * 
+     * @param beanClass
+     *            the bean class that should be newly instantiated
+     * @return the newly instantiated bean
+     */
+    protected WikiActionBean newInstance(Class<? extends WikiActionBean> beanClass) throws WikiException
+    {
+        if (beanClass != null)
+        {
+            try
+            {
+                return beanClass.newInstance();
+            }
+            catch (Exception e)
+            {
+                throw new WikiException("Could not create ActionBean: " + e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Looks up and returns the correct, versioned WikiPage based on a supplied
+     * page name and optional <code>version</code> parameter passed in an HTTP
+     * request. If the <code>version</code> parameter does not exist in the
+     * request, the latest version is returned.
+     * 
+     * @param request
+     *            the HTTP request
+     * @param page
+     *            the name of the page to look up; this page <em>must</em>
+     *            exist
+     * @return the wiki page
+     */
+    protected final WikiPage resolvePage(HttpServletRequest request, String page)
+    {
+        // See if the user included a version parameter
+        WikiPage wikipage;
+        int version = WikiProvider.LATEST_VERSION;
+        String rev = request.getParameter("version");
+
+        if (rev != null)
+        {
+            version = Integer.parseInt(rev);
+        }
+
+        wikipage = m_engine.getPage(page, version);
+
+        if (wikipage == null)
+        {
+            page = MarkupParser.cleanLink(page);
+            wikipage = new WikiPage(m_engine, page);
+        }
+        return wikipage;
+    }
+
+    /**
+     * Determines whether a "page" exists by examining the list of special pages
+     * and querying the page manager.
+     * 
+     * @param page
+     *            the page to seek
+     * @return <code>true</code> if the page exists, <code>false</code>
+     *         otherwise
+     */
+    protected final boolean simplePageExists(String page) throws ProviderException
+    {
+        if (m_specialRedirects.containsKey(page))
+        {
+            return true;
+        }
+        return m_engine.getPageManager().pageExists(page);
+    }
+
+    /**
+     * Returns the WikiActionBean associated with the current
+     * {@link javax.servlet.jsp.PageContext}, in request scope. The ActionBean
+     * will be retrieved from attribute {@link WikiInterceptor#ATTR_ACTIONBEAN}.
+     * 
+     * @param pageContext
+     *            the
+     * @return the WikiActionBean, or <code>null</code> if not found in the
+     *         current tag's PageContext
+     */
+    public static WikiActionBean findActionBean(PageContext pageContext)
+    {
+        return (WikiActionBean) pageContext.getAttribute(WikiInterceptor.ATTR_ACTIONBEAN, PageContext.REQUEST_SCOPE);
+    }
+
+    /**
+     * <p>
+     * Saves the supplied WikiActionBean and its associated WikiPage as
+     * PageContext attributes, in request scope. The action bean and wiki page
+     * are saved as attributes named {@link WikiInterceptor#ATTR_ACTIONBEAN} and
+     * {@link WikiInterceptor#ATTR_WIKIPAGE}. Among other things, by saving these items as
+     * attributes, they can be accessed via JSP Expression Language variables,
+     * in this case <code>${wikiActionBean}</code> and
+     * <code>${wikiPage}</code> respectively..
+     * </p>
+     * <p>
+     * Note: the WikiPage set by this method is guaranteed to be non-null. If
+     * the WikiActionBean is not a WikiContext, or it is a WikiContext but its
+     * WikiPage is <code>null</code>, the
+     * {@link com.ecyrd.jspwiki.WikiEngine#getFrontPage()} will be consulted,
+     * and that page will be used.
+     * </p>
+     * 
+     * @param pageContext
+     *            the page context
+     * @param actionBean
+     *            the WikiActionBean to save
+     */
+    public static void saveActionBean(PageContext pageContext, WikiActionBean actionBean)
+    {
+        // Stash the WikiActionBean
+        pageContext.setAttribute(WikiInterceptor.ATTR_ACTIONBEAN, actionBean, PageContext.REQUEST_SCOPE);
+    
+        // Stash the WikiPage
+        WikiPage page = null;
+        if (actionBean instanceof WikiContext)
+        {
+            page = ((WikiContext) actionBean).getPage();
+        }
+        if (page == null)
+        {
+            // If the page supplied was blank, default to the front page to
+            // avoid NPEs
+            WikiEngine engine = actionBean.getEngine();
+            page = engine.getPage(engine.getFrontPage());
+            // Front page does not exist?
+            if (page == null)
+            {
+                page = new WikiPage(engine, engine.getFrontPage());
+            }
+        }
+        if (actionBean instanceof WikiContext)
+        {
+            ((WikiContext) actionBean).setPage(page);
+        }
+        pageContext.setAttribute(WikiInterceptor.ATTR_WIKIPAGE, page, PageContext.REQUEST_SCOPE);
+    
+        // Debug messages
+        if (log.isDebugEnabled())
+        {
+            log.debug("Stashed WikiActionBean '" + actionBean + "' in page scope.");
+            log.debug("Stashed WikiPage '" + page.getName() + "' in page scope.");
+        }
+    
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java Sat Feb 23 10:33:08 2008
@@ -0,0 +1,24 @@
+/**
+ * 
+ */
+package com.ecyrd.jspwiki.action;
+
+import java.lang.annotation.*;
+
+/**
+ * WikiActionBean annotation that specifies the request context for an ActionBean (e.g., <code>edit</code>).
+ * @author Andrew Jaquith
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+@Inherited
+public @interface WikiRequestContext
+{
+    /**
+     * The request context for looking up an ActionBean.
+     * If not supplied and the class is a subclass of {@link AbstractActionBean}
+     * the superclass default value will be inherited ("none").
+     */
+    String value();
+}

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java Sat Feb 23 10:33:08 2008
@@ -0,0 +1,25 @@
+/**
+ * 
+ */
+package com.ecyrd.jspwiki.action;
+
+import java.lang.annotation.*;
+
+/**
+ * WikiActionBean annotation that specifies the pattern to use for URLs generated by 
+ * the {@link WikiActionBean#getURL()}.
+ * @author Andrew Jaquith
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+@Inherited
+public @interface WikiUrlPattern
+{
+    /**
+     * The request context for looking up an ActionBean.
+     * If not supplied and the class is a subclass of {@link AbstractActionBean}
+     * the superclass default value will be inherited ("none").
+     */
+    String value();
+}

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java Sat Feb 23 10:33:08 2008
@@ -0,0 +1,9 @@
+package com.ecyrd.jspwiki.action;
+
+import net.sourceforge.stripes.action.UrlBinding;
+
+@WikiRequestContext("workflow")
+@UrlBinding("/Workflow.action")
+public class WorkflowActionBean extends AbstractActionBean
+{
+}

Added: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html?rev=630500&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html Sat Feb 23 10:33:08 2008
@@ -0,0 +1,220 @@
+<html>
+<body>
+<p>Provides processing logic used by JSPs, using ActionBeans as used by the <a href="http://mc4j.org/confluence/display/stripes/Home">Stripes MVC framework</a>. In JSPWiki 3.0, the old JSP layer has been completely overhauled and simplified.</p>
+
+<h3>Overview of Stripes Features Used by JSPWiki 3.0</h3>
+<p>The Stripes MVC framework enforces separation of presentation and page markup (JSPs) from processing logic. 
+By "presentation" we mean anything that generates valid HTML or related markup, like cascading stylesheets. By "processing logic" we mean things like:</p>
+<ul>
+  <li>Creating wiki contexts</li>
+  <li>Extracting request parameters</li>
+  <li>Validating submitted form values</li>
+  <li>Looking, changing, or saving domain objects (<em>e.g.</em>, WikiPages, Groups, UserProfiles)</li>
+  <li>Request flow processing (<em>e.g.</em>, redirects)</li>
+  <li>Localization of resources</li>
+</ul>
+
+<p>In versions of JSPWiki prior to 3.0, JSPs did most of these things. In 3.0, JSPs do very little, and instead delegate most of the heavy lifting to the Stripes framework and to special JavaBeans called <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/ActionBean.html">ActionBeans</a>. These ActionBeans contain getters/setters that are used to store parameter values extracted from the HTTP request, and additional methods called <em>events</em> that correspond to the values that old-style JSPWiki templates stored in their "action" form parameters. In JSPWiki 3.0, we provide a subclass called <a href="../ui/WikiActionBean.html">WikiActionBean</a> includes accessors for things needed by JSPWiki, such as a {@linkplain com.ecyrd.jspwiki.action.WikiActionBean#getEngine()} method that returns the WikiEngine, and {@linkplain com.ecyrd.jspwiki.action.WikiActionBean#getWikiSession()} that returns the user's WikiSession.</p>
+
+<h3>Request parameter binding</h3>
+<p>Stripes includes a very cool, easy-to-understand technique for <a href="http://mc4j.org/confluence/display/stripes/Quick+Start+Guide">automatically populating WikiActionBeans</a> with submitted request parameters. Basically it boils down to this: if you have a request parameter that you want to map to a bean value, just create getters and setters that have same field name. Stripes will detect which parameters correspond to ActionBean fields, and will set the values automatically. For example, consider JSPWiki 2.<em>x</em>'s <code>Edit.jsp</code> that edits wiki pages. Page-related parameters supplied to this JSP include:</p>
+<ul>
+  <li><code>changenote</code></li>
+  <li><code>author</code></li>
+  <li><code>edittime</code></li>
+  <li><code>link</code></li>
+  <li><code>htmlPageText</code></li>
+</ul>    
+
+<p>These are parameters we need in order to save the wiki page correctly. Some of them are mandatory, and some aren't. In JSPWiki 3.0, responsibility for parsing and validating all of these parameters reside with Stripes. What happens is that when <code>Edit.jsp</code> is rendered, Stripes instantiates a corresponding WikiActionBean subclass called {@link com.ecyrd.jspwiki.action.EditActionBean} and automatically calls the correct accessors. In this example, the getters/setters look like this:</p>
+
+<pre>public String getChangenote() { ... }
+public void setChangenote(String note) { ... }
+
+public String getAuthor() { ... }
+public void setAuthor(String author) { ... }
+
+public String getEdittime() { ... }
+public void setEdittime(String time) { ... }
+
+public String getLink() { ... }
+public void setLink(String link) { ... }
+
+public String getHtmlPageText() { ... }
+public void setHtmlPageText(String text) { ... }</pre>
+
+<p>As you can see, there is a straight 1-to-1 binding between the getter/setter field names (<em>e.g.</em>, <code>getChangenote</code>/<code>setChangenote</code>) and request parameters (<code>changenote</code>). So, when the URL <code>Edit.jsp?changenote=Finished</code> is requested, Stripes invokes ViewActionBean's <code>setChangenote</code> method and passes the String "Finished". This is all done completely automatically -- there is no code to write.</p>
+
+<blockquote><em><strong>Guideline #1 for JSPWiki developers</strong>: processing code that extracts request parameters (that would have ordinarily gone into top-level JSPs) should </em>always<em> be moved into WikiActionBean getter and setter fields. For example, if the parameter <code>foo</code> is needed, add methods <code>setFoo</code> and <code>getFoo</code> to the ActionBean). The field types can be <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/validation/TypeConverter.html">any type that Stripes knows how to convert</a>, such as <code>int</code>, <code>long</code>, <code>Date</code>, <code>String</code>, <code>BigDecimal</code> and others, or JSPWiki-specific types we've built converters for: WikiPage (via {@link com.ecyrd.jspwiki.ui.WikiPageTypeConverter}), Group (via {@link com.ecyrd.jspwiki.ui.GroupTypeConverter}, and Principal (via {@link com.ecyrd.jspwiki.ui.PrincipalTypeConverter}).</em></blockquote>
+
+<h3>Bindings ActionBeans to URLs</h3>
+<p>All of this sounds great, but all of this assumes that somebody (us, Stripes, or God) knows how to bind the JSP <code>Edit.jsp</code> to our magic EditActionBean. How do we do this? Binding is done in two ways: manually in our JSPs by telling Stripes what bean to use, and automatically by Stripes when its servlet filter (StripesFilter) detects URLs that end in <code>.action</code>. JSPWiki uses both methods.</p>
+
+<p><strong>Manual binding via JSP markup</strong></p>
+<p>In JSPWiki 3.0, each top-level JSP is mapped to at least one, and usually just one, WikiActionBean. Each JSP declares what WikiActionBean it needs by including the following line:<p>
+<blockquote><code>&lt;<a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/useActionBean.html">stripes:useActionBean</a> beanclass="com.ecyrd.jspwiki.action.<em>foo</em>ActionBean"/&gt;</code></blockquote>
+<p>... where <em>foo</em> corresponds to a named action, such as <code>View</code> (for viewing wiki pages) and <code>UserProfile</code> (for editing a user's profile). For example, the page-editing JSP, <code>Edit.jsp</code>, contains this line:</p>
+
+<blockquote><code>&lt;stripes:useActionBean beanclass="com.ecyrd.jspwiki.action.EditActionBean"/&gt;</code></blockquote>
+
+<p>See this package for a complete list of WikiActionBeans used in JSPWiki 3.0. By convention, every top-level JSP in JSPWiki 3.0 <em>must</em> include a <code>&lt;stripes:useActionBean&gt;</code> tag at the top of their pages. This does three things: first, it guarantees that the specified WikiActionBean will be injected into into the PageContext's request scope with the well-known name {@link com.ecyrd.jspwiki.tags.WikiTagBase#ACTIONBEAN} ("wikiActionBean"). Second, it also means that JSTL expression language (EL) markup can access the WikiActionBean directly, for example, <code>${wikiActionBean.changenote}</code>. Third, it ensures that all of the request parameters we expect will be correctly bound to the WikiActionBean.</p>
+
+<p>In case you were wondering: conceptually, using <code>&lt;stripes:useActionBean&gt;</code> to instantiate WikiActionBeans is analogous to creating old-style "wiki contexts" JSPWiki versions prior to 3.0 using code like this:</p>
+
+<blockquote><code>WikiContext wikiContext = wiki.createContext( request, WikiContext.EDIT );</code></blockquote>
+
+<p>...although of course in 3.0 we do things via Stripes JSP tags rather than scriptlet code. WikiContexts aren't forgotten though; in fact, WikiContext is a <em>subclass</em> of WikiActionBean. That's right: we've refactored WikiContext so that it is now an ActionBean in its own right, and is the parent superclass of all of page-related WikiActionBeans, like {@link com.ecyrd.jspwiki.action.ViewActionBean}, {@link com.ecyrd.jspwiki.action.EditActionBean}, {@link com.ecyrd.jspwiki.action.PageInfoActionBean} and many others. If you've been paying attention, this means JSP markup can use and evaluate expressions like this:</p>
+
+<blockquote><code>${wikiActionBean.wikiSession.loginName}</code></blockquote>
+
+<p>EL syntax can be used to navigate much more complicated object graphs than this, but you get the idea. What could be easier?</p>
+
+<blockquote><em><strong>Guideline #2 for JSPWiki developers</strong>: JSPs should not attempt to instantiate WikiContexts directly. Instead, they </em>must<em> include a <code>&lt;stripes:useActionBean beanclass="com.ecyrd.jspwiki.action.</em>foo<em>Bean"/&gt;</code> element that tells JSPWiki which WikiActionBean (generally, a WikiContext subclass) to use. For example, <code>&lt;stripes:useActionBean beanclass="com.ecyrd.jspwiki.action.EditActionBean"/&gt;</code> tells a JSPWiki to automatically instantiate the wiki context EditActionBean and place it in request scope.</em></blockquote> 
+
+<p><strong>Automatic binding to <code>.action</code> URLs</strong></p>
+<p>In addition to the manual method for binding WikiActionBeans to JSPs, Stripes also automatically binds ActionBeans to URLs that contain the <code>.action</code> suffix. When it detects such a URL, it tries to locate and bind the correct ActionBean by scanning for ActionBean subclasses whose names share the same prefix and whose suffixes are ActionBean or Action. For example, the URL <code>Group.action</code> causes Stripes to look for ActionBeans called <code>GroupActionBean</code> and <code>GroupAction</code>, and if a class with that name is found, Stripes will instantiate one of these and bind its fields to the request parameters.</p>
+
+<p>Stripes also attempts to locate ActionBeans by looking for a special class-level annotation, <code><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/UrlBinding.html">@UrlBinding</a></code>, that specifies the mapping. By convention, this is how we do things in JSPWiki 3.0. For example, the ViewActionBean class contains this annotation:</p>
+
+<blockquote><code>@UrlBinding("/Wiki.action")</code></blockquote>
+
+<p>This annotation guarantees that any time StripesFilter encounters the URL <code>/Wiki.Action</code>, an instance of ViewActionBean will be instantiated and injected into the request as an attribute; its fields will also be bound in the same way as the manual method.</p>
+
+<p>Automatic ActionBean binding is typically done upon form submission, because Stripes' form tags cause forms to be posted to <code>.action</code> URLs. For this reason, automatic binding also causes fields to be validated as well (more on this shortly).</p>
+
+<blockquote><em><strong>Guideline #3 for JSPWiki developers</strong>: every WikiActionBean subclass should contain a class-level <code>@UrlBinding</code> annotation that tells Stripes how to locate the bean when user submit forms.</em></blockquote> 
+
+<blockquote><em><strong>Guideline #4 for JSPWiki developers</strong>: JSPs can -- and should -- use JSP 2.0 EL syntax to access properties of the current WikiActionBean. JSPWiki guarantees that when the <code>&lt;useActionBean&gt;</code> tag is present, the ActionBean will be made available as the page attibute <code>wikiActionBean</code>. For example, <code>${wikiActionBean.wikiSession.loginName}</code> prints the user's login name.</em></blockquote> 
+
+<h3>ActionBean Events</h3>
+<p>As discussed at the beginning of this page, Stripes ActionBeans includes getters and setters for parameters that are extracted from the request stream. ActionBeans also include methods that are annotated as "events," which respond to client activities like form POSTs. In JSPWiki 2.<em>x</em>, events were (very roughly) implemented using JSP scriptlet code. For example, consider this snippet from the old <code>UserPreferences.jsp</code>:</p>
+
+<pre>String action  = request.getParameter("action");
+if( "createAssertedName".equals(action) )
+{
+   ...
+   (event processing code goes here)
+   ...
+}</pre>
+
+<p>Scriptlets like these served only to clutter up JSPs with lots of spaghetti code. With Stripes, all event processing code is moved into ActionBean methods that have a special <code><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/HandlesEvent.html">@HandlesEvent</a></code> annotation. For example, the user preferences code that previously created user name assertions moved into an annotated <code>addAssertionCookie()</code> method that does the work by "handling" the event <code>createAssertedName</code>:</p>
+
+<pre>@HandlesEvent("createAssertedName")
+public Resolution addAssertionCookie()
+{
+    ...
+    (event processing code goes here)
+    ...
+    return new RedirectResolution("/");
+}</pre>
+
+<p>The effect of moving POST processing code into ActionBeans dramatically simplifies JSPs. But you might be wondering, how does Stripes know how (and when) it should call ActionBean event methods? Simple: a series of simple JSP tags, modeled after their HTML counterparts, tells Stripes what beans and events it should invoke. For example:</p>
+
+<pre>&lt;stripes:form id="preferences" action="/UserPreferences.action" method="POST" acceptcharset="UTF-8"&gt;
+  ...
+  (form markup)
+  ...
+  &lt;stripes:submit name="createAssertedName"/&gt;
+&lt;/stripes:form&gt;&lt;/pre&gt;</pre>
+
+<p>Here, the <code><a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/form.html">stripes:form</a></code> tag includes an <code>action</code> parameter that specifies the URL to post to. Because the {@link com.ecyrd.jspwiki.action.UserPreferencesActionBean} contains a class-level <code>@UrlBinding("/UserPreferences.action")</code> annotation, Stripes knows that this corresponds to that bean class UserPreferencesActionBean. And because the <code><a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/submit.html">stripes:submit</a></code> tag's <code>name</code> attribute contains the value <code>createAssertedName</code>, Stripes knows that it needs to locate and call the corresponding method that handles that event, in this case <code>addAssertionCookie</code>.
+
+<p>Note: an alternative syntax for &lt;stripes:form&gt; uses the <code>beanClass</code> attribute instead of <code>action</code>, which might be a little easier in some cases. This is functionally equivalent:</p>
+
+<pre>&lt;stripes:form id="preferences" action="/UserPreferences.action" method="POST" acceptcharset="UTF-8"&gt;</pre>
+
+<blockquote><em><strong>Guideline #5 for JSPWiki developers</strong>: processing code that handles form POST activities (that would have ordinarily gone into top-level JSPs) should </em>always<em> be moved into WikiActionBean event handler methods. These methods should contain a <code>@HandlesEvent</code> annotation that specifies which named event it handles. On JSPs, each event name should have an equivalent <code>&lt;stripes:submit&gt;</code> button; for example, <code>&lt;stripes:submit name="createAssertedName"/&gt;</code>. Its containing &lt;stripes:form&gt; element's <code>action</code> should contain the URL of the ActionBean the event pertains to, or alternative a <code>beanClass</code> attribute that names the ActionBean class explicitly. Example: <code>&lt;stripes:form beanClass="UserPreferencesAction.class"&gt;</code></em></blockquote> 
+
+<blockquote><em><strong>Guideline #6 for JSPWiki developers</strong>: all <code>&lt;form&gt;</code> tags and related markup (such as <code>&lt;input&gt;</code>, <code>&lt;textarea&gt;</code>, <code>&lt;option&gt;</code>) should use the Stripes tags instead (</em>e.g.<em>, <code>&lt;stripes:form&gt;</code>, <code><a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/text.html">&lt;stripes:text&gt;</a></code>, <code><a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/textarea.html">&lt;stripes:textarea&gt;</a></code>, <code><a href="http://stripes.sourceforge.net/docs/current/taglib/stripes/option.html">&lt;stripes:option&gt;</a></code>). With one or two exceptions, these tags are identical to their HTML equivalents, and contain a few extra attributes used by Stripes.</em></blockquote> 
+  
+<h3>Form validation</h3>
+<p>Because JSPWiki 3.0 uses Stripes, our WikiActionBeans take advantage of another terrific feature: automatic form field validation based on annotations. Recall previously that ActionBeans have getters and setters that Stripes uses to store and retrieve request parameters. For validation, either the getter or setter (by convention, the setter) can also contain a <code><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/validation/Validate.html">@validate</a></code> annotation that indicates whether values are required, and what their acceptable lengths or constraints might be. For example, consider {@link com.ecyrd.jspwiki.action.UserProfileActionBean}, a WikiActionBean that allows users to edit their user profile information. The <code>fullname</code> field has this annotation right above the setter:</p>
+
+<blockquote><code>@Validate(field="fullname", required=true, maxlength=100)</code></blockquote>
+
+<p>This annotation tells Stripes that the <code>fullname</code> field is a required field, and that its length must be less than 100 characters. When the form is POSTed, Stripes will check to see if this field was submitted by the user, and if not, automatically generate an error message that can be retrieved later. (By the <code>&lt;stripes:errors&gt;</code> tag, incidentally.) But Stripes can do more than simple text field validation. Here is the annotation for UserProfileActionBean's <code>email</code> field:</p>
+
+<blockquote><code>@Validate(field="email", required = false, converter = <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/validation/EmailTypeConverter.html">EmailTypeConverter.class</a>)</code></blockquote>
+
+<p>In this case, Stripes will use one of its own custom String converters to validate the e-mail address. In addition to these two validators (for Strings and e-mail addresses), Stripes also converts and validates dates, times, integers, longs, doubles, and <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/validation/TypeConverter.html">many other types</a>. It "knows" which converter to invoke because of the field's type. In addition to Stripes' own converters, JSPWiki 3.0 contains converters for fields of type WikiPage, Group and Principal.</p>
+
+<blockquote><em><strong>Guideline #7 for JSPWiki developers</strong>: When creating WikiActionBeans, all fields that require validation should have <code>@validate</code> annotations on their setter methods.</em></blockquote> 
+
+<h3>Pageflow and Redirection</h3>
+<p>Browser redirects and related pageflow issues are handled very differently in JSPWiki 3.0 than in previous versions. Previously, redirects were implemented directly in JSP scriptlet code. For example, consider this snippet from <code>Edit.jsp</code>:</p>
+
+<pre>if( change != null && change.getTime() != pagedate )
+{
+    //
+    // Someone changed the page while we were editing it!
+    //
+
+    log.info("Page changed, warning user.");
+
+    session.setAttribute( EditorManager.REQ_EDITEDTEXT, EditorManager.getEditedText(pageContext) );
+    response.sendRedirect( wiki.getURL(WikiContext.CONFLICT, pagereq, null, false) );
+    return;
+}</pre>
+
+<p>In JSPWiki 3.0, these activities fall into the category of "processing logic" -- the type of code that gets put into WikiActionBean events. In this particular case, the code moves into a Stripes event handler method in the EditActionBean class; its method annotations contains <code>@HandlesEvent("save")</code>.</p>
+
+<p>When a Stripes event handler method needs to redirect a user to another page, it returns an object called a <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/Resolution.html">Resolution</a> that tells Stripes how to proceed. Stripes contains four easy-to-use Resolutions:</p>
+
+<ul>
+  <li><strong><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/ForwardResolution.html">ForwardResolution</a></strong>: forwards the user to another path within the same web application using a server side forward. Constructors include:
+    <ul>
+      <li><code>ForwardResolution(Class<? extends ActionBean> beanType, String event)</code> - Constructs a ForwardResolution that will forward to the URL appropriate for the ActionBean supplied.</li>
+      <li><code>ForwardResolution(String path)</code> - Simple constructor that takes in the path to forward the user to.</li>
+    </ul>
+  </li>
+  <li><strong><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/ajax/JavaScriptResolution.html">JavaScriptResolution</a></strong>: converts a Java object web to a web of JavaScript objects and arrays, and stream the JavaScript back to the client. The output of this resolution can be evaluated in JavaScript using the eval() function, and will return a reference to the top level JavaScript object. Constructors include:
+    <ul>
+      <li><code>JavaScriptResolution(Object rootObject, Class<?>... userTypesExcluded)</code></li>
+    </ul>
+  </li>
+  <li><strong><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/RedirectResolution.html">RedirectResolution</a></strong>: redirects the user to another path by issuing a client side redirect.  Constructors include:
+    <ul>
+      <li><code>RedirectResolution(Class<? extends ActionBean> beanType)</code> - Constructs a RedirectResolution that will redirect to the URL appropriate for the ActionBean supplied.</li>
+      <li><code>RedirectResolution(Class<? extends ActionBean> beanType, String event)</code> - Constructs a RedirectResolution that will redirect to the URL appropriate for the ActionBean supplied.</li>
+      <li><code>RedirectResolution(String url)</code> - Simple constructor that takes the URL to which to forward the user.</li>
+    </ul>
+  </li>
+
+  <li><strong><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/StreamingResolution.html">StreamingResolution</a></strong>: streams data back to the client (in place of forwarding the user to another page). Designed to be used for streaming non-page data such as generated images/charts and XML islands.</li>
+</ul>
+
+<p>Handler events that need to interrupt processing or return data to the use simply need to return one of these four resolutions. For example, the previous scriptlet code could be re-written as an EditActionBean event handler as follows:
+
+<pre>
+@HandlesEvent("save")
+public Resolution save()
+{
+  if( m_change != null && m_change.getTime() != m_pagedate )
+  {
+      //
+      // Someone changed the page while we were editing it!
+      //
+  
+      log.info("Page changed, warning user.");
+      
+      Resolution r = new RedirectResolution(ConflictActionBean.class).flash( this );
+      r.addParameter("page", m_page.getName());
+      return r;
+  }
+}</pre>
+
+<p>The <code>RedirectResolution</code> tells Stripes to redirect the user to the conflict-editing page, the URL for which Stripes will automatically locate due to ConflictActionBean's <code>@UrlBinding</code> annotation (see Guideline #3). It will also append the parameter <code>page</code> with the value of the page. And finally it will add the current ActionBean to a temporary storage area called the <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/controller/FlashScope.html">flash scope</a> where it can be retrieved by the target of the redirect. When the target (in this case, ConflictActionBean) executes, it can retrieve the bean stored in flash scope easily:</p>
+
+<pre>FlashScope scope = FlashScope.getCurrent( request, true);
+WikiActionBean editContext = (WikiActionBean)scope.get( "/EditActionBean" );</pre>
+
+<p>After retrieving the previously-flashed EditActionBean, its contents can be retrieved and manipulated like any other bean.</p> 
+
+<blockquote><em><strong>Guideline #8 for JSPWiki developers</strong>: processing logic that would, in previous versions of JSPWiki, reside in scriptlet code should be moved into WikiActionBean event handlers. When an event handler needs to modify the user page flow or redirect the browser, it should return a suitable Resolution, such as the RedirectResolution.</blockquote> 
+
+<blockquote><em><strong>Guideline #9 for JSPWiki developers</strong>: client-side code that need to retrieve AJAX or JSON data from JSPWiki should POST to a WikiActionBean event handler, which should in turn return a <code>JavaScriptResolution</code> or a <code>StreamingResolution</code>.</blockquote> 
+
+<blockquote><em><strong>Guideline #10 for JSPWiki developers</strong>: event handlers that need to ensure that the current WikiActionBean is accessible by the next one in the request cycle should add themselves to "flash scope" by calling the Resolution's <code><a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/action/RedirectResolution.html#flash(net.sourceforge.stripes.action.ActionBean)">flash()</a></code> method. These can be retrieved by the next ActionBean by retrieving the current FlashScope for the request and calling its <code><a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/HashMap.html#get(java.lang.Object)">get()</a></code> method and passing the URLBinding as a parameter.</blockquote> 
+
+</body>
+</html>
\ No newline at end of file