incubator-jspwiki-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Andrew Jaquith <andrew.jaqu...@mac.com>
Subject WikiActionBeans & context factories (changed subject from ClassUtil thread)
Date Sun, 27 Jan 2008 05:31:58 GMT
I've been thinking very hard about WikiContext, factories, and Stripes  
for quite a while. Generally, factory classes are an extremely good  
idea. Comments inline and below...

On Jan 26, 2008, at 6:16 AM, Janne Jalkanen wrote:

> It sounds like something we should do for 3.0.
>
> Or, what we could do, is to migrate our own code to use  
> WikiContextFactory even earlier, but just deprecate the WikiContext  
> constructors.  That way people would get ample notification without  
> really breaking anything.

I do have concrete ideas for this. It is definitely a good idea to do  
away with WikiContext instantiation directly.

>   /** @since 3.0 */
>   public getContext( WikiPage page, ActionBeanContext ctx )  
> { ... } // Depending on which way Andrew was thinking about this -  
> maybe WikiContext should extend ActionBeanContext?  The interplay  
> must be thought out carefully so that embedding is still possible.
> }

(warning: LOTS of notes, not all of it organized, below)

ActionBeanContext captures information about the HTTP request: the  
request, response, context, validation errors, user's locale, etc.  
When exploring Stripes, my first instinct was to fuse WikiContext and  
ActionBeanContext together.

However, that proved to be an extremely brittle way to do things, and  
there were a lot of state-management issues that resulted. It  
unnecessarily tied servlet interface considerations (HTTP & request)  
to the "activity" that the WikiContext represented. It also made the  
API for a given WikiContext extremely inelegant and bloated. I  
concluded that it much nicer to leave all of the request/servlet stuff  
as its own "WikiActionBeanContext" class, which extends  
ActionBeanContext. WikiContexts are required, by contract, to set  
their parent WikiActionBeanContext at instantiation.  
WikiActionBeanContexts, by the way, also would set references to  
WikiEngine.

If you are familiar with the Stripes concepts, you'll guess from the  
preceding discussion that I'm recommending that we make WikiContext  
analogous to a regular Stripes ActionBean. The nice thing about  
regular old ActionBeans is that their APIs don't need to be very  
complicated: just getters/setters for form fields, and methods for  
"actions." Also the obligatory getActionBeanContext/ 
setActionBeanContext.

Now, as far as WikiActionBean and (wiki context) factories go, here  
are some detailed notes I made last June when I started getting into  
this in earnest. (Certain things are stated as "facts" even though  
they just reflected my thoughts at the time; in retrospect, they seem  
decent...) I've supplemented them a bit with some additional notes  
today.

Class Hierarchy
---------------
WikiContext is the all-singing, all-dancing "request context" class in  
JSPWiki. It encapsulates the idea of "doing a wiki activity": viewing  
a page, editing a group, commenting, etc. In Stripes parlance, these  
are what you'd call "ActionBeans."

WikiContext was originally conceived to encompass everything and  
everything you might want to do to a wiki page. In JSPWiki 2.4 and  
higher, WikiContext got overloaded with lots of activities that have  
nothing to do with pages, like creating groups and registering.

It has been clear for a while that WikiContext needed to split itself  
into multiple classes and interfaces. The most logical thing to do is  
make WikiContext the general class or interface actions that deal  
exclusively with pages. Therefore: WikiContext becomes an abstract  
subclass of AbstractActionBean.

Note that because WikiContext becomes abstract, we need to change all  
of the direct instantiations of WikiContext. So, what I've done is  
created a WikiContext subclass (a Stripes ActionBean, in fact) called  
ViewActionBean that is for the "view page" context.

The new class hierarchy for WikiContext and related types, for 3.0,  
looks like this:

     WikiActionBean extends ActionBean [Stripes]

     AbstractActionBean implements WikiActionBean
      |
      +--WikiContext extends AbstractActionBean
      |   |
      |   +--ViewActionBean extends WikiContext
      |   +--EditActionBean extends WikiContext
     ... ... ...
      +--GroupActionBean extends AbstractActionBean
      |
      +--UserPreferencesActionBean extends AbstractActionBean
     ...

