Web Development

Static website generation with Java, Maven and JBake

Did you notice? Last week, we migrated the entire www.optaplanner.org website (1399 files) to build with Java and Maven, instead of Ruby and Rake. On the face of it, nothing changed. But in the sources, for our team of Java developers, it is a game changer.

Our java team can now contribute to the website easily. Within hours of completing the migration, there was already a commit of one of our developers who would rather not touch the previous source code with a ten-foot pole.


We built this site.
We built this site on Java and Maven.
We built this site.
We built this site on JBake and Freemarker.

Why a static website generator?

A static website generator transforms templates and content files into a static HTML/JS/CSS website. This has many advantages over a Content Management System (CMS) for projects such as ours:

  • Hosting is cheap. GitHub pages even hosts static websites for free.
  • The source files go into Git for backup and history.
  • The source files are in plain text:

     

    • Changes come in as a Pull Request for proper review and CI validation.
    • The sources are open in our IDEs, which encourages refactoring them alongside the code. This results in less stale content.

For many years, Awestruct has served us well. But due to lack of activity, it was time to upgrade.

Why JBake?

Because we’re Java programmers.

There are several good static website generators out there, such as Jekyll (Ruby) and Hugo (Go). We choose JBake (Java), because:

  1. Our website now builds with Maven (mvn generate-resources).

     

    No need to install anything. Not even JBake. Everyone builds with the same version of JBake, as declared in the pom.xml.

    And it’s fast: even a mvn clean build of 150 output pages only takes 20 seconds on my machine.

  2. It’s all Java underneath.

     

    Writing conditional expressions is straightforward. The APIs (String.substring(), …​) are familiar. Date formatting (d MMMM yyyy) and regular expressions behave as expected.

    And most importantly, error messages are clear.

For 8 years, I wrote the website with Awestruct (Ruby). But I never took the time to decently learn Ruby, so every change entailed hours of trial and error. I couldn’t just read the error message and fix it. This isn’t Ruby’s fault. It was because I never took a few days to actually learn Ruby. With JBake, I fix errors in a fraction of time: no more trial and error.

What is JBake?

JBake is a static website generator with many options:

  • Build with Maven or Gradle.

     

    We choose Maven, because all our repos build with Maven (although two OptaPlanner Quickstarts also build with Gradle because OptaPlanner supports Gradle too).

  • Write content in Asciidoc, Markdown or HTML.

     

    We choose Asciidoc because it’s richer and more reliable than Markdown. Also, all our documentation is written in Asciidoc.

  • Create templates with Freemarker, Thymeleaf or Groovy.

     

    We choose Freemarker because it’s a powerful, battle-tested templating engine.

Tips and tricks

These are common tasks to build an advanced static website and how to implement each task in JBake-Freemarker. You might even call these JBake Design Patterns:

Use a macro to render shared content

Almost all our templates show the same Latest releases panel:

A Freemarker template is perfect to avoid repeating yourself (DRY):

  1. Create templates/macros.ftl with a macro that outputs the HTML:

     

    1
    2
    3
    4
    5
    6
    <#macro latestReleases>
        <div class="panel panel-default">
            <div class="panel-heading">Latest release</div>
            ...
        </div>
    </#macro>
  2. Then use it in the *.ftl templates:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    <#import "macros.ftl" as macros>
    ...
    <div class="row">
        <div class="col-md-9">
            ...
        </div>
        <div class="col-md-3">
            <@macros.latestReleases/>
        </div>
    </div>

Use data files to add videos, events or other volatile data

Some data changes too often to maintain in a content or template file:

A data file, for example a simple *.yml file, works well to hold such volatile data:

  1. Create data/videos.yml:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    - youtubeId: blK7gxqu2B0
    title: "Unit testing constraints"
    ...
     
    - youtubeId: gIaHtATz6n8
    title: "Maintenance scheduling"
    ...
     
    - youtubeId: LTkoaBk-P6U
    title: "Vaccination appointment scheduling"
    ...
  2. Then use it in ftl templates:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    <#assign videos = data.get('videos.yml').data>
     
    <div class="panel panel-default">
        <div class="panel-heading">Latest videos</div>
        <div class="panel-body">
            <ul>
                <#list videos[0..6] as video>
                    <li>
                        <a href="https://youtu.be/${video.youtubeId}">${video.title}</a>
                    </li>
                </#list>
            </ul>
        </div>
    </div>

Layout inheritance

All HTML pages typically share the same HTML head (metadata), header (navigation) and footer. These fit well into a base.ftl layout, extended by all other templates:

Even though most content uses the normalBase.ftl, there’s separate useCaseBase.ftl template for all the use case pages, such as the Vehicle Routing Problem (VRP), Maintenance Scheduling and Shift Rostering.

Use a macro with the <\#nested> to build layout inheritance:

  1. Create templates/base.ftl:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    <#macro layout>
        <html>
            <head>
              ...
            </head>
            <body>
                <div>
                    ... <#-- header -->
                </div>
                <#nested>
                <div>
                  ... <#-- footer -->
                </div>
            </body>
        </html>
    </#macro>
  2. Extend it in templates/useCaseBase.ftl and introduce the custom attribute related_tag:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <#import "base.ftl" as parent>
     
    <@layout>${content.body}</@layout>
     
    <#macro layout>
        <@parent.layout>
            <h1>${content.title}</h1>
            <#nested>
            <h2>Related videos</h2>
            <#assign videos = data.get('videos.yml').data>
            <#assign relatedVideos = videos?filter(video -> video.tags.contains(content.related_tag))>
            <ul>
                <#list relatedVideos as video>
                    <li><a href="https://youtu.be/${video.youtubeId}">${video.title}</a></li>
                </#list>
            </ul>
        </@parent.layout>
    </#macro>
  3. Create the use case page content/vehicleRoutingProblem.adoc that uses that template and sets that related_tag attribute:

     

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    = Vehicle Routing Problem
    :jbake-type: useCaseBase
    :jbake-related_tag: vehicle routing
     
    The Vehicle Routing Problem (VRP) optimizes the routes of delivery trucks,
    cargo lorries, public transportation (buses, taxis and airplanes)
    or technicians on the road, by improving the order of the visits.
    This routing optimization heavily reduces driving time and fuel consumption compared to manual planning:
     
    ...

Get started

Try it yourself. To build the www.optaplanner.org website, run these commands:

1
2
3
4
5
6
$ git clone https://github.com/kiegroup/optaplanner-website.git
...
$ cd optaplanner-website
$ mvn clean generate-resources
...
$ firefox target/website/index.html

Or take a look at the source code.


Published on Java Code Geeks with permission by Geoffrey De Smet, partner at our JCG program. See the original article here: Static website generation with Java, Maven and JBake

Opinions expressed by Java Code Geeks contributors are their own.

Geoffrey De Smet

Geoffrey De Smet (Red Hat) is the lead and founder of OptaPlanner. Before joining Red Hat in 2010, he was formerly employed as a Java consultant, an A.I. researcher and an enterprise application project lead. He has contributed to many open source projects (such as drools, jbpm, pressgang, spring-richclient, several maven plugins, weld, arquillian, ...). Since he started OptaPlanner in 2006, he’s been passionately addicted to planning optimization.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button