Skip to content

The Request Annotation

Michael Delamere edited this page Sep 7, 2016 · 53 revisions

With the @Controller annotation we told geeMVC that our bean can handle an incoming request. Now we need to inform the framework when the controller is reponsible - i.e. for which URI - and which method to call that can handle the incoming request.

The @Request Annotation

Element Type Required Description
value String N Tells geeMVC for which URI/path this request handler is responsible. Must not be empty when no other element, specifically the path, has been set. Paths can be specified as plain text, with wildcards or using regular expresisons.
path String N Tells geeMVC for which URI/path this request handler is responsible. Must not be empty when other elements have also been set. Paths can be specified as plain text, with wildcards or using regular expresisons.
name String N Specifies a unique name that identifies the request handler. The name may then be used in the form tag instead of an action-path.
ignore String[] N Indicates one or more paths where this handler should be ignored.
method String[] N Defines one or more HTTP methods that the request handler supports.
params String[] N Specifies one or more parameters that must be in the current request in order to activate this handler method.
headers String[] N Specifies one or more headers that must be in the current request in order to activate this handler method.
cookies String[] N Specifies one or more cookies that must be in the current request in order to activate this handler method.
handles String N Enables a mapping to be freely defined using Javascript, Groovy or MVEL.
consumes String[] N Determines which incoming content-type this handler can process.
produces String[] N Indicates which content-type is sent to the client.
priority int N If two mappings are so similar that geeMVC cannot decide which one to use, you can specify a priority. geeMVC will use the one with the highest priority.
onError String N Tells geeMVC to which view to forward to in case of a validation error. If not specified, geeMVC will call the handler method, whether there is an error or not.

Examples

#####Mapping the Request on the Controller

In this example we create a very simple handler that simply processes "/home". For that we only need to specify a path on the class-level and then tell geeMVC which method is able to handle the request.

@Controller
@Request("/home")
public class HomeController {

    // Maps to /home
    @Request
    public void view() {

    }
}

#####Mapping the Request on the Controller and Method

Often it makes sense to group request handlers together into a single controller. Examples for this would be typical CRUD (create-update-delete) scenarios or where you have few related functionalities like list and view products.

Obviously in the example below you would normally pass some identification parameter in order to view or delete the right product. We'll be dealing with parameters in the next section.

@Controller
@Request("/products")
public class ProductController {

    // Maps to /products/view
    @Request("view")
    public String view() {
        return "view";
    }

    // Maps to /products/list
    @Request("list")
    public String list() {
        return "list";
    }

    // Maps to /products/delete
    @Request("delete")
    public String delete() {
        return "delete";
    }
}

Back to the top

#####Using Wildcards in your Request Mapping

geeMVC allows you to use ant-like wildcard mapping for your paths. This enables you to create a single controller that matches various URIs where only a small portion is different. For example:

/*/b/c and /a/*/c and /a/b/* would all match the URI /a/b/c.

A single wildcard will not match a slash. For example /a/*/d would not match /a/b/c/d. For that you need the double-wildcard, like this:

/a/**/d would match /a/b/c/d.

@Controller
@Request("/products")
public class ProductController {

    // Maps to /products/view/this
    @Request("/**")
    public String view() {
        return "view";
    }

    // Maps to /products/list-products/p1
    @Request("list*/*")
    public String list() {
        return "list";
    }

    // Maps to /products/12345/delete
    @Request("/*/delete")
    public String delete() {
        return "delete";
    }
}

Back to the top

#####Using Regular Expressions in your Request Mapping

If the wildcard path mapping does not solve your problem you can also use regular expressions. Paths containing a regular expression must begin with the caret character so that geeMVC knows that it is a pattern. For example:

@Controller
@Request("/products")
public class ProductController {

    // Maps to /products/view/12345
    @Request("^/view/[0-9]+")
    public String view() {
        return "view";
    }

    // Maps to /products/list-products/p1
    @Request("^/list.*/p[0-9]{1}$")
    public String list() {
        return "list";
    }

    // Maps to /products/12345/123/delete
    @Request("^/[\\d]+/[\\d]+/delete")
    public String delete() {
        return "delete";
    }
}

Back to the top

#####Specifying the HTTP Method