[NOTE: it would be nice if we could deprecate "WikiContext" in favor  
of, for example, "PageActionBean" to clearly denote that it's about  
page activities. Maybe PageActionBean becomes the interface, and we  
start coding to that...]

The new class structure means that techniques for creating  
WikiContexts will change. Old instantiations of WikiContext looked  
like this:

WikiContext dummyContext = new WikiContext( m_engine,  
m_context.getPage() );

WikiContext dummyContext = new WikiActionBean( m_engine,  
m_context.getPage() );

In 3.0, if you want to create a new ViewActionBean you can do it via  
the zero-arg constructor [WikiContext context = new ViewActionBean()]  
-- although you must immedidately also call setActionBeanContext() and  
set the ActionBeanContext's WikiEngine reference. It's much easier,  
instead, to call the factory method  
WikiActionBeanFactory.newActionBean( WikiPage page), which does it for  
you (see below).

[NOTE: it might be better to make WikiActionBeans have protected  
constructors...]

Other significant changes to WikiContext:

- The setRequestContext() method disappears (it was only used in like  
5 places anyway), because the WikiContext subclasses *are* the  
contexts. getReqeustContext() merely looks up a special JSPWiki class- 
level annotation that specifies the context. (For example:  
@WikiRequestContext("view"))

- getCommand() goes away, as do the other Command-related methods.  
Command interface is eliminated. It was a pretty dodgy idea to begin  
with.

- getLocale(). Not used in JSPs or core code. Because Stripes takes  
care of a lot of this, it's not needed.

- hasAccess()/requiredPermission(). This are handled by our own  
Stripes interceptor/controller (WikiInterceptor) in combination with  
event-level annotations. So these methods should absolutely be  
eliminated.

WikiActionBean Contract
-----------------------
WikiActionBeans are the core action class in JSPWiki 3.0. All  
WikiActionBeans are expected to adhere to the following contract:
- must have a zero-argument constructor
- must set the ActionBeanContext immediately after instantiation  
(setContext)
- must set the associated ActionBeanContext's WikiEngine immediately  
after instantiation (setWikiEngine)

WikiActionBeans can be created in four ways:

1) Injection by UseActionBeanTag due to <stripes:useActionBean> tag in  
JSP. This is the preferred way because it can be done with little  
effort, and only requires single line at the top of a top-level JSP:

<stripes:useActionBean  
beanclass="com.ecyrd.jspwiki.action.____ActionBean"/>


Technically, the WikiActionBean is looked up by Stripes' configured  
ActionResolver (by default, the AnnotatedClassActionResolver); the  
bean itself is instantiated using its zero-argument constructor.  
Stripes guarantees that the following things happen after  
instantiation, as part of its ActionBeanResolution lifecycle stage:

- The WikiActionBean's setActionBeanContext() method is called (by  
AnnotatedClassActionResolver); this causes a reference to a new  
WikiActionBeanContext to be set in the WikiActionBean

- The WikiActionBeanContext's setServletContext() method is called (by  
UseActionBeanTag); this sets an internal reference to the WikiEngine

- The WikiActionBeanContext's setRequest/setResponse methods are  
called (by DefaultActionBeanContextFactory). This will also retrieve  
and set a reference to the user's WikiSession.

Thus, when <stripes:useActionBean> is used in a JSP, the  
WikiActionBean in question is guaranteed to have a non-null, valid  
WikiActionBeanContext associated with it. This WikiActionBeanContext,  
in turn, is guaranteed to return non-null results for its getResponse/ 
getRequest/getServletContext methods, as well as its getWikiEngine and  
getWikiSession methods.

