Easy JDK selection using SDK-man

18 November 2021

SDKman Logo

SDKMan is a great tool for installing Java versions and a lot more. But it is not handy when you just want to switch between Java 11 and Java 17 on the commandline:

$ sdk use java 17.0.1-tem

Using java version 17.0.1-tem in this shell.

You have to provide the full version and vendor (in my case Eclipse Temurin) for SDKMan to select the JDK of choice. But I just want to say sdk 17 or sdk 11. On macOS you can use the default installers and use /usr/libexec/java_home to select the right Java version. But unfortunately SDKMan doesn’t provide such an experience.

In order to get this:

$ jdk 17

Using java version 17.0.1-tem in this shell.

I wrote the shell function below to select the top installed version for the JDK you request:

function jdk() {
  sdk use java `sdk ls java | grep installed | grep $1 | awk '{print $NF}' | head -1`

It uses SDKman’s functionality to:

  • list the installed java versions,
  • filters out the installed java versions,
  • filters the requests versions (you can have multiple installed, e.g. 17.0.0-tem and 17.0.1-tem)
  • selects the last column of the SDKman line
  • instructs SDKman to use that version

Have fun with this one!

Generate Java 16/17+ records from JDBC resultset

15 November 2021

Since I couldn’t surface anyone using Java records and generating them from a JDBC result set (as a lazy programmer I want to type as less code as I possibly can), here’s a method that just does that.

Caveats: some types are not generated correctly (e.g. arrays), but you can easily modify those types as you like.

public static void resultSetToJavaRecordCodeGenerator(ResultSet rs) throws SQLException
	while (rs.next())
		System.out.println("public record ChangeMyName(");
		var komma = "  ";
		for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++)
			var typeName = rs.getMetaData().getColumnClassName(i).replace("java.lang.", "");
			var fieldName = StringUtil.firstCharLowercase(rs.getMetaData().getColumnName(i));
			System.out.print(" ");
			komma = ", ";

Given for example the columns returned from a SqlServer RESTORE HEADERSONLY FROM DISK='/dumps/...' you get this Java record:

