Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
472 lines (351 sloc) 13.3 KB

Controller and Action handler

ActFramework provides maximum flexibility in creating controller and action handlers.

Concept

  1. Controller. Controller refers to a class that contains one or more action handlers.

    Note in ActFramework it does NOT require controller class to extend a specific class, neither does it require controller class to be annotated with a certain annotation

  2. Action handler. An action handler is a method that provides logic to handler an incoming request. In other words, action handler is a method that has been configured as a route destination.

    An action handler could be either static method or non-static method

A simple controller

A very simple controller with one action handler could be as simple as

package com.mycom.myprj.controller;

public class MyController {
    public void home() {}
}

The home() method is an action handler if there is an entry in the /resources/routes.conf file like:

GET / com.mycom.myprj.controller.MyController.home

You can also use annotation based routing which is usually easier way to go:

@GetAction("/")
public void home() {}

If you haven't define any template, the handler will be a dumb handler which returns 200 Okay response with no body content

If you have a template file created in proper location, ActFramework will render that file and put the render result into the response body

Tips Although it does not require a controller to extend any class, it is good to have your controller class to extend act.controller.Controller.Util to get a set of handy utilities that helps to return responses. If your controller already extends other classes, you can use static import to achieve the same effect as demonstrated below:

  1. Extend act.controller.Controller.Util:

    import act.Controller;
    public class MyController extends Controller.Util {
        ...
    } 
  2. import static:

    import static act.Controller.Util.*;
    public class MyController extends Controller.Util {
        ...
    } 

Note In the following section of this page, it assumes the controller code has extended the Controller.Util class or has the static import statement as shown above.

Getting parameters

ActFramework automatically popluate your action handler parameters from

  1. URL path variables
  2. Query parameters
  3. Form post parameters
@PutAction("/customer/{customerId}/order/{orderId}")
public void updateOrderAmount(String customerId, String orderId, int amount) {
    ...
}

In the above example, the customerId and orderId is the URL path variable and amount is either the query param specified in the URL or the form data depending on the PUT request encoding.

Binding to POJO

ActFramework support binding complex form data to a domain model class (a POJO). Suppose you have the following model class:

public class Order {
    private String id;
    private String customerId;
    private List<Item> items;

    public String getId() {
        return id;
    } 

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public static class Item {
        private String description;
        private int amount;

        public String getDescription() {
            return description;
        }

        public void setDecsription(String desc) {
            this.description = desc;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }
}

Your html order creation form:

<form action="/customer/@customer.getId()/order" method="POST">
<div class="line-item">
    <span class="desc"><input name="order[items][][description]"></span>
    <span class="amount"><input name="order[items][][amount]"></span>
</div>
<div class="line-item">
    <span class="desc"><input name="order[items][][description]"></span>
    <span class="amount"><input name="order[items][][amount]"></span>
</div>
<div class="line-item">
    <span class="desc"><input name="order[items][][description]"></span>
    <span class="amount"><input name="order[items][][amount]"></span>
</div>
...
</form>

You can have an action to handle creating new Order:

@PostAction("/customer/{customerId}/order")
public void createOrder(String customerId, Order order) {
    order.setCustomerId(customerId);
    dao.save(order);
}

Binding from JSON content

The above createOrder method is also able to bind the JSON body:

{
    "items": [
        {
            "description": "item 1",
            "amount": 10000
        },
        {
            "description": "item 2",
            "amount": 12300
        },
        ...
    ]
}

Note ActFramework does NOT support binding to XML data at the current stage

Binding to file

Suppose you have the file upload form in your html page:

<form method="POST" enctype="multipart/form-data" action="/upload">
    Please specify file to upload: <input type="file" name="myfile"><br />
    <input type="submit" value="submit">
</form>

You can declare the file in your action handler method as:

public void handleUpload(File myfile) {
    ...
}

Specify responses

With ActFramework you have multiple ways of specifying response to be sent back, all of them are easy to understand and very expressive.

Implicit 200 Okay

If there is no return type and thrown exception on an action handler method, ActFramework will automatically return an empty 200 Okay response unless a template has been defined for the method. This is useful when the action handler is to servicing a RESTful POST or PUT request, e.g.

@PostAction("/order")
public void createOrder(Order order) {
    orderService.save(order);
}

Explicity 200 Okay

For developer who really want to make everything be explicity, here are two ways to create a 200 Okay response:

  1. Return result

    @PostAction("/order")
    public Result createOrder(Order order) {
        orderService.save(order);
        return ok();
        // or return new Ok();
    }
  2. Throw out result

    @PostAction("/order")
    public void createOrder(Order order) {
        orderService.save(order);
        throw ok();
        // or throw new Ok();
    }

    You can even throw out the result implicitly

    @PostAction("/order")
    public void createOrder(Order order) {
        orderService.save(order);
        ok();
    }

