This document explains the details of how Stapler "staples" your objects to URLs.
The way Stapler works is some what like Expression Language; it takes an object and URL, then evaluate the URL against the object. It repeats this process until it hits either a static resource, a view (such as JSP, Jelly, Groovy, etc.), or an action method.
This process can be best understood as a recursively defined mathematical function evaluate(node,url). For example, the hypothetical application depicted in the "getting started" document could have an evaluation process like the following:
Scenario: browser sends "POST /project/jaxb/docsAndFiles/upload HTTP/1.1" evaluate( , "/project/jaxb/docsAndFiles/upload") -> evaluate( .getProject("jaxb"), "/docsAndFiles/upload") -> evaluate( , "/docsAndFiles/upload") -> evaluate( .getDocsAndFiles(), "/upload") -> evaluate( , "/upload") -> .doUpload(...)
The exact manner of recursion depends on the signature of the type of the node parameter, and the following sections describe them in detail. Also see a document in Hudson that explains how you can see the actual evaluation process unfold in your own application by monitoring HTTP headers.
This section defines the evaluate(node,url) function. Possible branches are listed in the order of preference, so when the given node and url matches to multiple branches, earlier ones take precedence.
Notation
We often use the notation url[0], url[1], ... to indicate the tokens of url separated by '/', and url.size to denote the number of such tokens. For example, if url="/abc/def/", then url[0]="abc", url[1]="def", url.size=2. Similarly if url="xyz", then url[0]="xyz", url.size=1
List notation [a,b,c,...] is also used to describe url. Lower-case variables represent a single token, while upper-case variables represent variable-length tokens. For example, if we say url=[a,W] and the actual URL was "/abc/def/ghi", then a="abc" and W="/def/ghi". If the actual URL was "/abc", then a="abc" and W="".
node can implement the StaplerProxy interface to delegate the UI processing to another object. There's also a provision for selectively doing this, so that node can intelligently decide if it wants to delegate to another object.
Formally,
evaluate(node,url) := evaluate(target,url) — if target instanceof StaplerProxy, target=node.getTarget(), and target!=null
If there's no remaining URL, then a welcome page for the current object is served. A welcome page is a side-file of the node named index.* (such as index.jsp, index.jelly, etc.
Formally,
evaluate(node,[]) := renderView(node,"index")
If url is of the form "/fooBar/...." and node has a public "action" method named doFooBar(...), then this method is invoked.
The action method is the final consumer of the request. This is is convenient for form submission handling, and/or implementing URLs that have side effects. Formally,
evaluate(node,[x,W]) := node.doX(...)
Stapler performs parameter injections on calling action methods.
If the remaining URL is "/xxxx/...." and a side file of the node named xxxx exists, then this view gets executed. Views normally have their view-specific extensions, like xxxx.jelly or xxxx.groovy.
Formally,
evaluate(node,[x,W]) := renderView(node,x)
This is a slight variation of above. If there's no remaining URL and there's an action method called "doIndex", this method will be invoked. Formally,
evaluate(node,[]) := node.doIndex(...)
If url is "/fooBar/..." and node has a public field named "fooBar", then the object stored in node.fooBar will be evaluated against the rest of the URL. Formally,
evaluate(node,[x,W]) := evaluate(node.x,W)
If url is "/fooBar/..." and node has a public getter method named "getFooBar()", then the object returned from node.getFooBar() will be evaluated against the rest of the URL.
Stapler also looks for the public getter of the form "getXxxx(StaplerRequest)". If such a method exists, then this getter method is invoked in a similar way. This version allows the get method to take sophisticated action based on the current request (such as returning the object specific to the current user, or returning null if the user is not authenticated.)
Formally,
evaluate(node,[x,W]) := evaluate(node.getX(...),W)
If url is "/xxxx/yyyy/..." and node has a public method named "getXxxx(String arg)", then the object returned from currentObject.getXxxx("yyyy") will be evaluated against the rest of the URL "/...." recursively.
Formally,
evaluate(node,[x,y,W]) := evaluate(node.getX(y),W)
Really the same as above, except it takes int instead of String.
evaluate(node,[x,y,W]) := evaluate(node.getX(y),W) — if y is an integer
If node is an array and url is "/nnnn/...." where nnnn is a number, then the object returned from node[nnnn] will be evaluated against the rest of the URL "/...." recursively.
Formally,
evaluate(node,[x,W]) := evaluate(node[x],W) — if node instanceof Object[]
If node implements java.util.List and the URL is "/nnnn/...." where nnnn is a number, then the object returned from node.get(nnnn) will be evaluated against the rest of the URL "/...." recursively.
Formally,
evaluate(node,[x,W]) := evaluate(node.get(x),W) — if node instanceof List
If node implements java.util.Map and the URL is "/xxxx/....", then the object returned from node.get("xxxx") will be evaluated against the rest of the URL "/...." recursively.
evaluate(node,[x,W]) := evaluate(node.get(x),W) — if node instanceof Map
If the current object has a public method getDynamic(String,StaplerRequest,StaplerResponse), and the URL is "/xxxx/..." and then this method is invoked with "xxxx" as the first parameter. The object returned from this method will be evaluated against the rest of the URL "/...." recursively.
This is convenient for a reason similar to above, except that this doesn't terminate the URL mapping.
Formally,
evaluate(node,[x,W]) := evaluate(node.getDynamic(x,request,response),W)
If the current object has a public "action" method doDynamic(StaplerRequest,StaplerResponse), then this method is invoked. From within this method, the rest of the URL can be accessed by StaplerRequest.getRestOfPath(). This is convenient for an object that wants to control the URL mapping entirely on its own.
The action method is the final consumer of the request.
Formally,
evaluate(node,url) := node.doDynamic(request,response)
... then the client receives 404 NOT FOUND error.
A Java class can have associated "views", which are the inputs to template engines mainly used to render HTML. Views are placed as resources, organized by their class names. For example, views for the class org.acme.foo.Bar would be in the /org/acme/foo/Bar/ folder, like /org/acme/foo/Bar/index.jelly or /org/acme/foo/Bar/test.jelly. This structure emphasizes the close tie between model objects and views.
Views are inherited from base classes to subclasses.
Jelly script can be used as view files. When they are executed, the variable "it" is set to the object for which the view is invoked. (The idea is that "it" works like "this" in Java.)
For example, if your Jelly looks like the following, then it prints the name property of the current object.
<html><body> My name is ${it.name} </body></html>