Murray --

Ok, wanna turn the crank on this a little?

My thought, after reading what you wrote, is that we (meaning: you and I) could probably hammer out something that bridges 1) what you want, a nicer RESTish URL format, and 2) what I've been thinking about with all of the automatic bean-binding goodies that will come with JSPWiki 3.0, courtesy of Stripes.

1) STRIPES IN 60 SECONDS

First, Stripes in 60 seconds. The core unit of data in Stripes is the ActionBean. ActionBeans have properties (getters/setters) and events (methods specially annotated). ActionBeans are essentially fairly lightweight objects that model the range of activities you want to accomplish (using a form POST or AJAX, for example) -- these are the "verbs." The properties map cleanly to request parameters -- these are the nouns and adjectives. What Stripes does is inspect the request URL, and automatically wire up the right ActionBean, making sure that the "nouns and adjectives" are mapped to parameters, and that the right activity is executed.

Digging a little deeper... when the StripesFilter encounters a URL with a .action suffix, OR a JSP contains a stripes:useActionBean tag, the StripesFilter goes through a defined lifecycle that:

1) locates the and instantiates the correct ActionBean based on the .action URL or the bean class specified in the useActionBean tag

2) binds parameters passed in the request to properties (getters/ setters) in the ActionBean

3) calls the "event method" specified in the URL (validating the properties based on annotated instructions). If no event was specified, it executes the default event (generally forwarding to the "display JSP," like Wiki.jsp)

For example, suppose we post a URL:

Wiki.action?page=Foo?&_eventName=create

Stripes will inspect the ActionBeans it knows about. Suppose it finds one called PageActionBean that has a matching @URLBinding of Wiki.action. It knows, aha, I need to create a PageActionBean and stash it into the request as an attribute.

Next, it sees that there you supplied a parameter called "page", and it notes also that PageActionBean has a setPage(WikiPage) method. It will say, aha, we need to call setPage() with the value "Foo". Or more correctly, it will use its TypeConverter system to look up the WikiPage object corresponding to string "Foo" and set it. (I've written a TypeConverter for WikiPage already; very simple.)

Next, it sees the special parameter _eventName and its value "create", which is the event it needs to call. It will invoke the create() method, making sure to run any validation routines we've asked it to run (specified by the @Validate annotation on the bean properties). (Sidebar: I have written another annotation that expresses the Permissions the user must have to run the event.)

Lastly, the create() method will forward to Edit.jsp to display the wiki editor.

So far, that's pretty great, huh? We've gone through the entire bean resolution, binding and event execution lifecycle without writing any special code -- it's all done via simple annotations. Best of all, we get to rip out lots of crappy JSP scriptlet code.


2) WHAT ABOUT MURRAY'S CLEAN URLS?

Assuming you follow the logic here, you can see how the resolution/ binding/event lifecycle works in a straight-ahead, traditional URL format (e.g., path?param=value=&param=value).

Now, let's apply this to a "clean URL" scenario like the one you described:

http://murray.org/{subject}/{predicate}/ [optional "adjectives"]

Using our example, it might look like this:

/pages/Foo/?event=create

or maybe: /pages/Foo/create

This presents more of a challenge. We need some way of transforming the inbound URL into a normal request -- with normal parameters -- so that Stripes can locate the right bean, bind its properties correctly, etc. Specifically: it needs to somehow "know" that /pages meant "find the PageActionBean", and that /Foo meant "call setPage() with page Foo" and that /create meant, "call the 'create' event once you've done all that."

Originally I was thinking we'd need to write our own filter to do this, or use URLRewrite. It turns out some clever Stripes devs got there first. They call this the "clean URL" feature. It was initially a user contribution, but the Gregg and Tim liked it so much they put it into the trunk.

I have been watching the 1.5 builds, but TOTALLY forgot about this. Needless to day, it completely solves the problem, and it makes my previous comments about needing an external filter totally obsolete. Assuming you buy into the Stripes Way, the URL scheme you describe can be completely accommodated.

Anyway, the new Clean URL feature works like this (from the Stripes 1.5 Javadoc):

"Stripes supports "Clean URLs" through the UrlBinding annotation. Parameters may be embedded in the URL by placing the parameter name inside braces ({}). For example, @UrlBinding("/foo/{bar}/{baz}") maps the action to "/foo" and indicates that the "bar" and "baz" parameters may be embedded in the URL. In this case, the URL /foo/abc/123 would invoke the action with bar set to "abc" and baz set to "123". The literal strings between parameters can be any string. "The special parameter name $event may be used to embed the event name in a clean URL. For example, [EMAIL PROTECTED]("/foo/{$event}") the "bar" event could be invoked with the URL /foo/bar.