public record ChangeMyName(
	  String backupName
	, String backupDescription
	, Short backupType
	, java.sql.Timestamp expirationDate
	, Short compressed
	, Short position
	, Short deviceType
	, String userName
	, String serverName
	, String databaseName
	, Integer databaseVersion
	, java.sql.Timestamp databaseCreationDate
	, Long backupSize
	, java.math.BigDecimal firstLSN
	, java.math.BigDecimal lastLSN
	, java.math.BigDecimal checkpointLSN
	, java.math.BigDecimal databaseBackupLSN
	, java.sql.Timestamp backupStartDate
	, java.sql.Timestamp backupFinishDate
	, Short sortOrder
	, Short codePage
	, Integer unicodeLocaleId
	, Integer unicodeComparisonStyle
	, Short compatibilityLevel
	, Integer softwareVendorId
	, Integer softwareVersionMajor
	, Integer softwareVersionMinor
	, Integer softwareVersionBuild
	, String machineName
	, Integer flags
	, String bindingID
	, String recoveryForkID
	, String collation
	, String familyGUID
	, Boolean hasBulkLoggedData
	, Boolean isSnapshot
	, Boolean isReadOnly
	, Boolean isSingleUser
	, Boolean hasBackupChecksums
	, Boolean isDamaged
	, Boolean beginsLogChain
	, Boolean hasIncompleteMetaData
	, Boolean isForceOffline
	, Boolean isCopyOnly
	, String firstRecoveryForkID
	, java.math.BigDecimal forkPointLSN
	, String recoveryModel
	, java.math.BigDecimal differentialBaseLSN
	, String differentialBaseGUID
	, String backupTypeDescription
	, String backupSetGUID
	, Long compressedBackupSize
	, Short containment
	, String keyAlgorithm
	, [B encryptorThumbprint
	, String encryptorType

And yes, this will not compile because of the [B encryptorThumbprint, but that doesn’t make this method anymore less useable.

Use this to your advantage!

Fedora, Moby Engine, Docker, testcontainers and SELinux

15 November 2021

IT Crowd's Richard Ayoade is angry at computer

At $dayjob we make a portal that starts docker jobs. For this to work we need to access the Docker API running on the host. A typical way to make this work is to use the docker socket located in /var/run/docker.sock.

You just mount the socket in your container and point your Docker client to it. This works on macOS and several linux distributions. It works in Production(tm).

Then my macbook pro needed servicing so I installed Fedora on my replacement and figured things would just work out.

When you want to install docker, typically you just type in the executable name of your package manager and install docker:

$ sudo dnf install docker

This command doens’t work out of the box because it asks you to install moby-engine instead. Alright you think: moby-engine is just docker right?

SELinux enters the room

Apparently moby-engine does not work with the cgroups 2 used by SELinux, or tries to do so but fails. There was no way I was able to let my docker client talk to the host through a mounted docker socket. I changed the rights, changed the groups, changed the user. So we YOLO’d the container and put it in production for testing (fortunately the application is used internally by just a couple of users a couple of days in a month). This cost me about 2 days of fiddling, reading and trying stuff. SELinux won.

Next I wanted to use testcontainers.org for testing backup/restore procedures of databases we manage through the docker application. Stepping through the code I saw that they try to start a container for reaping dangling containers that were started during testing. This requires… access to the docker socket.

I tried starting the ryuk container manually using my shell, but the only way to start it was using the privileged flag. Looking at the code from testcontainers I noticed they already set the container to run privileged (UPDATE Apparently my reading of the code was wrong and I need to set a flag explicitly to let Ryuk run privileged. The documentation and the mitigation of ryuk at testcontainers is not good in this instance IMO), so something janky was happening with SELinux.

One of the tickets mentioned reinstalling containerd, to fix the rights. Looking at some other posts they mentioned installing docker-ce.

So I followed the manual for installing docker-ce and lo and behold: everything worked immediately.

While SELinux is probably making my life as a user of Linux safer, it is frustrating that it requires you to become an expert at it before you can actually get work done. At this time I can’t say I am impressed by SELinux.

Fedora 34 Workstation Desktop/menu icon

25 October 2021

As a new user coming to Desktop Linux, I like Fedora 34 Workstation because it is the only linux distribution that is capable of resuming most of the time after putting my computer to sleep. It is also very fast, and capable on my quite powerful desktop computer (AMD 3950X/5700XT/64GB RAM). But the one thing that kept bugging me is using Eclipse.

Eclipse and third-party package managers like those that work on linux distributions is a nightmare. They try to manage the Eclipse distribution in ways that is ill conceived and they are usually far behind the realities of Eclipse proper. Also it is quite nasty to try to update your Eclipse installation and install plugins etc in such a scenario as I have experienced in the past. But now that the Java package managers have thrown the towel into the ring, you really can’t use Eclipse without downloading the distribution yourself and use that.

But downloaded Eclipse doesn’t work (very) well in the Wayland/Gnome desktop environment because you have to start Eclipse using a commandline prompt. No icon, it doesn’t show up in the search or applications list. But using the following settings you can make this happen and have your downloaded Eclipse become a first party on your desktop.

Create a tekst file called eclipe.desktop in ~/.local/share/applications/ and add the following contents (modified for your system/installation of Eclipse obviously):

[Desktop Entry]
Name=Eclipse 2021.9

Now all that rests is to update the application database to have this show up in your application searches:

$ update-desktop-database ~/.local/share/applications/

Eclipse icon shows up in search

This was mostly written for myself to remember how to do this, as the search for a tutorial for this was quite difficult.

Installing Windows 10 on a M.2 Drive

20 July 2020

When you want to install Windows 10 on a M.2 NVME drive, you have to make sure you download the correct Windows installation media otherwise the installer won’t discover your M.2 drive.


When you go to the Windows 10 download page it doesn’t tell you in any way that the ISO is completely and utterly unable to install onto a M.2 drive. When you do go down this route–and I urge you not to do that–you will be presented at the beginning of the installation that you need additional drivers.

You have to use the Installation Media Creation Tool using an existing installation of Windows to create an USB installation, not the DVD/ISO.

Fortunately (NOT), Microsoft directs you to the ISO download when you try to access the Media Creation Tool page if you are on a non-Windows operating system, so you assume that the ISO Just Works™.

Don’t be like me, don’t waste 8 hours of your precious time in frustration and just create the USB media installation media.

PS. If all you have is macOS, you can try to download Virtual Box and the Microsoft Edge test virtual machine to run the Installation Media Creation Tool.

A Wicket Hot Summer

2 July 2020

Eclipse Pro Tip: Autocomplete Template for Logging

28 May 2020

In many of my classes I need to add some logging. We use SLF4J for that in our projects. But having to type

Logger<ctrl-space> log = LoggeF<ctrl-space>.getLo<ctrl-space>(MyCla<ctrl-space>.class);

every time gets annoying and tiresome. In many live coding demos folks use templates for autocompletion, so I figured to implement one for this logging.

Eclipse supports autocomplete coding templates (Preferences -> Java -> Editor -> Templates), where you can add the snippet below:

${:import(org.slf4j.Logger,org.slf4j.LoggerFactory)}private static final Logger log = LoggerFactory.getLogger(${enclosing_type}.class);

This template will automatically insert the appropriate imports and fill in the class name for the logger.

I have named this template log, so whenever I type log<ctrl-space> Eclipse will suggest the autocompletion template above.

This probably will save a couple of hours of your life.

Interview on the Bol.com Techlab podcast

14 February 2020

Right before the PostgreSQL meetup Sebastiaan Mannem and myself were interviewed by Peter Paul van de Beek and Peter Brouwers for the Bol.com Techlab Podcast.

This episode has 2 subjects: PostgreSQL migration and performance. The two guests of our show will present during the PostgreSQL User group meetup at bol.com which started shortly after this recording. The topic of the meetup is introduced as PG ConfEU – The Dutch talks.

You can listen to this episode here.

CDI Factory Method

21 January 2020

I’m working in a piece of software that needs a builder inside a loop inside a CDI managed bean, but the builder should be configured using CDI.

E.G. this is part of a JBatch step that processes rows from a ResultSet

public class BananaBuilder {}

public class BananaProcessor extends AbstractBatchlet {
    // ...
    public void processRow(ResultSet rs) {
        BananaBuilder bb = ...

        ..... do something with the bananabuilder

As the BananaBuilder maintains some state that should be reset on each invocation of processRow, this is not an @Inject away: the injected instance of the BananaBuilder will only be injected once in the lifetime of the BananaProcessor instance, but I need it to be fresh on each invocation.

As I invoke processRow myself, I can’t @Inject the builder as a parameter to the method, how would I obtain that particular instance?

A Google search didn’t reveal direct usable results, because factory and CDI are rather popular generic terms together.

The correct way of doing this is either through Instance<BananaBuilder> or CDI.current().select(BananaBuilder.class).get().

So if you want to get a new instance everytime you need a BananaBuilder, then you could inject an Instance into your context:

public class BananaProcessor extends AbstractBatchlet {
    Instance<BananaBuilder> builderInstances;

    public void processRow(ResultSet rs) {
        BananaBuilder bb = builderInstances.get();

        ..... do something with the bananabuilder

This will get a new BananaBuilder on each invocation of the builderInstances.get(), achieving what we wanted. Note that the BananaBuilder needs to have @Dependent scope, which is the default scope if you don’t annotate your bean with a specific scope.


Using CDI to make a new builder inside a loop is not too difficult, but it was a bit hard to discover documentation explaining this.

PostgreSQL Meetup: Converting 85% of Dutch Primary Schools from Oracle to PostgreSQL

17 January 2020

I have been invited to give my talk “CONVERTING 85% OF DUTCH PRIMARY SCHOOLS FROM ORACLE TO POSTGRESQL” at the Amsterdam PostgreSQL meetup on Thursday, 30 January.

You can register for this Meetup here.

The description of the talk:


This case study describes migrating the most used application for primary schools in the Netherlands from Oracle to PostgreSQL. The application uses a multi-tenant, single schema database (i.e. 6000 schools in a single database) and runs using a typical Java EE frontend.

You will learn about our application architecture, hardware platform, reasons for switching, migration strategies considered and the results of our migration.

Since the CFP closed one week before our actual migration we can’t reveal the results in this abstract, but the presentation will capture all the things that went wrong and well

Hope to see you there!

If you want to see the slides, you can find them here.

Converting 85% of Dutch Primary Schools from Oracle to PostgreSQL from Martijn Dashorst

The Future of Jakarta EE in the Wake of JavaEE

7 May 2019

Gentle doctors make stinking wounds – dutch proverb

In the wake of the announcement that Oracle won’t provide the javax namespace to the Jakarta EE community to allow for modifications and future development within the existing APIs, I would like to vent my opinion.

First of all, I’m grateful to Oracle for providing all the standards work, code and documentation to the Jakarta community. This was not a light endeavor, and has cost millions of real money. It is unfortunate that the namespace and JavaEE trademarks are not transferred as well, but looking at what they already have contributed, we have a net win overall.

So what now? Mark Struberg and David Blevins both have given two options:

  1. Big Bang: migrate (mostly) everything in one go (in a next Jakarta release)
  2. On a per-case basis: migrate only when necessary

There is a continuum between those two options: migrate only the most commonly used or preferred APIs (e.g. only microprofile) at first and on demand migrate the rest.

In my opinion a Big Bang release is the way forward, and the big bang should happen with Jarkarta EE 8.

Make Jakarta EE 8 an equivalent standard to JavaEE 8 with the exact same standards, and do the package rename in this release.

Why? Because of clarity, ease of migration for users and the ecosystem as a whole. The future of JavaEE is Jakarta EE, might as well make it official with the proper package names. This will delay the release of Jakarta EE 8, but I don’t think anyone was anxiously to adopt this release as the only change would be a new steward for the standards.

There are some folks that are pushing hard for microprofile as the successor of JavaEE, not Jakarta EE. I don’t agree with them. If you work in the microprofile projects it is easy to see the rest of the world as ancient, e.g. not see the forrest through the trees. An enormous amount of code depends on JavaEE and the evolution of the parts of JavaEE. We are not asking for revolution, but evolution of those components. If you want revolution, then by all means switch to microprofile or quarkus, but those are always suitable for all applications.

Other folks want a clean slate for Jakarta EE, so they can pick and choose from JavaEE for the future. This is effectively the on a per-case basis migration option.

The Java EE ecosystem is very diverse with many different customer profiles, but from my understanding even the glacial users of Java EE are moving ahead towards newer Java EE versions. (At least the dutch IRS and the various banks are migrating away from Java 6 and Java EE 5)

In my company we use a model where we follow the latest release of Wildfly almost immediately. So our production servers are now all running on Wildfly 16, and when Wildfly 17 is released, we will adopt that quickly as well. And we use the full JavaEE profile. So to say we should just start to innovate and migrate our application to Microprofile is ingenious at best.

Given our code base of around 3 million lines of Java code, we should be afraid of any change right? Well, we actually would prefer the big bang approach. A package rename is something we can manage in a week or so, would not lead to massive retesting and would land in one sprint on our production servers. From there we can build forward on the new Jakarte EE platform and continue to follow the Jakarate EE standards.

In short: please do a big bang release of Jakarta EE 8 including the package rename.

A Better HTML Autofocus Implementation

6 November 2018

So you have this login form with a username and password field. Typically you want the username to be pre-filled and the password to be empty for a password manager to fill or to type in. It would be nice if the password field receives the focus so you can start typing away and press to submit the login form.

But often the username field is also empty. And if that is the case, you want the userfield to have the focus.

Your first pick is to add the autofocus attribute to both fields:

<label for="username">Username:</label>
<input id="username" type="text" name="username" autofocus>

<label for="password">Password:</label>
<input id="password" type="password" name="password" autofocus>

But in some browsers this focuses the username field regardless of its contents, and in other browsers the password field is focussed, even though the username field is not filled.

It appears that the behavior of two fields with autofocus is not specified.

In one of my applications I solved this problem with a little JavaScript snippet that runs when the DOM is ready.


This requires JQuery to be in your page, but if you want the Vanilla JS version, the line below will do the same:

document.querySelector('input[value=""]:enabled:not([style*="display: none"]):not([style*="display:none"]):first-child').focus()

This will filter the disabled, invisible, empty fields and select the first field to put the focus on.

If you are an Apache Wicket user, you can easily add this to your page using a header item, in particular the OnDomReadyHeaderItem, in the renderHead() method of your page.

Enjoy the best autofocus behavior you could wish for.

Generate a User Manual - With Your Tests

6 July 2017

This article was published in Java Magazine, 4th edition of 2017 in Dutch. This is a augmented Google Translate version of that article.

“Do we have a manual for the users already?” Asks the product owner. As your teammates dive under their desks, you stammer something about no time, varying priorities and the endless backlog. Without success of course, so you are the volunteer to provide the 96-page Word document with 149 screenshots. Four sprints later, the application no longer looks like the screenshots and you can start all over again.

But it can be different! After all, we stepped into this field to automate time-consuming human activities, to have time left to do fun, valuable things. We also like to write software and solve complex problems.

If I tell you that you can take screenshots automatically with the right development tools, you can integrate them into a nicely formatted manual in HTML and PDF format, and you can test your application at the same time, will I have your attention?

In this article I will show you how to generate an always up-to-date manual with AsciiDoctor, Graphene, Arquillian and aShot. It solves an actual problem, it consists of a lot of programming work, it can become as complex as you want and is also fun. To illustrate this, I use a simple project: an online cheese shop.

The Cheesr Online Cheese Shop

The cheese shop ‘Cheesr’ was invented in 2005 for the book Wicket in Action, because my co-author Eelco really missed the cheese shop “De Brink” in Deventer when he emigrated to the US. Cheesr is a standard web application with two screens: selecting cheeses and placing an order. In figure 1 you see a number of pages of the manual.

Figure 1: Cheesr Manual Figure 1. Some example pages from the Cheesr Manual

The cheese shop uses Apache Maven to connect the various components of the building process. Figure 2 illustrates the steps that are taken to get from the source code to a web application, containing the manual bundled together.

Figure 2: The build process Figure 2. The phases and files that play a role in building the cheese shop and the manual

After the source code has been compiled, the tests are run. Arquillian ensures that the web application is started and controls the browser with WebDriver . Thanks to Graphene , we can describe HTML pages as Java objects and use them to perform the tests. The library aShot makes the screenshots and puts these files in the folder target / screenshots. You will learn more about these technologies later on.

In the “pre-package” phase of the construction process, just before the web application is assembled by Maven, AsciiDoctor converts the manual from AsciiDoc to HTML and PDF. Ultimately, the web application is ready and there is also the manual as a PDF and as an HTML file.

As an example, the code in Listing 1 tests that the checkout button is not available, if you do not have an item in your shopping cart yet. In addition, the test also makes the first screenshots immediately.

Listing 1. The first test of the cheese shop

public class IndexTest {
    @Deployment (testable = false)
    public static WebArchive createDeployment () {
        return CheesrDeployment.createWar ();

    private WebDriver browser;

    public void step1EmptyCart (@InitialPage GIndex index) {
        Camera.takeScreenshot (browser, "cheesr-1-home.png");
        Camera.takeScreenshot (browser, "cheesr-1-home-navigator.png", By.id ("navigator"));

        // nothing was selected, so the checkout should not be present
        assertFalse (index.checkoutPresent ());

The annotation @RunWith(Arquillian.class) indicates that this test case should be run with Arquillian. With Arquillian you can perform integration tests, start and stop servers and deploy your application code. You can also use Arquillian to run your tests against an already running remote server, so you do not have to set it up entirely from your tests.

The createDeployment method with the @Deployment annotation tells Arquillian what needs to be deployed to the server: a WAR in our case, containing our application code.

The @Drone WebDriver browser field is an instruction for Arquillian to inject an instance of WebDriver in the test case, in order to control the browser.

The @InitialPage GIndex index parameter of the test method indicates that this test should start with the GIndex page. GIndex is a representation of the interaction possibilities of the Cheesr homepage: an implementation of the Page Object Pattern. This class includes the location (URL) to which the browser should be sent. Graphene ensures that the GIndex instance is created correctly and that the browser loads the page just before the test has started. You can immediately interact with that page, such as taking screenshots, clicking on links and asking if certain elements are present.

For now it is useful to see how the screenshots of this test are used in the text of the manual. A piece of AsciiDoc from Listing 2 is an example of this.

Listing 2. A piece of AsciiDoc from the cheese shop manual.

== Buying Cheese

When you point your browser to our store front using the URL _http://example.com_.
This will show you the page from <<cheesr-home>>.

[[cheesr-home, figure 1]]
.The home page of the Cheesr store.

You can browse the cheeses using the navigator at the bottom of the page (<<cheesr-navigator>>).

[[cheesr-navigator, figure 2]]
.The navigator to discover even more cheese!

When you have found the cheese of your liking, you can add it to your shopping cart using the _add_ link.
This will add the cheese to the cart, as shown in <<cheesr-cart>>.

The test case from Listing 1 generates screenshots with names such as “cheesr-1-home.png”. The AsciiDoc of Listing 2 picks it up in image elements such as image::cheesr-1-home.png[]. Finally the result will look like the manual of Figure 1.

AsciiDoc and AsciiDoctor

AsciiDoc is a text format for writing documents. AsciiDoc is very similar to the widely used Markdown, but offers more options: includes, table of contents, automatic numbering of sections, warnings, annotations on code samples and much more.

AsciiDoctor is a project that can process AsciiDoc files and convert into PDF, HTML, EPub and DocBook. There is a commandline tool, which you can call directly, but also plug-ins for Maven and Jekyll (a static website generator). AsciiDoctor also integrates with chart tools such as PlantUML and GraphViz. This allows you to describe UML diagrams in plain text.

AsciiDoc is an ideal format for storing in a Git repository, because it is a plain text format. You can keep your your documentation up-to-date together with the code of your project, and even include it in code reviews (did you update the manual?)

The AsciiDoc content of the sample project (in which this article is also written) looks like this:

├── artikel.adoc
├── cheesr.adoc
└── images
    ├── build-process.png
    ├── cheesr-cover.jpg
    ├── cheesr-manual.jpg
    └── pageobjects.png

With these documents AsciiDoctor can generate the manual and include the screenshots (from target/screenshots) in the manual. The screenshots are taken during the execution of the application tests. This has been set up using Arquillian, WebDriver and Graphene.

WebDriver and Graphene

WebDriver is a technology for controlling and reading browsers. This can produce fragile code if you are not paying attention: changes in the structure of the HTML in the browser can easily make your tests fail. Graphene is a shell around WebDriver to describe your application at a higher level of abstraction by encapsulating the (interaction with) HTML by enabling the Page Object pattern.

Instead of looking for a link in the HTML document to add a cheese to the shopping cart, you call the addCheese method on your page object. Of course behind the scenes, the search in the HTML goes to the link in question, but that is hidden from your tests. Figure 3 shows the difference between working directly with the WebDriver API (and with it the HTML) and working with a Page Objects API.

WebDriver API vs Page Objects Figure 3. The difference between the WebDriver API and Page Objects

The use of page objects makes your tests much easier to read. The code in Listing 3 gives an example of the difference between the WebDriver API and the use of Page Objects.

Listing 3. Example of working with the WebDriver API and Page Objects

// WebDriver code:

List <WebElement> addLinks = browser

addLinks.stream ()
    .filter(e -> e.getAttribute("id").contains("edam"))


// page objects code:


Assert.assertTrue(index.checkoutIsPresent ());

In the example of the page objects code the intention of the test is immediately clear, while that in the WebDriver code is hidden in pitting the HTML structure. Of course this is simply moving the WebDriver logic to a façade, but that is precisely the essence of the Page Object pattern.

The code in Listing 4 shows the implementation of the GIndex page object.

Listing 4. A Page Object for the Cheesr homepage

public class GIndex {
    private List <GrapheneElement> addLinks;

    private GrapheneElement checkout;

    public void addCheese(String cheese) {
        addLinks.stream ()
            .filter(a -> a.getAttribute ("id").contains(safeCheeseId(cheese)))
            .orElseThrow(() -> new NoSuchElementException("Add" + cheese + "link"))

    public boolean checkoutPresent() {
        return checkout.isPresent();

    public By byCart() {
        return By.cssSelector("div[id=cart], input[type=button]");

The code starts with @Location to tell where the page is located in the application: "" means the root. From there all the links are collected, which can add a cheese to the shopping cart. Finally, the addCheese method offers the possibility to add a cheese based on the name by clicking on the corresponding link.

Thanks to Graphene, you can easily apply the Page Objects pattern to create a model of your application, so that your tests remain readable and maintainable. Now we still have to actually make the screenshots. The library aShot is made to make screenshots with WebDriver.

Screenshots with aShot

aShot has a fairly simple but powerful API. You only have to provide aShot with the WebDriver and the element you want to make the screenshot of. aShot will give you the picture. You can do some extra things and so it is useful to wrap it in a function that you can call from your tests.

You can also specify more web elements, of which a picture has to be taken. In addition, it is also possible to add extra space to the elements and you can release filters on the surrounding area, such as black-white or blur (blur) of the edge. The code in Listing 5 shows how you can achieve this with aShot.

Listing 5. Takes a screenshot of specific elements in the page

public static void takeScreenshot(WebDriver browser, String name, By crop) {
    IndentCropper cropper = new IndentCropper(25)
        .addIndentFilter(new MonochromeFilter());

    BufferedImage screenshot = new AShot()
        .takeScreenshot(browser, browser.findElements(crop))

    saveScreenshot(name, screenshot);

Making a screenshot has become very simple with this function. Now we can save screenshots during testing and use them in our manual (see Listing 6).

Listing 6. A test that uses the page object GIndex and takes screenshots

public void step2AddToEmptyCart(@InitialPage GIndex index) {
    Camera.takeScreenshot(browser, "cheesr-2-cart-0-empty.png", index.byCart());
    index.addCheese ("edam");
    Camera.takeScreenshot (browser, "cheesr-2-cart-1-edam.png", index.byCart());

    // assert that the checkout button is present

An example of the screenshots taken in this test is shown in Figure 4. ! Screenshots of listing 6 Figure 5. Two screenshots of the shopping cart

This completes the article. We can now test the application, take screenshots and generate a manual.


We have one integrated whole, because the documentation lives together with the code in AsciiDoc format. We test our application via the browser, thanks to Arquillian and Graphene. During the execution of the tests we make screenshots and focus on exactly those parts that are important with aShot. Maven combines all these steps into one smooth process, which ultimately incorporates our up-to-date manual into our application.

With this setup you can also create internationalized screenshots for multilingual manuals: put your application in another language and perform the tests again. Now only someone willing to translate that 96 pages of text to translate …

View all code on Github

The source code is available on Github: https://github.com/dashorst/nljug-article-2017. It is a Maven project, so you can import it directly into your favorite IDE. The README.md contains instructions for building and running the project. In this article I do not dwell on the specific versions and configuration of plug-ins and dependencies. You can find this in the project.

Is Blendle the Savior of Traditional Media?

3 May 2015

Ever since we invited Alexander Klöpping to our company’s conference as a keynote speaker I have been following his endeavors. He’s a busy fellow and does some amazing stuff. He’s created the University of the Netherlands, a televised series of lectures rich in media by renowned professors. And he created a startup that is shaking up old media.


Blendle strives to revolutionize the way you consume news using micropayments in a sensible way. Blendle has contracted with several traditional newspapers and magazines (mostly in the Netherlands and Belgium), and is now increasing its reach across the atlantic ocean with content from the Wall Street Journal and the New York Times.

The idea is that you can read an article for a small amount of money–a micro-payment. So one article could set you back €0.19 and another €0.59. The price depends on the publisher, the content and demand.

Curated content combined with technology

The site features a lot of curated content and automated trending content. Of course any article you read is accompanied by a list of related articles.

Blendle’s setup wizard lets you create your own curated list of publications. The publications you have selected will be featured in a section called “My Blendle” and will list the most popular articles. You can also select categories that will form the top menu bar and the site will select the trending and popular articles for each category:

Blendle top menu bar showing categories

For example I picked “Tech”, “Big Interviews”, “Foreign”, “Science”, “Media” and “Education” as my categories, as shown in the prior image.

When you want “Share on the Interwebs” Blendle has you covered, allowing you to share links to articles through social media (Twitter, Facebook and LinkedIn), the platform itself or through email.

Engage more with the daily newsletters

You’ll recieve a curated list of articles of all morning editions in your inbox. You’ll also receive a message when a new issue of a magazine is published, and one message at the end of the week with an overview of the most important and popular articles.

Blende daily newsletter

Of course you can opt out of these messages with a familiar “Recieve too much mail from us? Click here to unsubscribe of this daily newsletter”. The preferences page on their site has several options for customizing your need to stay informed.

Each daily message ends with a nice “Psst, this is also noteworthy” suggestion for some tongue in cheek content, making the newsletter bit more entertaining. For example:

Pssssssst! Eva had an affair with a co-worker just after getting married. The affair stopped and her marriage was saved, but then Eva ran into her co-worker by chance

Of course this is click bait, but the Blende team appears to use it as a form of satire rather than a monetization strategy.

Readers can have their cake and eat it too

The platform is thought through very well: when you are already a subscriber to a participating newspaper you get all their articles for free on blendle–you already paid for them. You still get access to all other articles from other publishers, and there’s no risk of paying for an article twice.

Buy an issue or use an existing subscription with Blendle

When you read an article you can get the whole issue where it appeared in (e.g. the saturday newspaper) at a discount: the amount you already spent on the articles of that issue. While I haven’t tried it, it wouldn’t surprise me that when you buy enough articles to offset the cost of the complete issue, it unlocks the issue automatically.

Satisfaction guarantee or your money back

When you read an article but don’t like it, or the price doesn’t fit the article in your opinion, Blendle will gladly refund you. You’re only asked a simple question: why you want your money back. Blendle provides you with a list of prepared answers, or provide your own feedback for the author and publisher.

Satisfaction Guarantee or your money back

The ease of getting a refund makes it risk free for clicking on articles. The price of newspaper articles is low enough to ignore the cost and are pretty much on par what you’d want to pay. Articles from magazines (looking at you Viva) are often priced too high in my short experience with the platform.

Blendle also released an app for mobile called “Blendle Trending”. It currently only shows the curated list of trending articles, and your reading list. It was launched as a single day public beta on the App Store for iOS. The app works, but is very limited in its use when compared to the web site. I expect the app to become the major interaction vector for Blendle really soon.

Awesome design

The design of the website (and app) is gorgeous. The articles look good, are nicely typesetted using good readable fonts not unlike the original publication’s type face. In fact the website loads about 45 different type faces in multiple weights.

The gorgeous design of Blendle

The user experience is great too. The tone of voice is familiar, friendly. It might be a bit too familiar for some folks, but it didn’t bother me. As the site is aimed at the 20 something crowd I think they hit the sweet spot.


The Blendle team has created a really interesting platform for the future of publications, enticing youngsters to read background articles that were previously hidden inside old media. Who has time to read magazines, news papers and other publications in our age of 140 character snippets? Who even has time to read 140 character snippets when a couple of 😜🎓✈️ will do?

Blendle has crafted a great experience to entice not only the youngsters but also the old media. Springer and the New York Times are on board as investors and have their publications available through the site. The team has grown from 3 to 40 in a very short time and is one of the Netherlands’ most successful start ups.

I’m just a recent user of Blendle, but I’ll keep checking their site and application for more interesting news every day, consuming it a micropayment at a time.

How to keep YouTube working on your first gen iPad

26 April 2015

YouTube is shutting down version 2 of its API, effecitvely killing the support for devices that have YouTube apps that haven’t been updated to use the newer version. This includes many ‘Smart’ TVs, and of course the first iPad.

The YouTube API is used to search for, and serve videos to your devices. The version 2 of the API provided limited ad support to YouTube, which makes the site run. That combined with a declining number of users of the old API version, made YouTube decide to shut down the old version. This saves on a lot of maintenance on their part.

This decision leaves many parents without a working YouTube app on their old, passed down iPad 1s. The first iPad is stuck at iOS 5.1 and starting from iOS 6 Apple no longer provides their own YouTube app, nor will Apple update the old YouTube app to use the newer version of the YouTube API.

YouTube suggests using the mobile version of their website, but considering that the Safari browser on the first iPad is also no longer maintained and has many unfixed security issues, this is not a good path to take.

Fortunately there seems to be a workaround to install an older version of the official YouTube app on your iPad 1:

  1. purchase the official YouTube app on a supported device
  2. on your iPad 1 go to App Store » Purchased
  3. search for YouTube
  4. click the download button

Your iPad will ask you to download an older version of the app, because the current version requires iOS 6.

  1. Confirm the download of the older version
  2. $$$ profit

This will install a new YouTube app on your iPad with an updated (but older) YouTube icon (white shield with black “You” and a red button with white “Tube”).

Enjoy while it lasts!

Improve Error Reporting in Java EE

9 March 2015

While I am a great advocate of adopting standards and have come to dislike ducttaped together solutions of the years, I still have some qualms with Java (EE) in general and WildFly in particular.

My number one dislike is the error reporting during deployment. These are inscrutable, unsolvable, unhelpful messages that are complete and utter gibberish. If there should be one fricking standard that should be adopted for Java EE 8 it should be better, standardized error reporting.

Exhibit A:

14:47:37,980 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-15) MSC000001: Failed to start service jboss.deployment.subunit."eduarte-deliverable-dev-ear.ear"."eduarte-common-dao-2.42-SNAPSHOT.jar".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.subunit."eduarte-deliverable-dev-ear.ear"."eduarte-common-dao-2.42-SNAPSHOT.jar".PARSE: JBAS018733: Failed to process phase PARSE of subdeployment "eduarte-common-dao-2.42-SNAPSHOT.jar" of deployment "eduarte-deliverable-dev-ear.ear"
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:166) [wildfly-server-8.1.0.Final.jar:8.1.0.Final]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948) [jboss-msc-1.2.2.Final.jar:1.2.2.Final]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881) [jboss-msc-1.2.2.Final.jar:1.2.2.Final]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [jrebel-bootstrap-a53fd962a83484078782af30d2cabc06.jar:1.8.0_31]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [jrebel-bootstrap-a53fd962a83484078782af30d2cabc06.jar:1.8.0_31]
	at java.lang.Thread.run(Thread.java:745) [jrebel-bootstrap-a53fd962a83484078782af30d2cabc06.jar:]
Caused by: org.jboss.msc.service.ServiceNotFoundException: Service service jboss.ejb.default-resource-adapter-name-service not found
	at org.jboss.msc.service.ServiceContainerImpl.getRequiredService(ServiceContainerImpl.java:668) [jboss-msc-1.2.2.Final.jar:1.2.2.Final]
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.getDefaultResourceAdapterName(MessageDrivenComponentDescriptionFactory.java:278)
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.processMessageBeans(MessageDrivenComponentDescriptionFactory.java:155)
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.processAnnotations(MessageDrivenComponentDescriptionFactory.java:82)
	at org.jboss.as.ejb3.deployment.processors.AnnotatedEJBComponentDescriptionDeploymentUnitProcessor.processAnnotations(AnnotatedEJBComponentDescriptionDeploymentUnitProcessor.java:58)
	at org.jboss.as.ejb3.deployment.processors.AbstractDeploymentUnitProcessor.deploy(AbstractDeploymentUnitProcessor.java:81)
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:159) [wildfly-server-8.1.0.Final.jar:8.1.0.Final]
	... 5 more

And a couple of hundred lines below:

14:47:42,608 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) JBAS014613: Operation ("deploy") failed - address: ([("deployment" => "eduarte-deliverable-dev-ear.ear")]) - failure description: {"JBAS014671: Failed services" => {"jboss.deployment.subunit.\"eduarte-deliverable-dev-ear.ear\".\"eduarte-common-dao-2.42-SNAPSHOT.jar\".PARSE" => "org.jboss.msc.service.StartException in service jboss.deployment.subunit.\"eduarte-deliverable-dev-ear.ear\".\"eduarte-common-dao-2.42-SNAPSHOT.jar\".PARSE: JBAS018733: Failed to process phase PARSE of subdeployment \"eduarte-common-dao-2.42-SNAPSHOT.jar\" of deployment \"eduarte-deliverable-dev-ear.ear\"
    Caused by: org.jboss.msc.service.ServiceNotFoundException: Service service jboss.ejb.default-resource-adapter-name-service not found"}}
14:47:42,642 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-portal-eo-student.war" (runtime-name : "eduarte-portal-eo-student.war")
14:47:42,642 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-portal-eo-ouder.war" (runtime-name : "eduarte-portal-eo-ouder.war")
14:47:42,642 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-portal-eo-docent.war" (runtime-name : "eduarte-portal-eo-docent.war")
14:47:42,642 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-portal-eo-bedrijf.war" (runtime-name : "eduarte-portal-eo-bedrijf.war")
14:47:42,643 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-portal-eo-authenticator.war" (runtime-name : "eduarte-portal-eo-authenticator.war")
14:47:42,643 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "eduarte-deliverable-dev-ear.ear" (runtime-name : "eduarte-deliverable-dev-ear.ear")
14:47:42,644 INFO  [org.jboss.as.controller] (Controller Boot Thread) JBAS014774: Service status report
JBAS014777:   Services which failed to start:      service jboss.deployment.subunit."eduarte-deliverable-dev-ear.ear"."eduarte-common-dao-2.42-SNAPSHOT.jar".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.subunit."eduarte-deliverable-dev-ear.ear"."eduarte-common-dao-2.42-SNAPSHOT.jar".PARSE: JBAS018733: Failed to process phase PARSE of subdeployment "eduarte-common-dao-2.42-SNAPSHOT.jar" of deployment "eduarte-deliverable-dev-ear.ear"

14:47:42,653 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015961: Http management interface listening on
14:47:42,653 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015951: Admin console listening on
14:47:42,654 ERROR [org.jboss.as] (Controller Boot Thread) JBAS015875: WildFly 8.1.0.Final "Kenny" started (with errors) in 22967ms - Started 1208 of 1315 services (4 services failed or missing dependencies, 225 services are lazy, passive or on-demand)
14:47:42,936 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-13) JBAS015974: Stopped subdeployment (runtime-name: eduarte-common-dao-2.42-SNAPSHOT.jar) in 38ms
14:47:42,942 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-7) JBAS015974: Stopped subdeployment (runtime-name: eduarte-ws-eo-rest-2.42-SNAPSHOT.war) in 44ms
14:47:43,054 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-6) JBAS015974: Stopped subdeployment (runtime-name: eduarte-sis-web-main.war) in 156ms
14:47:43,088 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-9) JBAS015877: Stopped deployment eduarte-deliverable-dev-ear.ear (runtime-name: eduarte-deliverable-dev-ear.ear) in 195ms
14:47:43,136 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 2) JBAS018558: Undeployed "eduarte-deliverable-dev-ear.ear" (runtime-name: "eduarte-deliverable-dev-ear.ear")
14:47:43,137 INFO  [org.jboss.as.controller] (DeploymentScanner-threads - 2) JBAS014774: Service status report
JBAS014777:   Services which failed to start:      service jboss.deployment.subunit."eduarte-deliverable-dev-ear.ear"."eduarte-common-dao-2.42-SNAPSHOT.jar".PARSE

14:47:47,715 WARN  [org.jboss.as.server.deployment.scanner] (DeploymentScanner-threads - 2) JBAS015002: Deployment of 'eduarte-web-main.war' requested, but the deployment is not present
14:47:47,715 INFO  [org.jboss.as.server.deployment.scanner] (DeploymentScanner-threads - 2) JBAS015003: Found eduarte-deliverable-dev-ear.ear in deployment directory. To trigger deployment create a file called eduarte-deliverable-dev-ear.ear.dodeploy

Apparently something is missing, but the stack traces don’t contain any code that is under my purview, and the error message is really clear and helpful in identifying what is missing:

MSC000001: Failed to start service jboss.deployment.subunit.”eduarte-deliverable-dev-ear.ear”.”eduarte-common-dao-2.42-SNAPSHOT.jar”. PARSE: org.jboss.msc.service.StartException in service jboss.deployment.subunit.”eduarte-deliverable-dev-ear.ear”.”eduarte-common-dao-2.42-SNAPSHOT.jar”. PARSE: JBAS018733: Failed to process phase PARSE of subdeployment “eduarte-common-dao-2.42-SNAPSHOT.jar” of deployment “eduarte-deliverable-dev-ear.ear”

Which is caused by:

org.jboss.msc.service.ServiceNotFoundException: Service service jboss.ejb.default-resource-adapter-name-service not found

What the hell is Wildfly trying to tell me?

The solution (of course) is to run the configuration script that is external to our application, in order to tell Wildfly that a new message queue should be added:

15:03:30,952 WARN  [org.jboss.messaging] (management-handler-thread - 2) JBAS011618: There is no resource matching the expiry-address jms.queue.ExpiryQueue for the address-settings #, expired messages from destinations matching this address-setting will be lost!
15:03:30,952 WARN  [org.jboss.messaging] (management-handler-thread - 2) JBAS011619: There is no resource matching the dead-letter-address jms.queue.DLQ for the address-settings #, undelivered messages from destinations matching this address-setting will be lost!
15:03:31,033 WARN  [org.jboss.as.messaging] (MSC service thread 1-16) JBAS011600: AIO wasn't located on this platform, it will fall back to using pure Java NIO. If your platform is Linux, install LibAIO to enable the AIO journal
15:03:31,149 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221000: live server is starting with configuration HornetQ Configuration (clustered=false,backup=false,sharedStore=true,journalDirectory=/Users/dashorst/Workspaces/luna/wildfly-8.1.0.Final/standalone/data/messagingjournal,bindingsDirectory=/Users/dashorst/Workspaces/luna/wildfly-8.1.0.Final/standalone/data/messagingbindings,largeMessagesDirectory=/Users/dashorst/Workspaces/luna/wildfly-8.1.0.Final/standalone/data/messaginglargemessages,pagingDirectory=/Users/dashorst/Workspaces/luna/wildfly-8.1.0.Final/standalone/data/messagingpaging)
15:03:31,151 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221006: Waiting to obtain live lock
15:03:31,222 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221013: Using NIO Journal
15:03:31,288 INFO  [io.netty.util.internal.PlatformDependent] (ServerService Thread Pool -- 72) Your platform does not provide complete low-level API for accessing direct buffers reliably. Unless explicitly requested, heap buffer will always be preferred to avoid potential system unstability.
15:03:31,359 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221043: Adding protocol support CORE
15:03:31,372 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221043: Adding protocol support AMQP
15:03:31,380 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221043: Adding protocol support STOMP
15:03:31,427 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221034: Waiting to obtain live lock
15:03:31,428 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221035: Live Server Obtained live lock
15:03:31,610 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221007: Server is now live
15:03:31,610 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 72) HQ221001: HornetQ Server version 2.4.1.Final (Fast Hornet, 124) [1141831d-c665-11e4-86db-0ffb92fb33f7] 
15:03:31,666 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 72) JBAS011601: Bound messaging object to jndi name java:/messaging/ConnectionFactory
15:03:31,672 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 73) HQ221003: trying to deploy queue jms.queue.eventQueue
15:03:31,785 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 73) JBAS011601: Bound messaging object to jndi name queue/eventQueue
15:03:31,820 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-10) JBAS010406: Registered connection factory java:/messaging/JmsXA
15:03:31,908 INFO  [org.hornetq.ra] (MSC service thread 1-10) HornetQ resource adaptor started
15:03:31,908 INFO  [org.jboss.as.connector.services.resourceadapters.ResourceAdapterActivatorService$ResourceAdapterActivator] (MSC service thread 1-10) IJ020002: Deployed: file://RaActivatorhornetq-ra
15:03:31,910 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-5) JBAS010401: Bound JCA ConnectionFactory [java:/messaging/JmsXA]
15:03:31,910 INFO  [org.jboss.as.messaging] (MSC service thread 1-16) JBAS011601: Bound messaging object to jndi name java:jboss/DefaultJMSConnectionFactory

Unfortunately the original error message did not provide any useful insights as to what was missing.


20 February 2015

A list of all active space probe missions including mission details.

New Horizons Rosetta
The New Horizons and Rosetta missions

Via @kottke

Brianna Wu Risking Life versus Gamergate

20 February 2015

Unreal what happens to Brianna Wu. I admire her will to continue to fight the good fight and I hope she’ll be safe. Nobody deserves what she has to go through. This is from 9 days ago:

This weekend, a man wearing a skull mask posted a video on YouTube outlining his plans to murder me. I know his real name. I documented it and sent it to law enforcement, praying something is finally done. I have received these death threats and 43 others in the last five months.

Read the whole article and be appalled at the threats and vitriol she recieves for being a woman in tech.

IBM Design Language

10 February 2015

Who’d thought: IBM talking design.

“Living Language – A shared vocabulary for design”

Via Daring Fireball

Designing a Backend for a Famous Backend

10 February 2015

When Kim Kardashian and Paper magazine set out to break the internet, it was the task of one engineer to make sure that Paper’s backend systems wouldn’t crumble under the weight of Kim’s backend.

Hosting that butt is an impressive feat. You can’t just put Kim Kardashian nudes on the Internet and walk away —that would be like putting up a tent in the middle of a hurricane. Your web server would melt. You need to plan.

And the understatement of the year:

“We may get a lot of traffic.”

Rather interesting read how to scale a system with mostly static content, but that will get hammered by millions within a few hours.