jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r627255 [9/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/...
Date Wed, 13 Feb 2008 05:54:24 GMT
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiActionBeanFactory.java Tue Feb 12 21:53:55 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_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiRequestContext.java Tue Feb 12 21:53:55 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_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WikiUrlPattern.java Tue Feb 12 21:53:55 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_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/WorkflowActionBean.java Tue Feb 12 21:53:55 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_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/action/package.html Tue Feb 12 21:53:55 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

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/Attachment.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/Attachment.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/Attachment.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/Attachment.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,122 @@
+/*
+    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.attachment;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiPage;
+
+/**
+ *  Describes an attachment.  Attachments are actually derivatives of
+ *  a WikiPage, since they do actually have a WikiName as well.
+ *
+ *  @author Erik Bunn
+ *  @author Janne Jalkanen
+ */
+public class Attachment
+    extends WikiPage
+{
+    private String m_fileName;
+    private String m_parentName;
+    private boolean m_cacheable = true;
+
+    /**
+     *  Creates a new attachment.  The final name of the attachment will be 
+     *  a synthesis of the parent page name and the file name.
+     *  
+     *  @param engine     The WikiEngine which is hosting this attachment.
+     *  @param parentPage The page which will contain this attachment.
+     *  @param fileName   The file name for the attachment.
+     */
+    public Attachment( WikiEngine engine, String parentPage, String fileName )
+    {
+        super( engine, parentPage+"/"+fileName );
+
+        m_parentName = parentPage;
+        m_fileName   = fileName;
+    }
+
+    /**
+     *  Returns a human-readable, only-debugging-suitable description.
+     *  
+     *  @return A debugging string
+     */
+    public String toString()
+    {
+        return "Attachment ["+getName()+";mod="+getLastModified()+"]";
+    }
+
+    /**
+     *  Returns the file name of the attachment.
+     *  
+     *  @return A String with the file name.
+     */
+    public String getFileName()
+    {
+        return m_fileName;
+    }
+
+    /**
+     *  Sets the file name of this attachment. 
+     *  
+     *  @param name The name of the attachment.  Must be a legal file name without
+     *              the path.
+     */
+    public void setFileName( String name )
+    {
+        m_fileName = name;
+    }
+
+    /**
+     *  Returns the name of the parent of this Attachment, i.e. the page
+     *  which contains this attachment.
+     *  
+     *  @return String depicting the parent of the attachment.
+     */
+    public String getParentName()
+    {
+        return m_parentName;
+    }
+
+    /**
+     *  Returns true, if this attachment can be cached by the user agent.  By default
+     *  attachments are cacheable.
+     *  
+     *  @return False, if the attachment should not be cached by the user agent.
+     *  @since 2.5.34
+     */
+    public boolean isCacheable()
+    {
+        return m_cacheable;
+    }
+
+    /**
+     *  Sets this attachment to be cacheable or not.  This mostly concerns things
+     *  like DynamicAttachments, but it may be useful for certain AttachmentProviders
+     *  as well.
+     *  
+     *  @param value True or false, depending on whether you want this attachment
+     *               to be cacheable or not.
+     *  @since 2.5.34
+     */
+    public void setCacheable(boolean value)
+    {
+        m_cacheable = value;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,581 @@
+/*
+    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.attachment;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+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.providers.WikiAttachmentProvider;
+import com.ecyrd.jspwiki.util.ClassUtil;
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+
+/**
+ *  Provides facilities for handling attachments.  All attachment
+ *  handling goes through this class.
+ *  <p>
+ *  The AttachmentManager provides a facade towards the current WikiAttachmentProvider
+ *  that is in use.  It is created by the WikiEngine as a singleton object, and
+ *  can be requested through the WikiEngine.
+ *
+ *  @author Janne Jalkanen
+ *  @since 1.9.28
+ */
+public class AttachmentManager
+{
+    /**
+     *  The property name for defining the attachment provider class name.
+     */
+    public static final String  PROP_PROVIDER = "jspwiki.attachmentProvider";
+
+    /**
+     *  The maximum size of attachments that can be uploaded.
+     */
+    public static final String  PROP_MAXSIZE  = "jspwiki.attachment.maxsize";
+
+    /**
+     *  A space-separated list of attachment types which can be uploaded
+     */
+    public static final String PROP_ALLOWEDEXTENSIONS    = "jspwiki.attachment.allowed";
+
+    /**
+     *  A space-separated list of attachment types which cannot be uploaded
+     */
+    public static final String PROP_FORDBIDDENEXTENSIONS = "jspwiki.attachment.forbidden";
+
+    static Logger log = Logger.getLogger( AttachmentManager.class );
+    private WikiAttachmentProvider m_provider;
+    private WikiEngine             m_engine;
+
+    /**
+     *  Creates a new AttachmentManager.  Note that creation will never fail,
+     *  but it's quite likely that attachments do not function.
+     *  <p>
+     *  <b>DO NOT CREATE</b> an AttachmentManager on your own, unless you really
+     *  know what you're doing.  Just use WikiEngine.getAttachmentManager() if
+     *  you're making a module for JSPWiki.
+     *
+     *  @param engine The wikiengine that owns this attachment manager.
+     *  @param props  A list of properties from which the AttachmentManager will seek
+     *  its configuration.  Typically this is the "jspwiki.properties".
+     */
+
+    // FIXME: Perhaps this should fail somehow.
+    public AttachmentManager( WikiEngine engine, Properties props )
+    {
+        String classname;
+
+        m_engine = engine;
+
+
+        //
+        //  If user wants to use a cache, then we'll use the CachingProvider.
+        //
+        boolean useCache = "true".equals(props.getProperty( PageManager.PROP_USECACHE ));
+
+        if( useCache )
+        {
+            classname = "com.ecyrd.jspwiki.providers.CachingAttachmentProvider";
+        }
+        else
+        {
+            classname = props.getProperty( PROP_PROVIDER );
+        }
+
+        //
+        //  If no class defined, then will just simply fail.
+        //
+        if( classname == null )
+        {
+            log.info( "No attachment provider defined - disabling attachment support." );
+            return;
+        }
+
+        //
+        //  Create and initialize the provider.
+        //
+        try
+        {
+            Class providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
+                                                       classname );
+
+            m_provider = (WikiAttachmentProvider)providerclass.newInstance();
+
+            m_provider.initialize( m_engine, props );
+        }
+        catch( ClassNotFoundException e )
+        {
+            log.error( "Attachment provider class not found",e);
+        }
+        catch( InstantiationException e )
+        {
+            log.error( "Attachment provider could not be created", e );
+        }
+        catch( IllegalAccessException e )
+        {
+            log.error( "You may not access the attachment provider class", e );
+        }
+        catch( NoRequiredPropertyException e )
+        {
+            log.error( "Attachment provider did not find a property that it needed: "+e.getMessage(), e );
+            m_provider = null; // No, it did not work.
+        }
+        catch( IOException e )
+        {
+            log.error( "Attachment provider reports IO error", e );
+            m_provider = null;
+        }
+    }
+
+    /**
+     *  Returns true, if attachments are enabled and running.
+     *
+     *  @return A boolean value indicating whether attachment functionality is enabled.
+     */
+    public boolean attachmentsEnabled()
+    {
+        return m_provider != null;
+    }
+
+    /**
+     *  Gets info on a particular attachment, latest version.
+     *
+     *  @param name A full attachment name.
+     *  @return Attachment, or null, if no such attachment exists.
+     *  @throws ProviderException If something goes wrong.
+     */
+    public Attachment getAttachmentInfo( String name )
+        throws ProviderException
+    {
+        return getAttachmentInfo( name, WikiProvider.LATEST_VERSION );
+    }
+
+    /**
+     *  Gets info on a particular attachment with the given version.
+     *
+     *  @param name A full attachment name.
+     *  @param version A version number.
+     *  @return Attachment, or null, if no such attachment or version exists.
+     *  @throws ProviderException If something goes wrong.
+     */
+
+    public Attachment getAttachmentInfo( String name, int version )
+        throws ProviderException
+    {
+        if( name == null )
+        {
+            return null;
+        }
+
+        return getAttachmentInfo( null, name, version );
+    }
+
+    /**
+     *  Figures out the full attachment name from the context and
+     *  attachment name.
+     *
+     *  @param context The current WikiContext
+     *  @param attachmentname The file name of the attachment.
+     *  @return Attachment, or null, if no such attachment exists.
+     *  @throws ProviderException If something goes wrong.
+     */
+
+    public Attachment getAttachmentInfo( WikiContext context,
+                                         String attachmentname )
+        throws ProviderException
+    {
+        return getAttachmentInfo( context, attachmentname, WikiProvider.LATEST_VERSION );
+    }
+
+    /**
+     *  Figures out the full attachment name from the context and
+     *  attachment name.
+     *
+     *  @param context The current WikiContext
+     *  @param attachmentname The file name of the attachment.
+     *  @param version A particular version.
+     *  @return Attachment, or null, if no such attachment or version exists.
+     *  @throws ProviderException If something goes wrong.
+     */
+
+    public Attachment getAttachmentInfo( WikiContext context,
+                                         String attachmentname,
+                                         int version )
+        throws ProviderException
+    {
+        if( m_provider == null )
+        {
+            return null;
+        }
+
+        WikiPage currentPage = null;
+
+        if( context != null )
+        {
+            currentPage = context.getPage();
+        }
+
+        //
+        //  Figure out the parent page of this attachment.  If we can't find it,
+        //  we'll assume this refers directly to the attachment.
+        //
+        int cutpt = attachmentname.lastIndexOf('/');
+
+        if( cutpt != -1 )
+        {
+            String parentPage = attachmentname.substring(0,cutpt);
+            parentPage = MarkupParser.cleanLink( parentPage );
+            attachmentname = attachmentname.substring(cutpt+1);
+
+            // If we for some reason have an empty parent page name;
+            // this can't be an attachment
+            if(parentPage.length() == 0) return null;
+
+            currentPage = m_engine.getPage( parentPage );
+
+            //
+            // Go check for legacy name
+            //
+            // FIXME: This should be resolved using CommandResolver,
+            //        not this adhoc way.  This also assumes that the
+            //        legacy charset is a subset of the full allowed set.
+            if( currentPage == null )
+            {
+                currentPage = m_engine.getPage( MarkupParser.wikifyLink( parentPage ) );
+            }
+        }
+
+        //
+        //  If the page cannot be determined, we cannot possibly find the
+        //  attachments.
+        //
+        if( currentPage == null || currentPage.getName().length() == 0 )
+        {
+            return null;
+        }
+
+        // System.out.println("Seeking info on "+currentPage+"::"+attachmentname);
+
+        //
+        //  Finally, figure out whether this is a real attachment or a generated
+        //  attachment.
+        //
+        Attachment att;
+
+        att = getDynamicAttachment( currentPage.getName()+"/"+attachmentname );
+
+        if( att == null )
+        {
+            att = m_provider.getAttachmentInfo( currentPage, attachmentname, version );
+        }
+
+        return att;
+    }
+
+    /**
+     *  Returns the list of attachments associated with a given wiki page.
+     *  If there are no attachments, returns an empty Collection.
+     *
+     *  @param wikipage The wiki page from which you are seeking attachments for.
+     *  @return a valid collection of attachments.
+     *  @throws ProviderException If there was something wrong in the backend.
+     */
+
+    // FIXME: This API should be changed to return a List.
+    public Collection<Attachment> listAttachments( WikiPage wikipage )
+        throws ProviderException
+    {
+        if( m_provider == null )
+        {
+            return new ArrayList<Attachment>();
+        }
+
+        Collection<Attachment> atts = m_provider.listAttachments( wikipage );
+
+        //
+        //  This is just a sanity check; all of our providers return a Collection.
+        //
+        if( atts instanceof List )
+        {
+            Collections.sort( (List<Attachment>)atts );
+        }
+
+        return atts;
+    }
+
+    /**
+     *  Returns true, if the page has any attachments at all.  This is
+     *  a convinience method.
+     *
+     *
+     *  @param wikipage The wiki page from which you are seeking attachments for.
+     *  @return True, if the page has attachments, else false.
+     */
+    public boolean hasAttachments( WikiPage wikipage )
+    {
+        try
+        {
+            return listAttachments( wikipage ).size() > 0;
+        }
+        catch( Exception e ) {}
+
+        return false;
+    }
+
+    /**
+     *  Finds a (real) attachment from the repository as a stream.
+     *
+     *  @param att Attachment
+     *  @return An InputStream to read from.  May return null, if
+     *          attachments are disabled.
+     *  @throws IOException If the stream cannot be opened
+     *  @throws ProviderException If the backend fails due to some other reason.
+     */
+    public InputStream getAttachmentStream( Attachment att )
+        throws IOException,
+               ProviderException
+    {
+        return getAttachmentStream( null, att );
+    }
+
+    /**
+     *  Returns an attachment stream using the particular WikiContext.  This method
+     *  should be used instead of getAttachmentStream(Attachment), since it also allows
+     *  the DynamicAttachments to function.
+     *
+     *  @param ctx The Wiki Context
+     *  @param att The Attachment to find
+     *  @return An InputStream.  May return null, if attachments are disabled.  You must
+     *          take care of closing it.
+     *  @throws ProviderException If the backend fails due to some reason
+     *  @throws IOException If the stream cannot be opened
+     */
+    public InputStream getAttachmentStream( WikiContext ctx, Attachment att )
+        throws ProviderException, IOException
+    {
+        if( m_provider == null )
+        {
+            return null;
+        }
+
+        if( att instanceof DynamicAttachment )
+        {
+            return ((DynamicAttachment)att).getProvider().getAttachmentData( ctx, att );
+        }
+
+        return m_provider.getAttachmentData( att );
+    }
+
+    private Cache m_dynamicAttachments = new Cache( true, false, false );
+
+    /**
+     *  Stores a dynamic attachment.  Unlike storeAttachment(), this just stores
+     *  the attachment in the memory.
+     *
+     *  @param ctx A WikiContext
+     *  @param att An attachment to store
+     */
+    public void storeDynamicAttachment( WikiContext ctx, DynamicAttachment att )
+    {
+        m_dynamicAttachments.putInCache( att.getName(),  att );
+    }
+
+    /**
+     *  Finds a DynamicAttachment.  Normally, you should just use getAttachmentInfo(),
+     *  since that will find also DynamicAttachments.
+     *
+     *  @param name The name of the attachment to look for
+     *  @return An Attachment, or null.
+     *  @see #getAttachmentInfo(String)
+     */
+
+    public DynamicAttachment getDynamicAttachment( String name )
+    {
+        try
+        {
+            return (DynamicAttachment) m_dynamicAttachments.getFromCache( name );
+        }
+        catch( NeedsRefreshException e )
+        {
+            //
+            //  Remove from cache, it has expired.
+            //
+            m_dynamicAttachments.putInCache( name, null );
+
+            return null;
+        }
+    }
+
+    /**
+     *  Stores an attachment that lives in the given file.
+     *  If the attachment did not exist previously, this method
+     *  will create it.  If it did exist, it stores a new version.
+     *
+     *  @param att Attachment to store this under.
+     *  @param source A file to read from.
+     *
+     *  @throws IOException If writing the attachment failed.
+     *  @throws ProviderException If something else went wrong.
+     */
+    public void storeAttachment( Attachment att, File source )
+        throws IOException,
+               ProviderException
+    {
+        FileInputStream in = null;
+
+        try
+        {
+            in = new FileInputStream( source );
+            storeAttachment( att, in );
+        }
+        finally
+        {
+            if( in != null ) in.close();
+        }
+    }
+
+    /**
+     *  Stores an attachment directly from a stream.
+     *  If the attachment did not exist previously, this method
+     *  will create it.  If it did exist, it stores a new version.
+     *
+     *  @param att Attachment to store this under.
+     *  @param in  InputStream from which the attachment contents will be read.
+     *
+     *  @throws IOException If writing the attachment failed.
+     *  @throws ProviderException If something else went wrong.
+     */
+    public void storeAttachment( Attachment att, InputStream in )
+        throws IOException,
+               ProviderException
+    {
+        if( m_provider == null )
+        {
+            return;
+        }
+
+        m_provider.putAttachmentData( att, in );
+
+        m_engine.getReferenceManager().updateReferences( att.getName(),
+                                                         new java.util.Vector() );
+
+        WikiPage parent = new WikiPage( m_engine, att.getParentName() );
+        m_engine.updateReferences( parent );
+
+        m_engine.getSearchManager().reindexPage( att );
+    }
+
+    /**
+     *  Returns a list of versions of the attachment.
+     *
+     *  @param attachmentName A fully qualified name of the attachment.
+     *
+     *  @return A list of Attachments.  May return null, if attachments are
+     *          disabled.
+     *  @throws ProviderException If the provider fails for some reason.
+     */
+    public List getVersionHistory( String attachmentName )
+        throws ProviderException
+    {
+        if( m_provider == null )
+        {
+            return null;
+        }
+
+        Attachment att = getAttachmentInfo( (WikiContext)null, attachmentName );
+
+        if( att != null )
+        {
+            return m_provider.getVersionHistory( att );
+        }
+
+        return null;
+    }
+
+    /**
+     *  Returns a collection of Attachments, containing each and every attachment
+     *  that is in this Wiki.
+     *
+     *  @return A collection of attachments.  If attachments are disabled, will
+     *          return an empty collection.
+     *  @throws ProviderException If something went wrong with the backend
+     */
+    public Collection<Attachment> getAllAttachments()
+        throws ProviderException
+    {
+        if( attachmentsEnabled() )
+        {
+            return m_provider.listAllChanged( new Date(0L) );
+        }
+
+        return new ArrayList<Attachment>();
+    }
+
+    /**
+     *  Returns the current attachment provider.
+     *
+     *  @return The current provider.  May be null, if attachments are disabled.
+     */
+    public WikiAttachmentProvider getCurrentProvider()
+    {
+        return m_provider;
+    }
+
+    /**
+     *  Deletes the given attachment version.
+     *
+     *  @param att The attachment to delete
+     *  @throws ProviderException If something goes wrong with the backend.
+     */
+    public void deleteVersion( Attachment att )
+        throws ProviderException
+    {
+        if( m_provider == null ) return;
+
+        m_provider.deleteVersion( att );
+    }
+
+    /**
+     *  Deletes all versions of the given attachment.
+     *  @param att The Attachment to delete.
+     *  @throws ProviderException if something goes wrong with the backend.
+     */
+    // FIXME: Should also use events!
+    public void deleteAttachment( Attachment att )
+        throws ProviderException
+    {
+        if( m_provider == null ) return;
+
+        m_provider.deleteAttachment( att );
+
+        m_engine.getSearchManager().pageRemoved( att );
+
+        m_engine.getReferenceManager().clearPageEntries( att.getName() );
+
+    }
+}



Mime
View raw message