OptaPlanner logo
  • Download
  • Learn
    • Documentation
    • Videos

    • Use cases
    • Compatibility
    • Testimonials and case studies
  • Get help
  • Blog
  • Source
  • Team
  • Services
  • Star
  • T
  • L
  • F
  • YT
Fork me on GitHub

Static website generation with Java, Maven and JBake

Wed 9 June 2021
Avatar Geoffrey De Smet
Geoffrey De Smet

Twitter LinkedIn GitHub

OptaPlanner creator

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:

Latest releases screenshot

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

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

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

    <#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:

Videos screenshot

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

  1. Create data/videos.yml:

    - 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:

    <#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:

Template hierarchy

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> directive to build layout inheritance:

  1. Create templates/base.ftl:

    <#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:

    <#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:

    = 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:

$ 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.


Permalink
 tagged as community

Comments

Visit our forum to comment
AtomNews feed
Don’t want to miss a single blog post?
Follow us on
  • T
  • L
  • F
Blog archive
Latest release
  • 9.44.0.Final released
    Wed 6 September 2023
Upcoming events
    Add event / Archive
Latest blog posts
  • Scaling Up Vehicle Routing Problem with planning list variable and Nearby Selector
    Thu 27 April 2023
    Anna Dupliak
  • OptaPlanner 9 has been released
    Mon 24 April 2023
    Radovan Synek
  • OptaPlanner 9 is coming
    Tue 21 February 2023
    Lukáš Petrovický
  • Farewell - a new lead
    Tue 15 November 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part II
    Wed 9 November 2022
    Radovan Synek
  • Bavet - A faster score engine for OptaPlanner
    Tue 6 September 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part I.
    Thu 9 June 2022
    Radovan Synek
  • Blog archive
Latest videos
  • The Vehicle Routing Problem
    Fri 23 September 2022
    Geoffrey De Smet
  • Introduction to OptaPlanner AI constraint solver
    Thu 25 August 2022
    Anna Dupliak
  • On schedule: Artificial Intelligence plans that meet expectations
    Sat 23 July 2022
    Geoffrey De Smet
  • Host your OptaPlanner app on OpenShift (Kubernetes)
    Mon 7 February 2022
    Geoffrey De Smet
  • OptaPlanner - A fast, easy-to-use, open source AI constraint solver for software developers
    Mon 31 January 2022
  • Order picking planning with OptaPlanner
    Fri 31 December 2021
    Anna Dupliak
  • AI lesson scheduling on Quarkus with OptaPlanner
    Thu 18 November 2021
    Geoffrey De Smet
  • Video archive

OptaPlanner is open. All dependencies of this project are available under the Apache Software License 2.0 or a compatible license. OptaPlanner is trademarked.

This website was built with JBake and is open source.

Community

  • Blog
  • Get Help
  • Team
  • Governance
  • Academic research

Code

  • Build from source
  • Issue tracker
  • Release notes
  • Upgrade recipes
  • Logo and branding
CC by 3.0 | Privacy Policy
Sponsored by Red Hat