A Java library for enabling your webapps to have clean, REST like urls. Rather than being a swiss army knife tool like others, it is optimized for this single role, and as such it is (hopefully) easier to use, less verbose and packed with convenience features (such as binding matched url parts directly to javabean properties).
Download the jar and add it to your application library directory.
Create a subclass of com.pagegoblin.jurlmap.DispatchFilter and override the configure method to add your configuration.
Add a mapping of your filter to your web.xml file.
<filter>
<filter-name>DispatchFilter</filter-name>
<filter-class>com.myapp.MyDispatchFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>DispatchFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Lets start with some examples:
protected void configure() {
// Match given pattern, in which $ mean a string, bind to parameter
// `Username` and forward to profile.jsp
// Parameter will be accessible via request.getParameter()
forward("/profile.jsp", "/profile/$Username");
// Match given pattern in which % means integer and * mean until end of pattern,
// bind integer to parameter ArticleId and forward to article.jsp
forward("/article.jsp", "/article/%ArticleId/*");
// match pattern, bind parameter and redirect to `/servlets/profileservlet?Username=...`
redirect("/servlets/profileservlet", "/member/$Username");
// Shows how to add http methods to patterns. First one will be run when pattern matches
// AND method is POST (and only POST). Second will run when method is GET.
// Default is GET and POST.
// Both patterns are the same (besides the http method that is).
forward("/edit_person.jsp", "/member/$Username;POST");
forward("/person.jsp", "/member/$Username");
// Shows use of deploy rule
deploy(LogoutPage.class, "/logout");
// Deploy is also possible without passing a pattern parameter, if LoginPage class is
// annotated with a @Deploy(path) annotation
deploy(LoginPage.class);
// Shows that multiple patterns are possible in single rule call.
// Also demonstrates `enum` pattern element where one of several given values is matched
deploy(ArticlesHomePage.class, "/", "/sort/[newest|oldest|hottest]SortOrder");
}
The above examples show 90% of what you need to know to effectively use jurlmap.
Rather than having external configuration files jurlmap is configured in plain old Java. Why this and not XML?
The answer deserves an article or a debate of its own, but here is the short version: XML is a syntax not a language and as such it is no more declarative or imperative (or object-oriented, relational, bisexual or other) that any other syntax. As a matter of principle and convenience I much prefer to code in a single language rather than many if that is possible.
So in this case we have one configuration artifact (MyDispatchFilter.java) compared to one artifact we would have with XML (lets say myconfig.xml), and
forward("/profile.jsp", "/member/$Username");
is no less declarative than
<rule>
<from>/member/$Username</from>
<to>/profile.jsp</to>
</rule>
However we get to stay in Java with all the advantages this has.
Having said all this if any of you really feel you cannot live without an XML configuration ability (or indeed some other external format) let me know, and I might add such options in the near future.
Now back to our story...
As can be seen from the example (the one way way above...) there are (currently) three possible actions:
forward, redirect and deploy. Each takes as parameters a target, and one (or in the case of deploy zero) or multiple
patterns. On invocation if the pattern is matched the action is executed...obviously.
What do these action types mean exactly:
forward When matched it will make parameters accesible via request.getParameter and will forward to target
redirect When matched it will append parameters as query string and will send redirect to target
deploy When matched it will create a new instance of target class, will bind parameters to new instance via setters or raw fields (whichever it finds) and finally invoke the service method.
The first two action types are useful when you are working with an existing application, or an application written in an existing framework for which you want to enable clean URLs. The deploy action is more interesting if you are developing a new application and you are looking for an alternative to the limited servlet mapping facilities of web.xml.
The available pattern syntax is very simple:
A pattern is separated between a ; character. Anything before ; is the path pattern. Anything after is the http methods
such as GET, POST, PUT, DELETE. You can specify multiple http methods separated by | such as GET|PUT.
Any given path is split at each / character and each subelement can be one of the following:
$ Matches any string.
% Matches an integer
[] Matches one of several options separated by | in the like: [options1|option2|option3]
* Matches everything until the end of the pattern, including subsequent /.
Any other text is matched as is. it behaves same as [text] with a single element, only in this case it cannot be followed by ? or binding options.
If a ? follows any of the above elements (except the last one) then that element is optional, that is the pattern matches whether the element is present or not.
Any of the above elements can be followed by binding options that specify to which parameter to bind the matched result. example
/member/$Username will match a string and bind it to parameter username. Parameters can also be compound such as
/member/%User.Id, which will work as follows:
For the forward and redirect actions the matched parameter will contain a dot and will be User.Id exactly as give.
For the deploy action, the result will be more complicated, in that first the object property User will be looked up, a new object will be instantiated to the type of that property, the property set to that object, and the Id property or the new instance will be set. Lets draw a picture to explain that:
Assuming the following:
class MyPage implements Page {
User user;
}
class User {
int id;
}
When binding User.Id this will happen:
- result = new User()
- result.id = @@MatchedIntegerParameter@@
- myPage.user = result;
There is one case where the binding syntax is a little different. It is possible to annotate methods of a class with pattern annotations @Deploy and then use them as targets in a pattern dispatch like:
class MyClass {
private static final PathSet PATHS = new PathSet(MyClass.class);
public void doMe(String path) {
PATHS.dispatch(this, path);
}
@Deploy("/something/to/do")
public void runSomething() {
...
}
@Deploy("/person/$1/%2/%3.Id")
public void runSomething(String parameter1, int parameter2, User parameter3) {
...
}
}
In this case, as the examples show the parameter bindings are specified as numbers, corresponding to the method parameters as they appear in order.
One problem you will never have with jurlmap is the issue of trailing /. In many frameworks /path is different from /path/ and sometimes you get one when you want the other or you are confused about which is which. With jurlmap all extra / characters are ignored so /my/path is the same as /my/path/ which is the same as /my/////path//
Here is a list of the classes and interfaces that you can use as part of jurlmap:
public class DispatchFilter implements javax.servlet.Filter {
abstract protected void configure();
public final void forward(String target, String ...patterns);
public final void redirect(String target, String ...patterns);
public final void deploy(Class<?> clazz, String ...patterns);
public final void setDispatchServlet(String dispatchServlet);
public final void setDefaultHttpMethods(String methods)
}
public class DispatchServlet {
}
public interface Page {
public void service(ServletContext context, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}
abstract public class AbstractPage implements Page {
public void run() throws ServletException, IOException;
protected boolean dispatch();
protected HttpServletRequest getRequest();
protected HttpServletResponse getResponse();
protected ServletContext getServletContext();
protected void forward(String target) throws ServletException, IOException;
protected void redirect(String target) throws IOException;
protected boolean modified(long lastModified);
}
public class PathSet {
public PathSet(Class targetClass);
public Object dispatch(Object self, String path);
public Object dispatch(Object self, String path, HttpServletRequest request);
}
public @Retention(RetentionPolicy.RUNTIME)
@interface Deploy {
String[] value();
}
License is LGPL.
If you think there is something missing either from this manual or a feature that should be added to jurlmap let me know.