Reference

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(<root object>, "/project/jaxb/docsAndFiles/upload")
-> evaluate(<root object>.getProject("jaxb"), "/docsAndFiles/upload")
-> evaluate(<jaxb project object>, "/docsAndFiles/upload")
-> evaluate(<jaxb project object>.getDocsAndFiles(), "/upload")
-> evaluate(<docs-and-files-section-object for jaxb>, "/upload")
-> <docs-and-files-section-object for jaxb>.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.

Evaluation of URL: Reference

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="".

Stapler Proxy

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

Index View

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")

Action Method

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.

View

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)

Index Action Method

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(...)

Public Field

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)

Public Getter Method

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)

Public Getter Method with a String Argument

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)

Public Getter Method with an int Argument

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

Array

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[]

List

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

Map

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

Dynamic Getter Method

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)

Dynamic Action Method

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)

If None of the Above Works

... then the client receives 404 NOT FOUND error.

Views

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

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>