    Note ActFramework will enhance your controller action method, so that if a Result type exception has been returned in the source code be thrown out automatically.

Return 404 Not Found

The server respond with 404 NotFound automatically when it cannot find a handler to service an incoming request in route table. However there are cases that your business logic needs to return a 404 response, e.g. when a query to an order by order ID cannot locate the order in the database with the given order ID, here is what you can do:

@GetAction("/order/{orderId}")
public Order getOrder(String orderId) {
    Order order = dao.findById(orderId);
    if (null == order) {
        throw new NotFound();
    }
}

A more expressive way to do that is:

@GetAction("/order/{orderId}")
public Order getOrder(String orderId) {
    Order order = dao.findById(orderId);
    notFoundIfNull(order);
}

The utmost expressive way is:

@GetAction("/order/{orderId}")
public Order getOrder(String orderId) {
    return dao.findById(orderId);
}

ActFramework will check if there is return type on action handler signature and it returns null then 404 will be send to response automatically.

Return other error request

Here is the demo code shows how to return response with different HTTP status code

public void foo(int status) {
    badRequestIf(400 == status);
    unauthorizedIf(401 == status);
    forbiddenIf(403 == status);
    notFoundIf(404 == status);
    conflictIf(409 == status);
    // none of the above?
    throw ActServerError.of(status);
} 

Automatic map Java Exception to Response

Got exception not handled? ActFramework map them to response automatically!

  1. IllegalArgumentException -> 400 Bad Request
  2. IndexOutOfBoundsException -> 400 Bad Request
  3. IllegalStateException -> 409 Conflict
  4. UnsupportedOperationException -> 501 Not Implemented
  5. Other uncaught exception -> 500 Internal Error

Returning data to response

ActFramework does not require you to return a Result type in your action handler if there are data needs to be returned although you are free to do that. The following two action handlers have the same effect when the accept header is application/json:

@GetAction("/order/{orderId}")
public Order getOrder(String orderId) {
    return dao.findById(orderId);
}
@GetAction("/order/{orderId}")
public Result getOrder(String orderId) {
    Order order = orderService.findById(orderId);
    return renderJSON(order);
}

However the first style is recommended because:

  1. It is simpler
  2. It allow the flexibility of content-negotiation

Render template

For classic MVC application it always needs to render response via templating solution. There are three ways to render through templating.

  1. Implicity template rendering

    For any action handler, if the corresponding template is defined, the template will always be called to render the response

    If an action handler has return value, the value will be passed to the template by variable named result

  2. Explicity renderTemplate call

    @GetAction("/order/editForm")
    public Result orderEditForm(String orderId) {
        Order order = orderService.findById(orderId);
        boolean hasWritePermission = ...;
        return renderTemplate(order, hasWritePermission);
    }

    The above code will call the template (location by convention) with parameter named order and hasWritePermission

  3. Specify the template path

    @GetAction("/order/editForm")
    public Result orderEditForm(String orderId) {
        Order order = orderService.findById(orderId);
        boolean hasWritePermission = ...;
        return renderTemplate("/myTemplateRoot/orderForm.html", order, hasWritePermission);
    }

    As shown above, when the first parameter passed to renderTemplate is a String literal, (not String variable), it will treated as template path, instead of render argument

Render binary data

  1. Render binnary as stream embedded in browser (e.g. a PDF or image):

    @GetAction("/user/{userId}/avatar")
    public Result getAvatar(String userId) {
        User user = userDao.findById(userId);
        return binary(user.getAvatarFile());
    }
  2. Render binnary as a download file

    @GetAction("/invoice/{id}/photoCopy")
    public Result downloadInvoicePhotoCopy(String id) {
        Invoice invoice = dao.findById(id);
        return download(invoice.getPhoto());
    }

Content awareness

ActFramework detects the request's accept header and render content accordingly

@GetAction("/person/{id}")
public Person getPerson(String id) {
    return dao.findById(id);
}

With the action handler code showed above, if the request's Accept header is "application/json", the response will be something like:

{
  "firstName": "John",
  "lastName": "Smith"
}

While if the header is text/html or text/plain, the response will just be the plain String like

John Smith

You can define template files with different suffix if you need tweak the default rendered result:

getPerson.html

@args Person result
<div>
  <span class="label">First name</span><span>@result.getFirstName()</span>
</div>
<div>
  <span class="label">Last name</span><span>@result.getLastName()</span>
</div>

getPerson.json

@args Person result
{
    "firstName": "@result.getFirstName()",
    "lastName: "@result.getLastName()"
}

ActFramework will pickup the propery template file based on the Accept header

Session and Flash

TBD...

Wrap up

In the section we have explained/demonstrates:

  1. The concept of Controller and Action handler in ActFramework
  2. How to write a simple controller
  3. How to handle request parameters includingn binding request parameters to POJO instance
  4. How to respond request with different status code
  5. How to return data
  6. How to find/specify template to render the response
  7. How to get or download binary data
  8. How Accept header impact ActFramework's behavior
  9. How to use Session and Flash Object

Back to index