Kotowari is a lightweight MVC web application framework on Enkan.
A controller is a plain Java class. Its methods handle HTTP requests and return
an HttpResponse (or a value that a middleware converts into one).
public class CustomerController {
@Inject
private TemplateEngine templateEngine;
@Inject
private DomaProvider daoProvider;
public HttpResponse index() {
CustomerDao dao = daoProvider.get(CustomerDao.class);
return templateEngine.render("customer/index",
"customers", dao.selectAll());
}
public HttpResponse show(Parameters params) {
CustomerDao dao = daoProvider.get(CustomerDao.class);
return templateEngine.render("customer/show",
"customer", dao.selectById(Long.valueOf(params.get("id"))));
}
}
Components registered in the Enkan system are injected into @Inject-annotated
fields of a controller by ControllerInvokerMiddleware.
@Inject
private TemplateEngine templateEngine;
@Inject
private DomaProvider daoProvider;
If multiple fields share the same type, use @Named to distinguish them:
@Named("freemarker")
@Inject
private TemplateEngine freemarker;
@Named("thymeleaf")
@Inject
private TemplateEngine thymeleaf;
ControllerInvokerMiddleware injects the following types into controller method
parameters automatically, in any order:
| Type | Description |
|---|---|
Parameters | Query string and form parameters |
UserPrincipal | Authenticated user (requires AuthenticationMiddleware) |
Session | HTTP session |
Flash | One-request flash messages |
Conversation | Long-running conversation identifier |
ConversationState | Key/value state scoped to the current conversation |
Locale | Negotiated locale (requires ContentNegotiationMiddleware) |
Kotowari provides Rails-style routing.
Routes routes = Routes.define(r -> {
r.get("/").to(HomeController.class, "index");
r.get("/customers").to(CustomerController.class, "index");
r.get("/customers/:id").to(CustomerController.class, "show");
r.post("/customers").to(CustomerController.class, "create");
r.resource(CustomerController.class); // generates all CRUD routes
r.scope("/admin", admin -> {
admin.resource(UserController.class);
});
}).compile();
| Method | Description |
|---|---|
get | Maps GET requests to a controller method |
post | Maps POST requests to a controller method |
put | Maps PUT requests to a controller method |
patch | Maps PATCH requests to a controller method |
delete | Maps DELETE requests to a controller method |
resource | Generates a full set of CRUD routes for a controller |
scope | Groups routes under a common path prefix |
r.resource(CustomerController.class) generates:
| HTTP verb | Path | Controller method |
|---|---|---|
| GET | /customers | index |
| GET | /customers/:id | show |
| GET | /customers/new | newForm |
| POST | /customers | create |
| GET | /customers/:id/edit | edit |
| PUT | /customers/:id | update |
| DELETE | /customers/:id | delete |
// Generates "/customers/"
routes.generate(OptionMap.of("controller", CustomerController.class, "action", "index"));
// Generates "/customers/1"
routes.generate(OptionMap.of("controller", CustomerController.class, "action", "show", "id", 1));
FormMiddleware populates a form object from request parameters.
public class CustomerForm extends FormBase {
@NotBlank
@Size(max = 100)
private String name;
@Email
private String email;
}
The form object is injected into the controller method by ControllerInvokerMiddleware:
public HttpResponse create(CustomerForm form) {
// form fields are already populated from request parameters
}
ValidateBodyMiddleware validates the form object using Jakarta Bean Validation (JSR 380).
Validation errors are stored on the form object itself (via Validatable).
public HttpResponse create(CustomerForm form) {
if (!form.isValid()) {
return templateEngine.render("customer/new", "form", form);
}
// proceed with valid form
}
TransactionMiddleware wraps controller invocation in a JTA transaction when
the controller method or class is annotated with @Transactional.
@Transactional
public HttpResponse create(CustomerForm form) {
CustomerDao dao = daoProvider.get(CustomerDao.class);
dao.insert(toEntity(form));
return redirect("/customers");
}
If the controller method throws a RuntimeException, the transaction is rolled back automatically.
SerDesMiddleware deserializes the request body and serializes the response body
using JAX-RS MessageBodyReader/MessageBodyWriter implementations.
Add a JAX-RS provider dependency (e.g. Jackson) to enable JSON support:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
A controller method can return any serializable object:
public List<Customer> list() {
return daoProvider.get(CustomerDao.class).selectAll();
}
SerDesMiddleware automatically converts it to JSON (or another format)
based on the Accept request header.