Skip to main content

Template engine integration

In this example we will implement template engines in ActiveJ applications. The example shows how to create a simple Poll app which creates new polls with a custom title, description, and options. Each new poll gets a unique generated link that leads to the page where you can vote.

See how simple it is to implement such features using ActiveJ: the embedded application server has only about 100 lines of code with no additional xml configurations. In this example we used Mustache as a template engine.

Here we will consider only the ApplicationLauncher class, which is the main class of the application. You can find full example sources on GitHub

Creating launcher

The ApplicationLauncher launches our application, takes care of routing and proper content generation on HTML pages. We will extend ActiveJ's HttpServerLauncher to manage the application's lifecycle:

note

In this example we omit error handling to keep everything brief and simple.

public final class ApplicationLauncher extends HttpServerLauncher {

private static ByteBuf applyTemplate(Mustache mustache, Map<String, Object> scopes) {
ByteBufWriter writer = new ByteBufWriter();
mustache.execute(writer, scopes);
return writer.getBuf();
}

@Provides
PollDao pollRepo() {
return new PollDaoImpl();
}

Let's have a closer look at the launcher. It contains two methods:

  • applyTemplate(Mustache mustache, Map<String, Object> scopes) fills the provided Mustache template with the given data through a ByteBuf - a more efficient implementation of ByteBuffer;
  • pollRepo() provides the business logic of our app. The @Provides annotation means it's done through ActiveJ Inject DI.

Next, we provide AsyncServlet:

@Provides
AsyncServlet servlet(Reactor reactor, PollDao pollDao) {
Mustache singlePollView = new DefaultMustacheFactory().compile("templates/singlePollView.html");
Mustache singlePollCreate = new DefaultMustacheFactory().compile("templates/singlePollCreate.html");
Mustache listPolls = new DefaultMustacheFactory().compile("templates/listPolls.html");

return RoutingServlet.builder(reactor)
.with(GET, "/", request -> HttpResponse.ok200()
.withBody(applyTemplate(listPolls, Map.of("polls", pollDao.findAll().entrySet())))
.toPromise())

In the AsyncServlet we create three Mustache objects, one for each HTML page. To define routing, we create a RoutingServlet. You may notice that the routing approach resembles Express.js. In the example above we've added the mapping to the homepage by using the with method of RoutingServlet's builder.

Methodwith(@Nullable HttpMethod method, String path, AsyncServlet servlet) adds a route to the RoutingServlet:

  • method is one of the HTTP methods (GET, POST and so on)
  • path is the path on the server
  • servlet defines the logic of request processing. If you need to get some data from the request while processing you can use:
  • request.getPathParameter(String key)/request.getQueryParameter(String key) (see example of query parameter usage') to provide the key of the needed parameter and receive back a corresponding String
  • request.getPostParameters() to get a Map of all request parameters

In this request we get all current polls and info about them in order to generate listPolls page.

Let's add one more request:

.with(GET, "/poll/:id", request -> {
int id = Integer.parseInt(request.getPathParameter("id"));
return HttpResponse.ok200()
.withBody(applyTemplate(singlePollView, Map.of("id", id, "poll", pollDao.find(id))))
.toPromise();
})

This request returns a page with a poll which id was specified in the path. Pay attention to the provided path /poll/:id syntax. : states that the following characters until the next / are a variable; in this case, its keyword is id. This way you don't have to map each poll's id to a different request.

The next requests with /create, /vote, /add and /delete paths take care of providing a page for creating new polls, voting, adding created polls to the pollDao and deleting them from it respectively:

.with(GET, "/create", request ->
HttpResponse.ok200()
.withBody(applyTemplate(singlePollCreate, Map.of()))
.toPromise())
.with(POST, "/vote", request -> request.loadBody()
.then(() -> {
Map<String, String> params = request.getPostParameters();
String option = params.get("option");
String stringId = params.get("id");
if (option == null || stringId == null) {
return HttpResponse.ofCode(401).toPromise();
}

int id = Integer.parseInt(stringId);
PollDao.Poll question = pollDao.find(id);

question.vote(option);

return HttpResponse.redirect302(nonNullElse(request.getHeader(REFERER), "/")).toPromise();
}))
.with(POST, "/add", request -> request.loadBody()
.then($ -> {
Map<String, String> params = request.getPostParameters();
String title = params.get("title");
String message = params.get("message");

String option1 = params.get("option1");
String option2 = params.get("option2");

int id = pollDao.add(new PollDao.Poll(title, message, List.of(option1, option2)));
return HttpResponse.redirect302("poll/" + id).toPromise();
}))
.with(POST, "/delete", request -> request.loadBody()
.then(() -> {
Map<String, String> params = request.getPostParameters();
String id = params.get("id");
if (id == null) {
return HttpResponse.ofCode(401).toPromise();
}
pollDao.remove(Integer.parseInt(id));

return HttpResponse.redirect302("/").toPromise();
}))
.build();

Also, we defined main() method which will start our launcher:

public static void main(String[] args) throws Exception {
Launcher launcher = new ApplicationLauncher();
launcher.launch(args);
}

And that's it, we have a full-functional poll application!

Running the application

If you want to run the example, you need to from GitHub and import it as a Maven project. Check out branch v6.0-beta2. Before running the example, build the project (Ctrl + F9 for IntelliJ IDEA). Open ApplicationLauncher class and run its main() method. Then open your favourite browser and go to localhost:8080