2) Injection by DispatcherServlet due to POST/GET to /dispatcher, / 
action/* or *.action URLs. This method appears to be used, in  
particular, by generated Stripes form elements. It is rare that a user  
would actually specify one of these URLs directly.

As with the previous method, the WikiActionBean is looked up by  
Stripes' configured ActionResolver. The same guarantees apply: after  
resolution and instantiation, the WikiActionBean will have a non-null,  
valid WikiActionBeanContext, and that the WikiActionBeanContext will  
return non-null results for getResponse/getRequest/getServletContext/ 
getWikiEngine/getWikiSession.

In addition to these activities, in cases 1) and 2), after the  
ActionBeanResolution stage completes, JSPWiki's custom WikiInterceptor  
executes. It will do two things:

- Stash the resolved WikiActionBean into the HTTP request's ACTIONBEAN  
attribute (see above)

- Check for proper access by calling the the bean's  
requiredPermission() method and checking that the user has that  
permission (null means "allowed"). If not, a RedirectResolution is  
returned that directs the user to the login page and appends all  
request parameters. (Note: we should make it check for authentication;  
if the user is already logged in, it should redirect to a "forbidden"  
page.

These two techniques are the preferred ways to create WikiActionBeans.  
There are two other, less preferred, ways:

3)  
WikiActionBeanFactory 
.newActionBean(HttpServletRequest,HttpServletResponse,Class<? extends  
WikiActionBean>).

In this case, JSPWiki does things a little differently than how  
Stripes does it, but the result is the same. Like Stripes, it will  
instantiate a new WikiActionBean and associate it with a new  
WikiActionBeanContext.

4) WikiActionBeanFactory.newActionBean( HttpServletRequest request,  
WikiPage page) and
WikiActionBeanFactory.newActionBean( WikiPage page).

Both of these methods instantiate ViewActionBeans and associate a  
WikiActionContext with the bean. The WikiActionContext's setWikiEngine  
and setServletContext methods are called after instantiation. If an  
HttpServletRequest was supplied, it is associated with the  
WikiActionContext.

Thus, when the createContext() methods are used, the resulting  
ViewActionBean is guaranteed to have a non-null, valid  
WikiActionBeanContext associated with it. This WikiActionBeanContext,  
in turn, is guaranteed to return non-null results for its  
getServletContext/getWikiEngine methods. Its getRequest() method may  
or may not be null.

WikiActionBean Security
-----------------------
In 2.4, security permissions were added to WikiContexts. Each  
WikiContext could specify what Permission a user needed to access that  
context by causing the method requiredPermission() to return a value.  
This worked well, although it required each WikiContext type  
("view","edit" etc.) to figure out a way to express the Permission  
through code. This, in part, was what let to the disaster that is the  
Command class.

In 3.0, Permissions are much more flexible because they are annotation- 
driven. Moreover, they are specified at the method level, NOT at the  
class (WikiContext) level. Because WikiContext (and its subclasses)  
implement ActionBean, this is another way of saying, "Permissions  
annotate methods that do things." For example, consider  
ViewActionBean, which is a WikiContext subclass that displays a wiki  
page. Its default event is the "view()" method, which simply forwards  
control to itself and causes the page to display. Here's the method  
signature:

     @DefaultHandler
     @HandlesEvent("view")
     @EventPermission(
        permissionClass=PagePermission.class,
        target="${page.qualifiedName}",
        actions=PagePermission.VIEW_ACTION)
     public Resolution view() { ... }

Note the @EventPermission annotation. It defines the Permission class  
and its target and actions. The "permissionClass" attribute tells use  
that the Permission class this method needs is "PagePermission". Note  
also the JSTL-style syntax in the target and actions attributes --  
these allow JSTL-access to bean properties for the instantiated  
ViewActionBean. In this case, "${page}" is the bean attribute that  
returns the value of this ViewActionBean's getPage() method. The  
nested syntax "${page.qualifiedName}" is equivalent to  
getPage().getQualifiedName(). Neat, huh?

Mime
View raw message