From WordPress to Hugo
I have used self-hosted WordPress for this blog since its start in 2006, but I finally got fed up with having to frequently update WordPress itself and a bunch of plugins, or having to constantly worry about security issues. Since I am the only one publishing on this blog, I decided to switch to a static site generator instead, and I chose Hugo, mainly because it is easy to install and does not require any heavy runtime (most of its competitors require Python, Ruby or Node.js).
From screen to tmux
I have written about how to configure screen in XTerm to support Ctrl-Tab and convenient scrolling back in history.
This has worked well for over a decade now, but it seems like screen is not developed very much any longer, and tmux has gained popularity as a more modern and actively developed alternative. So I decided to try it, and was able to replicate my setup with tmux instead of screen. The result was actually simpler, since tmux requires less modification of XTerm config.
In-memory database for testing
I have written about using an embedded database like HSQLDB or H2 for testing Java applications. This can still be useful (although JavaEE is not so popular any longer). But even though H2 claims to emulate PostgreSQL and several other popular production SQL databases, sometimes your application uses very specific features of a particular database and you need to test against the real thing to be really sure your application works correctly.
This can conveniently be done using Testcontainers, which run an instance of the real database in a Docker container for your tests. The downside is that this can be significantly slower than an embedded in-memory database like HSQLDB or H2.
Alpine rather than distroless
I have been using the distroless Docker base images to package my applications, mainly since I want slim and simple image without unnecessary cruft.
However, they are based on Debian, and Debian is unfortunately not so diligent to fix serious security issues as other distributions like Ubuntu or Alpine. If you scan a distroless image with the grype tool, you get this result:
$ grype gcr.io/distroless/java17-debian11
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
libharfbuzz0b 2.7.4-1 (won't fix) deb CVE-2023-25193 High
libjpeg62-turbo 1:2.0.6-4 (won't fix) deb CVE-2021-46822 Medium
libssl1.1 1.1.1n-0+deb11u4 (won't fix) deb CVE-2023-0464 High
openssl 1.1.1n-0+deb11u4 (won't fix) deb CVE-2023-0464 High
Furthermore, if small image size is important, distroless is quite overrated. They brags about being so small:
How to enable multi-platform Docker builds on Ubuntu 22.04
Docker’s official documentation on how to enable multi-platform Docker builds is a bit intimidating, suggesting you to run the Docker image tonistiigi/binfmt in privileged mode on your machine. I searched for alternatives on Ubuntu, and found this very detailed description. However, with recent versions of Ubuntu and Docker, it is now much easier than that:
- Install QEMU’s static user mode emulation binaries:
sudo apt install qemu-user-static - Install Docker Engine with Buildx support according instructions here (do not install
docker-ceordocker.iofrom Ubuntu’s standard repository). - Create a Docker Buildx builder:
docker buildx create --name multiplatform --bootstrap --use - Verify it:
docker buildx inspect
Then you can do multi-platform Docker builds like this:
docker buildx build --platform linux/amd64,linux/arm64 .
How to capture log events in tests with Log4j 2
Sometimes you want to verify that your program actually logs what it is supposed to be logging.
When using Apache Log4j 2 as logging framework, it can be done like this:
- Include this dependency (for Maven, adjust appropriately for other build systems):
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
- Import some stuff:
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.test.appender.ListAppender;
- Add a
ListAppenderto the logger you are interested in:
var loggerContext = LoggerContext.getContext(false);
var logger = (Logger) loggerContext.getLogger(MyClass.class);
var appender = new ListAppender("List");
appender.start();
loggerContext.getConfiguration().addLoggerAppender(logger, appender);
- Exercise your code which should be logging.
- Extract what was logged:
List<String> loggedStrings =
appender.getEvents().stream().map(event -> event.getMessage().toString()).collect(Collectors.toList());
Update:
How to run exec-maven-plugin only if a file is not up-to-date
In my Maven build, I use exec-maven-plugin to run an external tool for Java code generation. In this case the flatbuffers compiler (flatc).
By default, flatc always runs, so you always get a new .java file. This will always trigger Java compiler to run, which will in turn always trigger jar packaging to run, etc. I would like flatc to only run if it’s source file is updated, so I can get a properly incremental build and avoid needlessly recompiling.
Support fake Android TV devices
Android TV is included in some modern Smart TVs, and there are also external set-top-boxes to get Android TV for any TV with HDMI input.
However, there are also some set-top-boxes which almost, but not quite, support Android TV.
With not so much extra effort, it is possible to make an Android app supporting those fake Android TV devices as well as the real ones.
In addition to the guidelines from Google for building Android TV apps, do this to support the fake TV Android TV devices as well:
Data structures and Domain Driven Design
After listening to a presentation about Domain Driven Security, I got some additional insights about what a data structure (as opposed to OOP object) should be able to do. Data structures (I call them struct) should have a subset of OOP features which I think is useful and harmless.
A struct should allow constructors. Such constructors can have two purposes, to convert one type of data into another and to validate the input. Such constructors should take parameters and be able to throw an exception if the input is invalid, to make it impossible to create an invalid instance of a struct. Each constructor should be required to set every field of the struct, and if no constructors are defined, a default one should be generated which simply takes all fields as parameters.
Objects vs. data structures
Several popular statically typed programming languages, including C++, Java and C#, have a serious design flaw. They make no useful distinction between objects in the OOP sense and plain data structures.
A plain data structure should only be a dumb container for data. It should not have any behaviour, only simple accessors for data items in it. It should not be possible to override data accessors. It should be clear and transparent what it does.