Using Resolvers in Induction - Part 1
This tutorial introduces resolvers in Induction. We discuss the purpose of resolvers and how the powerful built-in ShortURL resolvers work.
What is a Resolver?
A resolver is used to map an HTTP request to a controller (or view). A resolver is also used to map a redirect request specified in terms of a controller (or view) to a fully qualified URL (to be returned via the HTTP response). Induction delegates to three resolvers based on what needs to be resolved:
- Controller resolver: resolves an HTTP request to a controller class name and method
- View resolver: resolves an HTTP request to a view class name
- Redirect resolver: resolves a redirect request specified in terms of a controller or view class to a fully qualified URL
An HTTP request is first sent to the controller resolver, if the controller resolver returns a resolution the respective controller is activated. If the HTTP request does not resolve to a controller the HTTP request is sent to the view resolver. If the view resolver returns a resolution the respective view is activated, otherwise an error is returned to the client indicating that the URL did not resolve to either a controller or view.
The resolution scheme is completely customizable. The default implementation for each the resolvers can be overriden by specifying an alternate implementation for the respective resolver in the Induction configuration. In the rest of this tutorial we will focus on how the powerful ShortURL resolvers work.
Mapping a URL path to fully qualified class name - the general algorithm
A key part of the ShortURL resolvers is an algorithm to map the path info URLs to fully qualified class names (and vice versa). Understanding this algorithm is key to making effective use the ShortURL resolvers.
The purpose of the core algorithm is to perform fast mappings from a short URL like /helloworld2 to a fully qualified class name like demoapp.helloworld2_app.HelloWorld2View (and vice versa for redirects). A simple but rather tedious (and undesirable) way to do this would be enumerate all such mappings in a configuration file. Induction's ShortURL resolvers, instead of requiring us to enumerate these mappings manually, automatically computes them using concise patterns we provide.
A key concept in the algorithm is what we call a "simplified name" or sname for ease of reference. An sname is extracted from each class name and URL. The algorithm uses two regular expressions 1) a class pattern regex used to extract the sname from a class name, and 2) a URL pattern regex used to extract the sname from a URL.
The basic idea of the core algorithm
For URL resolution, the algorithm creates a map with the sname as a key and the class name from which the sname was extracted as the value, which we will denote by sname->classname. When we recieve a URL we compute the sname for the URL and look it up in sname->classname to determine the class name.
For redirect resolution, the algorithm creates a map with the class name as a key and the corresponding sname as the value, which we will denote by classname->sname. When we recieve a redirect request with a class name (and optionally method name) we lookup the sname for the class name, and then pass the sname to a URL format that generates the URL.
Step 0: Scanning for class names
This step uses the class pattern regex. In this step we collect class names that match the class pattern by scanning the classpath. For our purposes the class pattern will be expected to match controller and/or view class names. The class pattern regex is augmented by a list of root package names to search for classes in. The class pattern regex that is matched against a fully qualified class name. In summary class names in the specified root package(s) that match the class pattern regex are collected.
For example, the class pattern regex (?:.*\.)?(\w+)View and a root package of demoapp would collect all classes in the demoapp package (or sub-package) that ends with View.
Note: An alternate to using a regex based matching approach above would have been to check if each class implements one of the controller or view interfaces (this approach is taken by some other frameworks including the Stripes Framework). We avoid this "interface check" approach since it requires that each class be loaded into the JVM, an operation that is both resource and time intensive.
Step 1: Creating the sname->classname map
In this step we extract the sname for each class name we collected in Step 0 and create a map with the sname as the key and the fully qualified class name as the value. So how do we extract the sname from a class name? We use the same regex we use in Step 0 above. The class pattern regex used in step 0 has an additional requirement that we did not discuss, it is required to have exactly one capturing group.
If you study the example regex (?:.*\.)?(\w+)View used above, you will notice that the regex has only exactly one capturing group which is (\w+) this capturing group is used to extract the sname value. Note that the first group in our sample regex used the (?: open paranthesis instead of the usual ( open paranthesis to mark it as a non-capturing group.
If a <class-replace> directive is specified the sname extracted above is subject to a simple search/replace for each <class-replace> in the respective <url-to-class-map> section.
Step 2: Going from a URL -> sname -> class name
This step URL pattern regex. If the path of a URL matches the URL pattern regex, the sname is extracted from the URL and lookup is done in the sname->classname map. If the map does not contain the sname the algorithm proceed to the next mapping rule (a mapping rule is defined with in a <url-to-class-map> block in the XML configuration)
The <view-mapping> section above can have an unlimited number of <url-to-class-map> sections, each <url-to-class-map> is checked in the order it is configured, and the request is dispatched to the first match. The example above has only one <url-to-class-map> section.
The URL pattern regex for controller mappings may have two capturing groups, if present the second caputuring group is assumed to extract the controller method name embedded in the URL.
Controller mapping example
Following is controller mapping example taken from the demoapp application. The ShortURL controller resolver is configured in the <controller-mapping> section of the Induction configuration. Shown below is the <controller-mapping> section of the Induction configuration file for demoapp:
<controller-mapping>
<url-to-class-map>
<url-pattern>/(\w+)(?:\.(\w+))?\.action</url-pattern>
<class-packages>demoapp</class-packages>
<class-pattern>(?:.*\.)?(\w*)Controller</class-pattern>
</url-to-class-map>
<default-handler-method>handler</default-handler-method>
<ignore-handler-method-case>true</ignore-handler-method-case>
</controller-mapping>
With the above mappings, when the URL http://localhost:8080/counter.incrementCounter.action is parsed the first capturing group returns the string counter (which is taken as the sname) and the second capturing group returns the string incrementCounter (which is taken as the method name).
The <controller-mapping> section can have an unlimited number of <url-to-class-map> sections, each <url-to-class-map> is checked in the order it is configured, and the request is dispatched to the first match. The example above has only one <url-to-class-map> section.
View mapping example
Following is view mapping example taken from the demoapp application. The ShortURL view resolver is configured in the <view-mapping> section of the Induction configuration. Shown below is the <view-mapping> section of the Induction configuration file for demoapp:
<view-mapping>
<url-to-class-map>
<url-pattern>/(\w+)</url-pattern>
<class-packages>demoapp</class-packages>
<class-pattern>(?:.*\.)?(\w+)View</class-pattern>
</url-to-class-map>
</view-mapping>
With the above view mappings the URL http://localhost:8080/helloworld2 will resolve to the view class demoapp.helloworld2_app.HelloWorld2View.
The first element in the <url-to-class-map> section is a <url-pattern> which must be Java™ regex with exactly one capturing group. This regex serves two purposes. First a URL is serviced by this <url-to-class-map> only if the URL matches the regex, second the regex is used to extract the a "short name" from the URL.
The regex in our example is /(\w+), when applied to the URL path /helloworld2 the regex produces a match and the capturing group returns the string helloworld2.
The <view-mapping> section can have an unlimited number of <url-to-class-map> sections, each <url-to-class-map> is checked in the order it is configured, and the request is dispatched to the first match. The example above has only one <url-to-class-map> section.