This post originated from an RSS feed registered with Java Buzz
by Chris Winters.
Original Post: More OI2 design fun: ActionResolvers and REST URLs
Feed Title: cwinters.com
Feed URL: http://www.cwinters.com/search/registrar.php?domain=jroller.com®istrar=sedopark
Feed Description: Chris Winters on Java, programming and technology, usually in that order.
OIN-135
says: "Allow REST-style URLs" and for what you'd think is a relatively
core change (how the application server gets data from the outside
world) it was a fairly simple change to make. I'll talk about the
first part today (pulling the data out); tomorrow we'll cover how
those data get assigned to actions.
First, an update on my earlier
post about resolving URLs to actions. Fortunately these changes worked out
exactly like I'd planned. Previously the controller instantiated the
action from the URL itself. Now it delegates the job to an
ActionResolver class which itself just asks each of a set of objects
collected at runtime if they can resolve the URL to an Action
object. First one to resolve wins. Classic
chain of
responsibility. So the controller now just does this:
my $action = OpenInteract2::ActionResolver->get_action( $request );
And in that method (condensed):
sub get_action {
my ( $class, $request ) = @_;
my $url = $request->url_relative;
my ( $action );
foreach my $r ( $class->get_all_resolvers ) {
$action = eval { $r->resolve( $request, $url ) };
if ( $@ ) {
$log->warn( "Resolver ", ref( $r ), " threw an ",
"exception ($@); continuing with others..." );
}
last if ( $action );
}
return $action;
}
So that call to resolve() in the foreach is where all the
work is getting done. Here's the initial version of the 'resolve()'
function for the workhorse link in the chain -- we grab the action
from the URL see if an action by that name exists. If so we assign the
task from the URL to it and return the object (condensed):
sub resolve {
my ( $self, $request, $url ) = @_;
my ( $action_name, $task_name ) = OpenInteract2::URL->parse( $url );
return unless ( $action_name );
my $action = eval {
CTX->lookup_action( $action_name )
};
if ( $@ ) {
$log->warn( "Caught exception from context trying to lookup ",
"action '$action_name': $@" );
return;
}
if ( $task_name ) {
$action->task( $task_name );
}
return $action
}
Pretty simple. Getting back to our original point: we now want to
add REST parameters. Since we've already done the work of decoupling
URL parsing and URL resolution, and URL resolution to action, the job
was actually pretty easy.
First, modify the code in OpenInteract2::URL->parse()
to return not only an 'action' and 'task' given a URL, but everything
else as parameters. (I won't show that here.) So given a URL
'http://foo/news/archive/2005/12' the URL parsing will break that down
into an action ('news'), task ('archive') and two parameters ('2005' and
'12').
So now modify our return values from parse() to
accommodate these parameters:
Here's another example of a resolver with the same functionality implemented.
OpenInteract2::ActionResolver::UserDir resolves URLs
like '/~cwinters/' so we don't have to use the uglier '/user/display/?user_id=5':
sub resolve {
my ( $self, $request, $url ) = @_;
return unless ( $url =~ m|^/?~| );
# cleanup url to known state
$url =~ s|^/||; $url =~ s/\?.*$//; $url =~ s|/$||;
my ( $username, $task, @params ) = split /\//, $url;
# /~user same as /~user/display
$task ||= 'display';
my $action = CTX->lookup_action( 'user' );
$action->task( $task );
$action->param( login_name => $username );
$self->assign_additional_params_from_url( $request, @params );
return $action;
}
Tomorrow: fine, now we've pulled the REST parameters out. What else
can we do with them?