"Clean URL parameters can be assigned default values using the = operator. For example,@UrlBinding("/foo/{bar=abc}/{baz=123}"). If a parameter with a default value is missing from a request URL, it will still be made available as a request parameter with the default value. Default values are automatically embedded when building URLs with the Stripes JSP tags. The default value for $event is determined from the DefaultHandler and may not be set in the @UrlBinding.

"Clean URLs support both prefix mapping (/action/foo/{bar}) and extension mapping (/foo/{bar}.action). Any number of parameters and/or literals may be omitted from the end of a request URL."

You can see some addition comments on the development of the feature here:

http://www.stripesframework.org/jira/browse/STS-262

So-- to wrap up our previous example, the @URLBinding annotation for our PageActionBean class would look like this:

@URLBinding("/pages/{page=Main}/?event={$event}")

or:

@URLBinding("/pages/{page=Main}/{$event}")

Nice, huh? No coding needed.


3) OUTBOUND URLS

So far I've described how inbound URLs would be parsed and mapped. What about the outbound case (URL generation)?

We would need to write a custom URL constructor that might, for example, query the ActionBeans for the correct format, then format the URL according to that format. The Stripes URLBuilder class isn't suitable for that, because it just knows how to build URLs of the traditional sort. (Although the Stripes guys say they intend to make it match the @URLBinding format soon...)


4) THINKING ABOUT BEANS AND EVENTS

The difficult part of all this is figuring out how to map ActionBeans to what we understand today as WikiContexts, and to the URL scheme you have suggested. It all comes down to how you look at things:

- Do you select a page, then take an action on it? (view, edit, comment)
- Or do you "edit" something... and specify that that something is a particular page?

The URL scheme you suggested implies the first approach. Today's JSP scheme implies the second.

Personally, I like the "object" followed by "verb" idea (the first approach). It maps fairly cleanly to the Stripes ActionBean properties + event model. Thus, a hypothetical PageActionBean class ("the action bean you use to 'do stuff' with pages") might include the following properties and methods:

@URLBinding("@URLBinding("/pages/{page=Main}/{$event}")
class PageActionBean {
  WikiPage getPage()
  void setPage(WikiPage)
  int getVersion()
  void setVersion(int)
  void view()
  void edit()
  void comment()
}

The view, edit and comment methods are the events. These are also, at least in theory, "request contexts" as well. (It is easy to see how these would map to WikiContext.VIEW, .EDIT, and .COMMENT...)

Murray, assuming I have not bored the crap out of you, what do you think? Want to brainstorm some more about "nouns" (action beans) and "verbs" (events)? Got the full schema to share?

Andrew


On Jul 7, 2008, at 6:06 PM, Murray Altheim wrote:

Murray Altheim wrote:
[...]
In the above scheme we can obtain the base URL, the collection hierarchy and object identifier all without regex, just parsing the '/' delimiters via indexOf(). If we use the '?' as the delimiter between the absolute identifier of the intellectual entity (IE, to use the term we're using here locally), then everything after that is parseable either by going one '/' forward or backward, and everything else is a parameter available
via the HTTPServletRequest.

I didn't finish a thought (how unusual!).

The idea was that there's a subject, predicate and object embedded in
that URL, with the '?' acting as a delimiter or boundary of sorts:

http://www.acme.org/wiki/ pages/ PageName/ get/ ['?' optional params]
or
 http://www.acme.org/wiki/  pages/  PageName/ '?' action=get

abstracted as:

 subject/  predicate/ [optional "adjectives"]
or
 subject/ '?' predicate [optional "adjectives"]

which means parsing comes down to traveling one '/' from either the end
of the URL or from '?', if the latter exists. In either case I don't
think we need regex.

Murray

...........................................................................
Murray Altheim <murray07 at altheim.com> === = = http://www.altheim.com/murray/ = = === SGML Grease Monkey, Banjo Player, Wantanabe Zen Monk = = = =

     Boundless wind and moon - the eye within eyes,
     Inexhaustible heaven and earth - the light beyond light,
     The willow dark, the flower bright - ten thousand houses,
     Knock at any door - there's one who will respond.
                                     -- The Blue Cliff Record

Reply via email to