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]