Sometimes you may want to allow only specific HTTP methods or you are building a RESTful application where the paths can be the same and only the HTTP methods are different. This can be done by simply indicating the supported HTTP methods in the @Request annotation.

@Controller
@Request("/products")
public class ProductController {

    // Maps to GET /products/view
    @Request(path = "view", method = HttpMethod.GET)
    public String view() {
        return "view";
    }

    // Maps to GET /products/list
    @Request(path = "list", method = HttpMethod.GET)
    public String list() {
        return "list";
    }

    // Maps to POST /products/delete
    @Request(path = "delete", method = HttpMethod.POST)
    public String delete() {
        return "delete";
    }
}

Back to the top

#####Specifying a Request Query Parameter

Sometimes you may want to target a specific request handler by one or more query parameters. geeMVC offers a very easy way to do this and you can even use regular expressions, JavaScript, Groovy or MVEL for parameter evaluation. Check the following examples:

@Controller
@Request("/products")
public class ProductController {

    // Plain text evaluation and regex matching.
    @Request(path = "view", params = {"myParam=myValue", "page=^\\d+"})
    public String view() {
        return "view";
    }

    // JavaScript matching.
    @Request(path = "view", params = "js: page >= 2")
    public String view() {
        return "view";
    }

    // Groovy matching.
    @Request(path = "view", headers = "groovy: (page as int) >= 2")
    public String view() {
        return "view";
    }

    // MVEL matching.
    @Request(path = "view", headers = {"mvel: page >= 2"})
    public String view() {
        return "view";
    }
}

Back to the top

#####Specifying a Request Header

geeMVC also lets you specify one or more header values in your request mapping. You may want a specific handler method to be called only when a certain header is set. Just like with the parameter evaluation, you can also use regular expressions, JavaScript, Groovy and MVEL when defining your headers. Here is how:

@Controller
@Request("/products")
public class ProductController {

    // Plain text evaluation and regex matching.
    @Request(path = "view", headers = {"Accept=application/json", "version=^\\d+"})
    public String view() {
        return "view";
    }

    // JavaScript matching.
    @Request(path = "view", headers = "js: version >= 2")
    public String view() {
        return "view";
    }

    // Groovy matching (default scripting language).
    @Request(path = "view", headers = "(version as int) >= 2")
    public String view() {
        return "view";
    }

    // MVEL matching.
    @Request(path = "view", headers = {"mvel: version >= 2"})
    public String view() {
        return "view";
    }
}

Back to the top

#####Specifying a Cookie Value

Cookie evaluation works very much the same way as parameter and header evaluation, as you can see in the following example:

@Controller
@Request("/products")
public class ProductController {

    // Plain text evaluation and regex matching.
    @Request(path = "view", cookies = {"loggedIn=true", "version=^\\d+"})
    public String view() {
        return "view";
    }

    // JavaScript matching.
    @Request(path = "view", cookies = "js: version >= 2")
    public String view() {
        return "view";
    }

    // Groovy matching (default scripting language).
    @Request(path = "view", cookies = "(version as int) >= 2")
    public String view() {
        return "view";
    }

    // MVEL matching.
    @Request(path = "view", cookies = {"mvel: version >= 2"})
    public String view() {
        return "view";
    }
}

Back to the top

#####Using the Handles Element

The "handles" element lets you completely freely define whether the handler method is responsible for a particular request or not. Again, for this you can use JavaScript, Groovy or MVEL. Check out the following example, which has been taken from one of the unit test-cases:

@Controller
@Request("/controller12")
public class TestController12 {
    // JavaScript:
    @Request(handles = "js: 1 == 0 || ( /handler[a]+/igm.test( Java.from( req.cookies ).filter( function(c) c.name == 'cookieOne' )[0].value ) )")
    public void handler12a() {

    }

    // Groovy (default scripting language):
    @Request(handles = "1 == 0 || ( req.cookies.findAll({ c -> c.name == 'cookieOne' })[0].value ==~ /(?im)handler[b]+/ )")
    public void handler12b() {

    }

    // MVEL:
    @Request(handles = "mvel: 1 == 0 || (($ in Arrays.asList(req.cookies) if $.name == 'cookieOne')[0].value ~= '(?im)handler[c]+')")
    public void handler12c() {

    }

