Dropwizard Views
The dropwizard-views-mustache
& dropwizard-views-freemarker
modules provide you with simple, fast HTML views using either FreeMarker or Mustache.
To enable views for your Application, add the ViewBundle
in the initialize
method of your Application class:
bootstrap.addBundle(new ViewBundle<>());
You can pass configuration through to view renderers by overriding getViewConfiguration
:
bootstrap.addBundle(new ViewBundle<ViewsConfiguration>() {
@Override
public Map<String, Map<String, String>> getViewConfiguration(ViewsConfiguration config) {
return config.getViewRendererConfiguration();
}
});
The returned map should have, for each renderer (such as freemarker
or mustache
), a Map<String, String>
describing how to configure the renderer. Specific keys and their meanings can be found in the FreeMarker and Mustache documentation:
---
# views: config->mustache
views:
mustache:
cache: false
# views: config->mustache
freemarker:
strict_syntax: true
Then, in your resource method, add a View
class:
package io.dropwizard.documentation;
import io.dropwizard.views.common.View;
public class PersonView extends View {
private final Person person;
public PersonView(Person person) {
super("person.ftl");
this.person = person;
}
public Person getPerson() {
return person;
}
}
person.ftl
is the path of the template relative to the class name. If this class was
com.example.service.PersonView
, Dropwizard would then look for the file
src/main/resources/com/example/service/person.ftl
.
If your template path contains .ftl
, .ftlh
, or .ftlx
, it’ll be interpreted as a FreeMarker template. If it contains
.mustache
, it’ll be interpreted as a Mustache template.
Tip
Dropwizard Freemarker Views also support localized template files. It picks up the client’s locale
from their Accept-Language
, so you can add a French template in person_fr.ftl
or a Canadian
template in person_en_CA.ftl
.
Your template file might look something like this:
<#-- @ftlvariable name="" type="io.dropwizard.documentation.PersonView" -->
<html lang="en">
<body>
<!-- calls getPerson().getName() and sanitizes it -->
<h1>Hello, ${person.name?html}!</h1>
</body>
</html>
The @ftlvariable
lets FreeMarker (and any FreeMarker IDE plugins you may be using) know that the
root object is a com.example.views.PersonView
instance. If you attempt to call a property which
doesn’t exist on PersonView
– getConnectionPool()
, for example – it will flag that line in
your IDE.
Once you have your view and template, you can simply return an instance of your View
subclass:
package io.dropwizard.documentation;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/people/{id}")
@Produces(MediaType.TEXT_HTML)
public class PersonResource {
private final PersonDAO dao;
public PersonResource(PersonDAO dao) {
this.dao = dao;
}
@GET
public PersonView getPerson(@PathParam("id") String id) {
return new PersonView(dao.find(id));
}
}
Tip
Jackson can also serialize your views, allowing you to serve both text/html
and
application/json
with a single representation class.
For more information on how to use FreeMarker, see the FreeMarker documentation.
For more information on how to use Mustache, see the Mustache and Mustache.java documentation.
Template Errors
By default, if there is an error with the template (eg. the template file is not found or there is a
compilation error with the template), the user will receive a 500 Internal Server Error
with a
generic HTML message. The exact error will logged under error mode.
To customize the behavior, create an exception mapper that will override the default one by looking
for ViewRenderException
:
environment.jersey().register(new ExtendedExceptionMapper<WebApplicationException>() {
@Override
public Response toResponse(WebApplicationException exception) {
// Return a response here, for example HTTP 500 (Internal Server Error)
return Response.serverError().build();
}
@Override
public boolean isMappable(WebApplicationException e) {
return ExceptionUtils.indexOfThrowable(e, ViewRenderException.class) != -1;
}
});
As an example, to return a 404 instead of a internal server error when one’s mustache templates can’t be found:
environment.jersey().register(new ExtendedExceptionMapper<WebApplicationException>() {
@Override
public Response toResponse(WebApplicationException exception) {
return Response.status(Response.Status.NOT_FOUND).build();
}
@Override
public boolean isMappable(WebApplicationException e) {
return ExceptionUtils.getRootCause(e).getClass() == MustacheNotFoundException.class;
}
});
Caching
By default templates are cached to improve loading time. If you want to disable it during the development mode,
set the cache
property to false
in the view configuration.
views:
mustache:
cache: false
Custom Error Pages
To get HTML error pages that fit in with your application, you can use a custom error view. Create a View
that
takes an ErrorMessage
parameter in its constructor, and hook it up by registering a instance of
ErrorEntityWriter
.
environment.jersey().register(new ErrorEntityWriter<ErrorMessage, View>(MediaType.TEXT_HTML_TYPE, View.class) {
@Override
protected View getRepresentation(ErrorMessage errorMessage) {
return new ErrorView(errorMessage);
}
});
For validation error messages, you’ll need to register another ErrorEntityWriter
that handles
ValidationErrorMessage
objects.
environment.jersey().register(new ErrorEntityWriter<ValidationErrorMessage, View>(MediaType.TEXT_HTML_TYPE, View.class) {
@Override
protected View getRepresentation(ValidationErrorMessage message) {
return new ValidationErrorView(message);
}
});