This is a long post about ideas for JSPWiki 3.0 development. Brace yourself!

I concluded a while ago that the minute Janne signaled intent to move to Java 5, I'd take a serious look at an MVC framework I have long admired: Stripes. Janne previously stated that he wasn't interested in looking into MVC frameworks because they impose a learning curve. I suspect his reticence is also because some of the most popular ones (for example, Struts) all suck in various ways. Struts, for example, is very configuration-heavy and requires lots of seemingly-redundant classes to do just even little things. Creating that kind of overhead isn't something I think he wanted, and of course, I agree. But the current system JSPWiki uses — which does not use a framework — has some significant limitations, too.

Critique of Current JSPWiki MVC Model...#

The idea behind model-view-controller (MVC) is fairly well understood: separate the data structures (model) from the logic that orchestrates the user experience and data flow (controller), and also from the page presentation markup (view). Classic MVC says that the controller should be very smart, while the pages ought to be "pretty but stupid."

JSPWiki has long enforced separation of the model (WikiPages, UserDatabases etc.) from the other two layers. But the view and controller have, essentially, been merged together in the JSPs. The top-level JSPs (Wiki.jsp, Edit.jsp) etc. essentially comprise the controller layer; the various content JSPs (templates/../*Content.jsp) are the view layer.

This isn't a horrible arrangement, and indeed JSPWiki has flourished nicely without a true controller layer. However, it has meant that the top-level JSPs are really doing double-duty: they do controller-type tasks like parsing and validating request parameters, creating pages, etc., and they also serve as view "helpers" by loading presentation JSPs. Our JSPs both pretty AND smart. This makes for very messy top-level JSPs — lots of spaghetti code that is hard to debug. It also means that responsibility for issues like parameter-parsing and type-safety is devolved to the various scriptlet code chunks. These chunks rarely take advantage of common code — meaning that certain things like input sanitization need to be repeated again and again, assuming the JSP author remembers to do it. Indeed, the cross-site scripting error Janne just fixed in the ChangeNote field is an example of a security hole that was overlooked, albeit innocently. (Not trying to pin blame — just pointing out that each top-level JSP needs to think about quite a few things.)

None of these issues are fundamentally intractable, and could be fixed with dedicated effort. We could create some developer guidelines (and some agreed-upon "auditing principles") for examining through top-level JSPs to make sure we nail the security issues. This would enable us to keep the idea of using the top-level JSPs as the controller. We could also consolidate parameter parsing into a common library. And we could also move more security-specific functions into one of the servlet filters, for example the WikiServletFilter.

So there's nothing forcing JSPWiki to embrace an MVC framework. But I don't think the status quo won't work for much longer, for three reasons:

  • We need to build much more complex forms for things like administration; hand-rolling the input validation and parsing of request parameters won't cut it.
  • More dynamic functionality (enabled via AJAX and JSON) is both inevitable and desirable; that will require us to split "unit of work" logic away from the JSPs *anyhow*
  • It's not easy to write good, scalale, clever libraries for form processing, input validation and parameter parsing. Lots of people have done this before, so why not leverage their efforts?

Here's a concrete example, to illustrate these points. Imagine a user management screen that allows administrators to batch-register, view, enable/disable and modify all of the existing users in a wiki. Let's say there are 100 users in the wiki. For the JSP, we'd render five form controls for each user: their name, wikiname, password, enabled/disabled checkbox, and e-mail address. That's five hundred (500) form parameters to parse and deal with. No doubt we would have to write some serious custom code to manage it all — not to mention a lot of code to deal with sanity-checking the input to make sure it wasn't abusing state.

But why do that if you don't have to? Extending the example a little: suppose we wanted to make the admin form use sub-page requests (AJAX or JSON) to make changes instantaneously. We would need to deal with that, too.

Again — these issues aren't intractable. But I'd rather not re-invent the wheel. Thus, I am proposing that JSPWiki 3.0 implement the following high-level changes:

  • New MVC layer: Stripes
  • Replacement of Command classes with Stripes ActionBeans
  • Elimination of direct WikiContext instantiation, and turning WikiContext into a subclass
  • Simplification of top-level JSPs

What's notable are the changes I won't recommend. But first, I offer a short introduction to the most significant change, namely the introduction of Stripes.

Change #1: New MVC Layer — Stripes#

It may seem strange to acknowledge the shortcomings of specific MVC frameworks like Struts, then turn right around and suggest that we use a different one: Stripes. But contrasted against Struts, Stripes is a different animal. It uses Java 5 features like annotations, and favors "convention over configuration." That is, it has sensible defaults and is basically zero-configuration. The author has done a great job making the markup easy to understand. The documentation is very thorough. Moreover, the framework is small — only about 400k for two jars.

Stripes does many things very, very well. But the things that I like most are these:

  • No need to hard-code flow logic in a single, brittle file like struts-config.xml. Stripes uses reflection and annotations to find what it needs
  • Excellent form validation capabilities (expressed as annotations, such as @Validate(mask="A-Z+") or @Validate(maxlength=25)
  • Awesome type-conversion routines for passed form parameters, which convert them correctly into their domain types, e.g., integer, String, Date or arbitrary ones we define
  • Form parameters (e.g., 'group', 'page', 'version' getters/setters) sit right next to the business logic tasks ("events"), such as createUserProfile()
  • Nice, clean JSP markup that works just like normal HTML, except that it has a Stripes namespace (e.g., instead of the <form> element, it's <stripes:form>)

If you want a very fast introduction to how Stripes works, see the "Quick Start" guide, and start with "My First Stripe":

Of course, none of this functionality comes for free without a little re-factoring. So, I've been experimenting a bit to see how hard it would be. Seems pretty easy — all you do is create an ActionBean class that has the fields you want in it, plus the events you need. So, for the plain old "view page" action (Wiki.jsp), all we need is a simple class (which I called ViewActionBean) that has getPage/setPage methods. We can't "do" anything to the page directly (it's just for viewing), so there are no event methods.

Next, then we associate the ViewActionBean with Wiki.jsp with a single tag: <stripes:useActionBean>. This tag has a single parameter that specifies what action we want: in this case, View.action. When the user tries to view Wiki.jsp, Stripes "knows" it needs a ViewActionBean, instantiates it, looks for the page parameter, and if found, automatically invokes setPage() with the correct WikiPage. Note that neither the ViewActionBean "form bean" nor Wiki.jsp needs to devote a single line of code to dealing with the HttpServletRequest at all! Much simpler.

Now, with more complex action beans, like "EditActionBean" (Edit.jsp), we need to account for more things, such as to figure out where to post our changes. Today, we simply post back to Edit.jsp. But with Stripes, you use the <stripes:form> tag and post back to Edit.action. Stripes will look up the EditActionBean and invoke our handler method (for example, public void save()) based on what we told the submit button to do, in this case <stripes:submit name="Save">. The submit button's name is the same as our event handling method. Again, very clean, very easy to understand.

So, the tasks required to incorporate Stripes into the presentation layer is pretty simple:

  • Create an ActionBean subclass for each JSP form, such as Edit.jsp. The parameters we would normally parse in the scriptlet would be included in the ActionBean as getters/setters.
  • Move scriptlet code that handles "events" into ActionBean. So, code that does a "save group" operation could become an "event handler method" called save()
  • Add a single line to each top-level JSP to load the correct ActionBean (<stripes:useActionBean>)
  • Modify content template JSPs to use the stripes:* tags instead of their HTML equivalents (all that is needed is to simply prepend the stripes: prefix and change form POST destination and submit button parameters)
  • Add type converters for our custom model objects, like WikiPage, Group, UserProfile so that JSPWiki can look up and marshal these objects correctly when it parses request parameters.

Extra bonus: with a little twiddling (which I have already done in my local build), all of the ActionBeans are fully testable using mock objects. So we can test request parameters, event handlers etc. without using web unit tests like JWebUnit. (Although, of course, we can still use these too.)

Change #2: Replacement of Command Classes with ActionBeans#

Somewhere in the 2.4 timeframe I introduced something called a Command, which consolidated URL patterns, content templates, and wiki contexts into a single class: the Command. The idea was to eliminate the number of places where all of these things were hard-coded. Previously, DefaultURLConstructor defined all of the URL patterns, IncludeTag defined the content templates and WikiContext defined the request contexts. Commands consolidated these. The other thing the Command classes did was make each Command in charge of creating its own "required permission", instead of the massive if-then code that was bloating up WikiContext.

The Command classes were a good idea, because they consolidated a lot of related things into a single place. But they weren't the easiest things to understand or debug. They also didn't solve our hard-coding problem for things like the JSP templates — they just moved them around.

As noted previously, the core Stripes type is the ActionBean, which encapsulates both the form requirements (parameters and validation requirements) and its related business logic (via the event handler methods). ActionBeans do some of the things the Command classes do, in the sense that we need to know when to use them based on user privileges. Both ActionBeans and Commands can be thought of doing some work to "something", which in the Command classes I call the "target" and are simply fields in ActionBeans. ActionBeans are also like Commands because they are dynamically looked up at runtime based on the page (or certain parameters in the request).

So: in the interest of cleaning up the code, I'm recommending that we replace Commands with ActionBeans. This also means that CommandResolver would go away. It would be replaced by a new class called WikiActionBeanResolver, which would look up any ActionBeans that we need to locate manually, such as the RSS code that manually creates WikiContexts.

Change #3: WikiContext#

WikiContext is an interesting beast. It encapsulates a number of aspects about what a user is currently doing. Calling it "WikiRequest" would arguably do a better name capturing the essence of what it does, but that's neither here nor there. The point is that it is a busy class: it includes methods to generate URLs, methods to check whether users have correct permissions, methods to set the active template and skin and so forth.

WikiContext was originally designed just for page-related user actions — because that's all JSPWiki had. After I got involved and added functionality for user groups and wiki-wide actions, I added in additional code — to support Groups, for example. The Command classes were part of how that was done — by hanging a Command off of a given WikiContext, we were able to store additional attributes such as the active Group being edited.

This is all great, of course, but it means that in essence we "overloaded" WikiContext into doing things other than page-related activities. This led to ugly hacks like forcing the "page" to be Main, even if were were dealing with something that had nothing to do with pages, like Group creation. This isn't fatal, but it isn't very elegant.

I propose that we do two things with WikiContext: merge it with the Stripes ActionBean concept and make it a superclass for all page-related actions. This means that every page-related ActionBean, such as ViewActionBean or EditActionBean (for example), becomes a subclass of WikiContext. It also means that ActionBeans that do not need page-related methods don't need to have them. So, ViewGroupActionBean can have getGroup/setGroup methods, but wouldn't need getPage/setPage methods. In a way, this takes WikiContext back to its original purpose — for encapsulating information about page-related user requests.

A consequence of making WikiContext a superclass is that its public constructors go away in favor of factory methods or direct instantiation of WikiContext subclasses (i.e., ActionBeans). This isn't really a problem, because there are only about 30 or so calls to WikiContext constructors, and nearly all of them create WikiContexts that are just simple "view" contexts. Moreover, all of these are in core classes, and not in the JSPs. So people who create their own templates and JSPs won't be affected.

If you've been paying attention to the discussion on ActionBeans, you'll recall that ActionBeans take advantage of Stripes' request parsing and type conversion utilities. Thus, merging WikiContext with ActionBeans means that we get automatic population of WikiContext fields for free. So if the user passes a "skin" request parameter, it is set automatically for all WikiContexts by Stripes — which means we can eliminate the code that does that in JSPWiki. Ah, simplification!

In summary: making WikiContext a superclass for page-related ActionBeans unifies three ideas: WikiContext, ActionBean and Command. It will make things simpler.

Change #4: Simplification of Top-Level JSPs#

Recall that the Stripes ActionBean gives us automatic form parameter binding, form validation and parameter type conversion. Stripes knows "which" ActionBean to instantiate for a given JSP because we tell it which Action we want, right at the top. Example:
<stripes:useActionBean binding="/View.action">
This particular usage of <stripes:useActionBean> tells Stripes to look up the "View" action, which Stripes knows how to locate. It knows how to locate it because, by default, it will append "ActionBean" to the name of the action, and look for a class with that name. Once Stripes finds the ViewActionBean class, it instantiates it and adds it to the request as an attribute. Then it parses the form parameters, validates them if we want to, and passes control to the rest of the JSP.

Now, if we've let Stripes do all of the parameter parsing, and moved all of our event processing into the ActionBeans, what's left in the top-level JSP? Answer: not much! We don't need to call WikiEngine.createContext() because Stripes has already done the equivalent thing for us. We don't need to parse parameters, because Stripes has already done that, too. Really, the only thing the JSP needs is the <wiki:include> tag that fetches the content template. We might want to include the checkAccess() call that verifies the user has permission to see the page, but with a little work we can get rid of that too by doing a tiny bit of subclassing to a Stripes class called the ActionBeanContext.

The result of all this is that top-level JSPs become super simple: maybe 4 lines long. In the test branch that I'm working on now, here is the entire contents of Wiki.jsp:

<%@ taglib uri="/WEB-INF/jspwiki.tld" prefix="wiki" %>
<%@ taglib uri="" prefix="stripes" %>
<stripes:useActionBean binding="/View.action" />
<wiki:Include page="ViewTemplate.jsp" />

In fact, If you wanted to you could simply over-write the top-level JSPs with the default template *Content.jsp files. In other words, templates would go away because you could merge them with the top-level files. Our JSPs would become very, very stupid but stay pretty. If someone wanted to hack JSPWiki's look and feel, they could simply hack the top-level files — because that's all there would be. (Janne — I'm not advocating we go that far, just suggesting how far one could push the envelope if inclined.)

Summary of Changes (and Non-Changes)#

Changes for JSPWiki 3.0: Non-changes:
  • WikiEngine.createContext() remains, although it should be deprecated in favor of <stripes:useActionBean> tags
  • Although WikiContext constructors go away, the WikiContext type remains, and its methods still work the way they should

Current Status of Work#

  • Skeleton ActionBeans built for all existing WikiContexts (package: com.ecyrd.jspwiki.action)
  • Abstract superclass and interface built (AbstractActionBean/WikiActionBean)
  • Commands and CommandResolver have been completely ripped out
  • Proof of concept Wiki.jsp is done
  • Type converters for WikiPage, Group, WikiPrincipal done
  • Stripes JARs, startup parameters (modifications to web.xml) and support classes are done
  • Stripes mock objects for testing ActionBeans work nicely!
  • Where possible and useful, I've been slipping in Java 5 modifications, like parameterized Collections and the enhanced for-loop syntax.
  • Bumped up web.xml to use servlet spec 2.4

Things I Haven't Figured Out Yet#

  • How much of UserManager, GroupManager will become obsolete
  • How much of the Stripes templating system to recommend (they have something that works like Tiles, but much more simply...)
  • WikiServletFilter will see some changes; not sure how much...
  • WikiJSPFilter might see some changes; not sure how much...
  • How much of the Stripes AJAX/JSON support we want

Last But Not Least#

Blaise Pascal once wrote, "I have written you a long letter because I lacked the time to make it shorter." This is a huge post, and I'm sorry for that. I hope I was able to communicate clearly what I am proposing, why the changes are needed, and my progress to date.

Please post your critiques, suggestions and thoughts on this. I am trying very hard not to disrupt existing APIs (other than the few instances I've mentioned), while trying to move things forward with this. If Stripes makes you nervous, check out the Quick Start guide first, and I think you'll agree that it will really accelerate our ability to add functionality, once we get it integrated.

In the short term: I'd like to recommend that we set up a place where this stuff can go. Janne, do you think it's worth setting up a 3.0 branch in CVS?

I'm going to stick my project management hat on, and say that we should stabilize 2.6 first before starting serious work on 3.0. I don't particularly want to import *two* sets of bug fixes into the main branch every single time we make a fix, and I fear the development will totally get out of hand so that we will end up with two half-functional releases of code: 2.6 (which nobody cares about because it's so much cooler to work on 3.0), and 3.0, which will become the host of all the nice improvements people have been drooling at for the past year.

3.0 will also include a full redesign of the provider interface, based on JSR-170, so we will almost certainly have to rewrite quite a lot of the internals. And that means that we won't have the time to work on 2.6.

-- JanneJalkanen

And, actually, to add... Stripes certainly looks like a good MVC framework which embodies many of the design principles also behind JSPWiki: simplicity, clarity, no extra libraries, etc. I'll have to delve into it a bit deeper to get a coherent image, but it certainly looks like it could simplify our existing structure as opposed to making it more complicated (which is what Struts would do).

--JanneJalkanen, 29-Jan-2007

Janne -- let me know what the most pressing issues are that you'd like me to work on for 2.6. Happy to help, as you know. :)

--Andrew Jaquith, 29-Jan-2007

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-11) was last changed on 27-Jan-2008 20:31 by HarryMetske