Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for 
change notification.

The following page has been changed by ChrisLewis:
http://wiki.apache.org/tapestry/Tapestry5HowToCreateADispatcher

New page:
When Tapestry recieves a request for a resource (page, etc) a lot happens. 
Before reading this it would be prudent to first read about how requests are 
processed on the main page:

http://tapestry.apache.org/tapestry5/tapestry-core/guide/request.html

For dispatching and handling requests, Tapestry employs a chain of command 
implemented as the 
[http://tapestry.apache.org/tapestry5/tapestry-core/guide/request.html 
MasterDispatcher] service. The framework uses Tapestry IoC to configure itself, 
and therefore has a 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/services/TapestryModule.html
 TapestryModule] (just like your apps have an "AppModule"). Buried in this 
module is a method for configuring the MasterDispatcher service: 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/services/TapestryModule.html#contributeMasterDispatcher(org.apache.tapestry.ioc.OrderedConfiguration,%20org.apache.tapestry.services.ClasspathAssetAliasManager,%20org.apache.tapestry.internal.services.ResourceCache,%20org.apache.tapestry.internal.services.ResourceStreamer,%20org.apache.tapestry.services.PageRenderRequestHandler,%20org.apache.tapestry.services.ComponentActionRequestHandler,%20org.apache.tapestry.services.C
 omponentClassResolver,%20java.lang.String) 
TapestryModule#contributeMasterDispatcher]. I strongly recommend looking at the 
source of this file and especially at the contributions to the 
MasterDispatcher, as that is how I learned what I am about to share.

There are many reasons to intercept requests. Tapestry installs several 
dispatchers to intercept certain kinds of requests, in a certain order. The 
kind of configuration used by the MasterDispatcher 
([http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/ioc/OrderedConfiguration.html
 OrderedConfiguration]) allows us to insert a dispatcher anywhere in the chain, 
using the before:Id or after:Id syntax (again, read the source and api for more 
information on these).
I had a personal interest in this because I work with apps that, like many, 
require a login mechanism and an access control mechanism. Logging in is easy, 
its implementing the access controls that embodies the real work. In the past 
I've worked with frameworks that have a roughly similar paradigm of pages, and 
the only way to implement access controls without coding logic into each 
controller or page class, was to implement it in a common base class and 
subclass that. Well, times have changed. I don't like the fact that my pages 
know about access controls, it's none of their business. Tapestry 5 provides 
the infrastructure for a completely transparent access controller, and now 
we'll create a simple one to demonstrate the usefulness of both the 
configuration mechanism and the request processing pipeline in Tapestry 5.

When you look at the 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/services/TapestryModule.html#contributeMasterDispatcher(org.apache.tapestry.ioc.OrderedConfiguration,%20org.apache.tapestry.services.ClasspathAssetAliasManager,%20org.apache.tapestry.internal.services.ResourceCache,%20org.apache.tapestry.internal.services.ResourceStreamer,%20org.apache.tapestry.services.PageRenderRequestHandler,%20org.apache.tapestry.services.ComponentActionRequestHandler,%20org.apache.tapestry.services.ComponentClassResolver,%20java.lang.String)
 TapestryModule#contributeMasterDispatcher] method, you'll notice several 
dispatchers get installed. The ids of them are:

RootPath - Responsible for rendering the Start page when the app root is 
requested.
Asset - Handles serving assets from the classpath, context path, etc.
PageRender - Responsible for resolving and rendering pages, as the name 
suggests.
ComponentAction - Responsible for resolving event handlers for page and 
component actions.

These dispatchers are added and executed in this order. While it's certainly 
possible you might want to restrict other types of requests, I'd wager that 
most of the time developers need to protect pages, or entire sections of pages, 
from being accessed by unauthorized users.

Now that you have a decent understanding of what dispatchers are and how they 
work, you should realize that implementing a completely unobtrusive access 
control system is as easy as writing a Dispatcher. That part I assure you, is 
easy. The internal architecture of your system may very will be quite complex, 
but hooking it into Tapestry 5 is cake, and this is how you do it in a nutshell:

1) Implement a Dispatcher
This is a wonderfully brief interface with one method to implement: dispatch(). 
As expected, looking at the api/source for this interface, as well as some of 
the implementations will be helpful. For implementing an access controller, its 
essentially as easy as returning false or throwing an exception. Here's summary 
of the possible outcomes of this method:

1. Return true. This will tell Tapestry that the request has been handled and 
no other dispatcher in the chain need be consulted. If you return true from 
your access controller dispatcher, you're likely to get a blank response (and a 
blank page). This is because "handling" the request also means returning a 
response, like a page. So for an access controller, you won't do this.

2. Return false. This will tell tapestry that the request was not handled and 
the next dispatcher in the chain should be consulted. If your access controller 
determines that the user is allowed to access the page, return false.

3. Throw an exception. This will short circuit the execution process 
altogether, as any unhandled exception will. I believe this is the best way to 
deal with access violations. Of course this raises other questions to be dealt 
with in other articles, such as what page should be displayed, how to redirect 
to a login page, etc.

This is how the dispatch method works, so let's implement it. In reality you'll 
want to use an interface to specify the contract of the access controller, but 
for the sake of brevity, will skip that.

{{{
import java.io.IOException;

import org.apache.tapestry.services.Request;
import org.apache.tapestry.services.Response;

public class AccessController implements Dispatcher {
        public boolean dispatch(Request request, Response response) throws 
IOException {
                boolean canAccess = false;
                
                /*
                 * Access control logic goes here. If the user is allowed to 
access the
                 * resource, canAccess should be set to true.
                 */
                
                if(!canAccess) {
                        /*
                         * This is an unauthorized request, so throw an 
exception. We'll need
                         * more grace than this, such as a customized exception 
page and/or
                         * redirection to a login page...
                         */
                        throw new RuntimeException("Access violation!");
                }
                
                return false;
        }
}
}}}

Now that you've got a bridge to your access control logic that will fit in to 
Tapestry's request processing pipeline, how do you hook it in? Contribute it of 
course!

2) Contribute the Access Controller to the MasterDispatcher
(All of these methods must be in your AppModule.)
Before contributing you must first instantiate, or have Tapestry IoC 
instantiate your dispatcher implementation. How you do this will depend on how 
exactly you configure your dispatcher. Since we have a very dumb one that uses 
no dedicated interface (you do not want to bind to the Dispatcher interface), 
and it currently needs no initialization, we can use a binder. Add this to your 
bind method:

{{{
        public static void bind(ServiceBinder binder) {
                binder.bind(AccessController.class).withId("AccessController");
        }
}}}

Now we can inject our object using the id "AccessController" into virtually 
anything. What we need to do now is contribute it to the MasterDispatcher, so 
we'll inject it into the appropriate contribution method in our AppModule. 
Before we do that, remember that the MasterDispatcher is a chain of command, 
and that this chain is structured such that dispatchers are accessed in a 
specific order. This is good since we need to have our access controller 
positioned before certain dispatchers, like the page renderer, so we can 
effectively secure pages.
So how do we do this? First you will know from the api (or source) that the 
MasterDispatcher uses an OrderedConfiguration, and that such configurations 
allow constraints. These constraints allow you to add a new object before or 
after another, using the object's id and a simple syntax. You can look at the 
code for the syntax, but we know that we want our dispatcher to run before page 
requests are serviced. Page requests are handled by the dispatcher whose id is 
"PageRender", so here is the contribution we need in our AppModule:

{{{
    public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> 
configuration,
                @InjectService("AccessController") Dispatcher accessController) 
{
                configuration.add("AccessController", accessController, 
"before:PageRender");
        }
}}}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to