    // JavaScript:
    @Request(handles = "js: 1 == 0 || (/handler[d]+/igm.test(req.parameterMap['paramOne'][0]))")
    public void handler12d() {

    }

    // Groovy (default scripting language):
    @Request(handles = "1 == 0 || (req.parameterMap['paramOne'][0] ==~ /(?im)handler[e]+/)")
    public void handler12e() {

    }

    // MVEL:
    @Request(handles = "mvel: 1 == 0 || (req.parameterMap['paramOne'][0] ~= '(?im)handler[f]+')")
    public void handler12f() {

    }

    // JavaScript:
    @Request(handles = "js: 1 == 0 || (/handler[g]+/igm.test(req.getHeader('headerOne')))")
    public void handler12g() {

    }

    // Groovy (default scripting language):
    @Request(handles = "1 == 0 || (req.getHeader('headerOne') ==~ /(?im)handler[h]+/)")
    public void handler12h() {

    }

    // MVEL:
    @Request(handles = "mvel: 1 == 0 || (req.getHeader('headerOne') ~= '(?im)handler[i]+')")
    public void handler12i() {

    }
}

Back to the top

#####Specifying Consumes and Produces

On some occasions you may need to specify what content-type the request handler can process and which content-type is being sent back to the client. You can do this with the "consumes" (the handler accepts and can process) and "produces" (the handler sends to the client) elements. For example:

@Controller
@Request("/api/v1/products")
public class ProductController {

    // Maps to GET /api/v1/products/view
    @Request(path = "/view", method = HttpMethod.GET, consumes = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}, produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public String view() {
        return "view";
    }

    // Maps to GET /api/v1/products/list
    @Request(path = "/list", method = HttpMethod.GET, consumes = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}, produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public String list() {
        return "list";
    }

    // Maps to /api/v1/products/delete
    @Request("delete")
    public void delete() {

    }
}

Back to the top

#####Setting a Priority

On rare occasions you may have specified two very similar paths where geeMVC, in certain situations, will not know which one to use. By default geeMVC will refuse to use either as it cannot simply select one at random. Therefore geeMVC gives you the possibility of specifying a priority where you feel it makes sense.

@Controller
@Request("/products")
public class ProductController {

    // Maps to /products/view/higher-priority
    @Request(path = "view/higher-priority", priority = 1)
    public String specificView() {
        return "specific-view";
    }

    // Maps to /products/view/some-other
    @Request(path = "^/view/.+", priority = 2)
    public String catchAllView() {
        return "catch-all-view";
    }
}

Back to the top

#####Setting an Error Path

geeMVC offers two ways for you to deal with validation errors:

  1. Tell geeMVC where to go when a validation error occurs using the onError element. When this value is set geeMVC will not invoke the request handler, but instead forwards directly to the specified view.
  2. By not specifying an error-view, geeMVC will invoke the request handler and you can inform geeMVC what to do in your code. Then you can use the Bindings or the Errors object to check for errors.

If you specify the Bindings or the Errors object in your method signature geeMVC will automatically bind these for you to use.

import static com.geemvc.Results.*;

@Controller
@Request("/contact")
public class ContactController {

    // Maps to /contact/view-form.
    @Request(path = "view-form")
    public String viewForm() {
        return "view: forms/form-page";
    }

    // Maps to /contact/process-form.
    @Request(path = "process-form", onError = "forms/form-page")
    public String processForm() {
        return "view: forms/success-page";
    }

    // Maps to /contact/process-form-with-bindings
    @Request(path = "process-form-with-bindings")
    public String processFormWithBindings(Bindings bindings) {
        if (bindings.hasErrors()) {
            // Forward to view using the statically imported Results object.
            return view("forms/form-page")
                    .bind(bindings.typedValues());
        } else {
            return "view: forms/success-page";
        }
    }

    // Maps to /contact/process-form-with-errors
    @Request(path = "process-form-with-errors")
    public String processFormWithErrors(Errors errors) {
        if (!errors.isEmpty()) {
            // Forward to view using the statically imported Results object.
            return view("forms/form-page");
        } else {
            return "view: forms/success-page";
        }
    }
}

Back to the top


Now that you have learnt how to map a request to your handler, lets move on to the next section Binding Parameters to your Handler Method.

Clone this wiki locally