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 Re: REST-ful URLs [Was: url rewriting supported?]
Date Tue, 08 Jul 2008 03:56:28 GMT
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, given@UrlBinding("/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


Mime
View raw message