<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.1.1">Jekyll</generator><link href="https://illegalexception.schlichtherle.de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://illegalexception.schlichtherle.de/" rel="alternate" type="text/html" /><updated>2021-09-09T18:11:42+02:00</updated><id>https://illegalexception.schlichtherle.de/feed.xml</id><title type="html">IllegalException</title><subtitle>A blog about computer science and open source software.</subtitle><entry><title type="html">Monitoring a secure Wi-Fi network using Wireshark on macOS</title><link href="https://illegalexception.schlichtherle.de/networking/2020/03/09/monitoring-a-secure-wi-fi-network-using-wireshark-on-macos/" rel="alternate" type="text/html" title="Monitoring a secure Wi-Fi network using Wireshark on macOS" /><published>2020-03-09T00:00:00+01:00</published><updated>2020-03-09T00:00:00+01:00</updated><id>https://illegalexception.schlichtherle.de/networking/2020/03/09/monitoring-a-secure-wi-fi-network-using-wireshark-on-macos</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/networking/2020/03/09/monitoring-a-secure-wi-fi-network-using-wireshark-on-macos/">&lt;p&gt;Do you have a secure Wi-Fi network and a computer running macOS?
Do you want to monitor the traffic of the devices connected to this network?
Look no further, here’s how to do this step by step using &lt;a href=&quot;https://www.wireshark.org&quot;&gt;Wireshark&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Wireshark is a very popular network protocol analyzer which understands many different network protocols out of the box
and shows their data packets in a powerful GUI.
The following instructions are based on macOS Mojave 10.14.6 running Wireshark 3.2.2 and may not work without changes
for other versions of these software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt;
Monitoring other people’s network traffic is a very bad idea and may be forbidden or even illegal in your case!
Therefore, you may only apply these instructions if you own the Wi-Fi network and all its connected devices and you
are the only user and no legal impediments whatsoever apply!&lt;/p&gt;

&lt;h2 id=&quot;installing-wireshark&quot;&gt;Installing Wireshark&lt;/h2&gt;

&lt;p&gt;I recommend to use &lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt; for this step because it makes installing and updating Wireshark really
simple.
In case you don’t already use it, Homebrew is a very popular package manager for macOS.
Follow the instructions on their home page for installation.
Once Homebrew is installed, you can install Wireshark in Terminal using&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew cask install wireshark
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wireshark provides an integrated update feature via the menu item &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Help&lt;/code&gt; » &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Check for Updates...&lt;/code&gt;.
However, this doesn’t work for me because nothing ever happens, so I simply upgrade it using&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew cask reinstall wireshark
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you ever need to uninstall Wireshark, you can do that using (you guessed it)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew cask uninstall wireshark
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Any configuration data is saved in your home directory and will be preserved on re-installation or un-installation.&lt;/p&gt;

&lt;h2 id=&quot;obtaining-the-wi-fi-secret&quot;&gt;Obtaining The Wi-Fi Secret&lt;/h2&gt;

&lt;p&gt;Assuming that your Wi-Fi network is secured using WPA-Personal alias
&lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)&quot;&gt;WPA-PSK&lt;/a&gt;,
it is protected by a pre-shared key or even a password from which the pre-shared key gets derived.
You need to copy this secret into the clipboard from which you will paste it into the Wireshark configuration later.&lt;/p&gt;

&lt;p&gt;Further assuming that your Mac is already connected to this network, its secret is stored in the keychain.
Otherwise, please connect your Mac to this network first.&lt;/p&gt;

&lt;p&gt;In Terminal, launch the keychain management GUI using&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;open -a 'keychain access'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the search box on the top right of the keychain window, enter the name of your Wi-Fi network, i.e. its SSID.
In my case it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FRITZ!Box 7530 CT&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2020/03/keychain-window.png&quot; alt=&quot;Keychain Access Main Window&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Right click on the list item, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Copy Password To Clipboard&lt;/code&gt; and enter your password in the upcoming dialog box.&lt;/p&gt;

&lt;h2 id=&quot;configuring-the-wi-fi-secret-in-wireshark&quot;&gt;Configuring The Wi-Fi Secret In Wireshark&lt;/h2&gt;

&lt;p&gt;In Terminal, launch Wireshark using&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;open -a wireshark
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After startup, the main window shows you all the interfaces from which you may capture network traffic:&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2020/03/wireshark-startup-1072x0902.png&quot;&gt;
        &lt;picture&gt;
            &lt;img src=&quot;/assets/2020/03/wireshark-startup-0536x0451.png&quot; title=&quot;Wireshark Startup Window&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Ignore this for now and select the menu item &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Wireshark&lt;/code&gt; » &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Preferences...&lt;/code&gt; instead.
In the dialog, unfold the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Protocols&lt;/code&gt; item and scroll down to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IEEE 802.11&lt;/code&gt; for Wi-Fi:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2020/03/wireshark-wifi-prefs.png&quot; alt=&quot;Wireshark Wi-Fi Preferences&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Edit...&lt;/code&gt; button to launch the following dialog:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2020/03/wireshark-wifi-secrets.png&quot; alt=&quot;Wireshark Wi-Fi Secrets&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; button to start entering the secret.
In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Key type&lt;/code&gt; column, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa-psk&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa-pwd&lt;/code&gt;, depending on if your secret is a pre-shared key or a password,
respectively.
If you are not sure, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa-psk&lt;/code&gt; first, paste the secret in the clipboard into the form using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command-v&lt;/code&gt; and check
if the dialog accepts it.
Otherwise, select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa-pwd&lt;/code&gt; and repeat.
You can enter secrets for multiple Wi-Fi networks, allowing you to monitor them all concurrently.
Once done, close all dialogs.&lt;/p&gt;

&lt;h2 id=&quot;capturing-the-wi-fi-network-traffic&quot;&gt;Capturing The Wi-Fi Network Traffic&lt;/h2&gt;

&lt;p&gt;Now that you have configured the secret for your Wi-Fi network, you can start monitoring the traffic of all connected
devices.
Select the menu item &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Capture&lt;/code&gt; » &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Options...&lt;/code&gt; to launch the following dialog:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2020/03/wireshark-capture-options.png&quot; alt=&quot;Wireshark Capture Options&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In here, select the list item representing your Wi-Fi interface and double click on the column &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Link-layer Header&lt;/code&gt;.
In the popup menu, select the list item &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Per-Packet Information&lt;/code&gt;.
This will prevent a lot of false positive network error messages later. 
Most importantly, make sure to check the box &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Monitor&lt;/code&gt; or otherwise you will only ever see network traffic which is
intended for your local computer.
After clicking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Start&lt;/code&gt;, Wireshark starts monitoring all packets from all Wi-Fi networks on the current channel within
the reach of your computer’s antenna - not just your network.
In the following capture, the beacon packets are from three different Wi-Fi networks on the current channel:&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2020/03/wireshark-802.11-1920x1200.png&quot;&gt;
        &lt;picture&gt;
            &lt;img src=&quot;/assets/2020/03/wireshark-802.11-0960x0600.png&quot; title=&quot;Wireshark Showing 802.11 Packets&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Initially, Wireshark shows 802.11 protocol packets only, so you won’t see packets from other protocols like ICMP, TCP or
HTTP.
This is because with WPA2, the 802.11 protocol packets are actually encrypted with a session key which gets derived from
the pre-shared key, which in turn may be derived from the password. 
Wireshark has to learn the session key first by observing the exchange of a sequence of packets known as the
&lt;a href=&quot;https://en.wikipedia.org/wiki/IEEE_802.1X&quot;&gt;EAPOL handshake&lt;/a&gt;.
It will do so automatically for any secure Wi-Fi network who’s secrets you have configured.
So all you have to do is to trigger the EAPOL handshake between the access point and the connected device, and the
easiest way to do this is to switch its Wi-Fi interface off and on again.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2020/03/wireshark-eapol-http-1920x1200.png&quot;&gt;
        &lt;picture&gt;
            &lt;img src=&quot;/assets/2020/03/wireshark-eapol-http-0960x0600.png&quot; title=&quot;Wireshark Showing EAPOL And HTTP Packets&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;In the preceding capture, first I have set the display filter to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eapol || http&lt;/code&gt;.
Then I have switched the Wi-Fi interface of my iPad off and on again, which triggers the initial four EAPOL messages.
Then I have used Safari on my iPad to browse my blog’s home page at &lt;a href=&quot;&quot;&gt;http://illegalexception.schlichtherle.de&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that the iPad and the web server exchange HTTP packets using both IPv4 and IPv6 addresses.
This is not very efficient because it implies that there is no persistent TCP connection between the two hosts.
As you can see, Wireshark is a very effective tool for analyzing issues like this.&lt;/p&gt;

&lt;h2 id=&quot;caveats&quot;&gt;Caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Obviously, you will not be able to read the plain text of any encrypted network protocols like HTTPS or SSH.&lt;/li&gt;
  &lt;li&gt;You may need to trigger the EAPOL handshake several times before Wireshark will be able to decrypt all data packets.&lt;/li&gt;
  &lt;li&gt;Sometimes the monitoring computer may get disconnected from the Internet.
You can easily fix this by stopping the monitoring and switching the Wi-Fi interface off and on again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This mini tutorial showed you how to obtain the Wi-Fi secret from the keychain, configure Wireshark with it, start the
monitoring and trigger the authentication handshake to make Wireshark learn about the session key for the connected
devices.&lt;/p&gt;

&lt;p&gt;When monitoring a secure Wi-Fi network, you need to configure the monitoring tool with the pre-shared key or the
password for decrypting the data packets.
This is because in monitor mode the interface driver will deliver the raw 802.11 packets to the application, leaving it
to deal with all the necessary processing, including the decryption.
In non-monitor mode however, it’s the interface driver’s responsibility to process each 802.11 packet which is intended
for the local host, thereby filtering, decrypting and converting the packets to plain old Ethernet (802.3) packets
before delivering them to the application.&lt;/p&gt;</content><author><name></name></author><category term="networking" /><category term="Wireshark" /><category term="monitoring" /><category term="tutorial" /><category term="WPA2" /><summary type="html">Do you have a secure Wi-Fi network and a computer running macOS? Do you want to monitor the traffic of the devices connected to this network? Look no further, here’s how to do this step by step using Wireshark!</summary></entry><entry><title type="html">Overclocking the Raspberry Pi 4</title><link href="https://illegalexception.schlichtherle.de/raspberry-pi/2019/09/22/overclocking-the-raspberry-pi-4/" rel="alternate" type="text/html" title="Overclocking the Raspberry Pi 4" /><published>2019-09-22T00:00:00+02:00</published><updated>2019-09-22T00:00:00+02:00</updated><id>https://illegalexception.schlichtherle.de/raspberry-pi/2019/09/22/overclocking-the-raspberry-pi-4</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/raspberry-pi/2019/09/22/overclocking-the-raspberry-pi-4/">&lt;p&gt;The &lt;a href=&quot;/raspbian/2019/09/21/how-to-upgrade-raspbian-to-a-64-bit-kernel/&quot;&gt;latest firmware&lt;/a&gt; for the Raspberry Pi 4 
Model B allows you to individually overclock it’s CPU, GPU, SDRAM etc.
This posting shows you how to do that.&lt;/p&gt;

&lt;p&gt;Before you rush to this, a word of warning:
&lt;strong&gt;Overclocking can void your warranty and irreparably damage your RPi.
To use sustainable overclocking, you must apply active cooling to your RPi - a heatsink alone is not good enough!
DON’T FOLLOW THESE INSTRUCTIONS unless you understand and agree to apply them at your own risk!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technically speaking, overclocking the RPi is simple:
All you have to do is edit the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot/config.txt&lt;/code&gt; to set certain variables for frequencies and voltages in the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[all]&lt;/code&gt; section and reboot the device.
For the full list of variables, please check this &lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/config-txt/overclocking.md&quot;&gt;reference page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, to set the frequency of the ARM CPU, you can set the variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arm_freq&lt;/code&gt;, which defines the frequency in
MHz.
The default value for the RPi 4 is 1500:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[all]
arm_freq=1500
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Setting a higher frequency for a component generally requires a higher voltage to drive the component.
For example, to set the voltage for the ARM CPU, you can set the variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;over_voltage&lt;/code&gt;, which defines the voltage in
steps of 25mV.
The default value for the RPi 4 is 0:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[all]
over_voltage=0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For me, I’ve applied the &lt;a href=&quot;https://www.raspberrypi.org/products/poe-hat/&quot;&gt;PoE HAT&lt;/a&gt; to the RPi 4s in
&lt;a href=&quot;/kubernetes/2019/09/12/provisioning-a-kubernetes-cluster-on-raspberry-pi-with-ansible/&quot;&gt;my Kubernetes cluster&lt;/a&gt;:&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2019/09/poe-hat-1920x2560.jpg&quot;&gt;
        &lt;picture&gt;
            &lt;source media=&quot;(min-width: 576px)&quot; srcset=&quot;/assets/2019/09/poe-hat-0510x0680.jpg&quot; /&gt;
            &lt;img src=&quot;/assets/2019/09/poe-hat-0320x0427.jpg&quot; title=&quot;A PoE HAT cooling an RPi 4 node in a cluster array&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The PoE HAT has a built-in fan which allows me to overclock the ARM CPU like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[all]
arm_freq=1750
over_voltage=2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Setting a higher frequency causes the firmware to frequently throttle the CPU clock in order to protect it from
overheating - throttling starts at 80’C.
Setting a lower voltage causes the CPU to crash because there is not enough power to drive it.
Setting a higher voltage causes the CPU to become hotter and thus, getting throttled more often. 
Of course, your mileage may vary based on your cooling solution and ambient temperature (mine is A/C controlled at 
23’C).&lt;/p&gt;

&lt;p&gt;Don’t forget to reboot the device:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can check the results using the &lt;a href=&quot;https://elinux.org/RPI_vcgencmd_usage&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vcgencmd&lt;/code&gt;&lt;/a&gt; command.
Here’s what it shows for me on one of the cluster nodes:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ vcgencmd measure_temp; vcgencmd measure_clock arm; vcgencmd get_throttled
temp=78.0'C
frequency(48)=1750412160
throttled=0x20000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Everything below 80’C is OK.
If it’s significantly lower while the device is running a CPU intensive workload for a while already, then it means that you can 
increase the frequency, which in turn may require you to set a higher voltage, too.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.raspberrypi.org/forums/viewtopic.php?f=63&amp;amp;t=147781&amp;amp;start=50#p972790&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throttled&lt;/code&gt;&lt;/a&gt; property indicates if 
the CPU is currently throttled or was throttled at some point in the past:
If the least significant digit is not zero, then the CPU is currently throttled for some reason.
If the most significant digit is not zero, then the CPU was throttled for some reason in the past.
In this example, the value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x20000&lt;/code&gt; indicates that the CPU has been set to a lower frequency at some point in the past.
Ideally, the value should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0&lt;/code&gt;, of course.&lt;/p&gt;

&lt;p&gt;Since I’m running a cluster, I’m using &lt;a href=&quot;https://www.ansible.com&quot;&gt;Ansible&lt;/a&gt; to get this information from all my nodes.
Here’s a sample reading:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ansible all --module-name=shell --args='vcgencmd measure_temp; vcgencmd measure_clock arm; vcgencmd get_throttled' --one-line | sort
node-0 | CHANGED | rc=0 | (stdout) temp=79.0'C\nfrequency(48)=1750412160\nthrottled=0x20000
node-1 | CHANGED | rc=0 | (stdout) temp=50.0'C\nfrequency(48)=1750412160\nthrottled=0x0
node-2 | CHANGED | rc=0 | (stdout) temp=79.0'C\nfrequency(48)=1750412160\nthrottled=0x20000
node-3 | CHANGED | rc=0 | (stdout) temp=76.0'C\nfrequency(48)=1750412160\nthrottled=0x20000
node-4 | CHANGED | rc=0 | (stdout) temp=77.0'C\nfrequency(48)=1750464896\nthrottled=0x20000
node-5 | CHANGED | rc=0 | (stdout) temp=73.0'C\nfrequency(48)=1750412160\nthrottled=0x0
node-6 | CHANGED | rc=0 | (stdout) temp=74.0'C\nfrequency(48)=1750412160\nthrottled=0x0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In case you wonder why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-1&lt;/code&gt; is relatively cool at 50’C while all the others are sweating close to 80’C:
This is because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-1&lt;/code&gt; is the single master node in my Kubernetes cluster.
It doesn’t run a real workload like the other nodes do - more on that in a separate posting.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="raspberry-pi" /><category term="raspbian" /><category term="arm64" /><category term="aarch64" /><category term="linux" /><category term="raspberry-pi" /><category term="overclocking" /><summary type="html">The latest firmware for the Raspberry Pi 4 Model B allows you to individually overclock it’s CPU, GPU, SDRAM etc. This posting shows you how to do that.</summary></entry><entry><title type="html">How To Upgrade Raspbian To A 64 Bit Kernel</title><link href="https://illegalexception.schlichtherle.de/raspbian/2019/09/21/how-to-upgrade-raspbian-to-a-64-bit-kernel/" rel="alternate" type="text/html" title="How To Upgrade Raspbian To A 64 Bit Kernel" /><published>2019-09-21T00:00:00+02:00</published><updated>2019-09-21T00:00:00+02:00</updated><id>https://illegalexception.schlichtherle.de/raspbian/2019/09/21/how-to-upgrade-raspbian-to-a-64-bit-kernel</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/raspbian/2019/09/21/how-to-upgrade-raspbian-to-a-64-bit-kernel/">&lt;p&gt;At the time of this writing, there is no 64 bit edition of Raspbian yet.
This is particularly bad if you are running Docker (or Kubernetes) on the new Raspberry Pi Model 4, because you will not
be able to run the many ARM64 images on Docker Hub, e.g. &lt;a href=&quot;https://hub.docker.com/_/openjdk&quot;&gt;OpenJDK&lt;/a&gt;.
Fear not, you can change this! Here is how.&lt;/p&gt;

&lt;p&gt;It’ll be a long way before a 64 bit edition of Raspbian gets released.
However, thanks to the &lt;a href=&quot;https://www.raspberrypi.org/forums/viewtopic.php?t=250730#p1530551&quot;&gt;recent work&lt;/a&gt; of the 
Raspberry Foundation, you can now upgrade to a beta version of a 64 bit Linux kernel.
This upgrade will not change the installed executables in your system, but at least the upgraded 64 bit kernel
can run new 64 bit executables as well as any old 32 bit executables.&lt;/p&gt;

&lt;p&gt;Before, you would have to try third party OS images to get a 64 bit Linux kernel.
I have tried some with mixed results.
The best alternative I have found so far is an
&lt;a href=&quot;https://github.com/TheRemote/Ubuntu-Server-raspi4-unofficial/releases&quot;&gt;unofficial image&lt;/a&gt; for Ubuntu Server 18.04.3,
which has been created and described in a &lt;a href=&quot;https://jamesachambers.com/raspberry-pi-4-ubuntu-server-desktop-18-04-3-image-unofficial/&quot;&gt;blog posting&lt;/a&gt;
by James A. Chambers.
However, the new hardware of the RPi 4 is not yet fully supported, so I thought I should give Raspbian another try.&lt;/p&gt;

&lt;p&gt;Before you do this, be warned:
&lt;strong&gt;This is beta software, so things may break!&lt;/strong&gt;
Retain your current OS image so that you can revert anytime if there is trouble.
Now, for the upgrade, login to your RPi and enter:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo rpi-update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will initiate the upgrade operation.
At some point, it will ask you to type a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; character to confirm the upgrade or not.
For tools like Ansible etc, you can simply automate a positive answer like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ echo -n y | sudo rpi-update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The 64 bit kernel will not be loaded upon boot until you also edit the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/boot/config.txt&lt;/code&gt; to contain the following
line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;arm_64bit=1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When testing this, I found a problem which is specific to Kubernetes:
Containers could not connect to the Internet anymore.
This is because the upgrade breaks &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iptables&lt;/code&gt;.
There is a &lt;a href=&quot;https://www.raspberrypi.org/forums/viewtopic.php?t=232415#p1509668&quot;&gt;trivial fix&lt;/a&gt; for this, however:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ update-alternatives --set iptables /usr/sbin/iptables-legacy
$ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, you need to reboot the system:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, you can check the upgraded kernel:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ uname -r
Linux node-3 4.19.73-v8+ #1267 SMP PREEMPT Fri Sep 20 18:14:38 BST 2019 aarch64 GNU/Linux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aarch64&lt;/code&gt; is a synonym for ARM64v8 - sweet!&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="raspbian" /><category term="raspbian" /><category term="arm64" /><category term="aarch64" /><category term="linux" /><category term="raspberry-pi" /><summary type="html">At the time of this writing, there is no 64 bit edition of Raspbian yet. This is particularly bad if you are running Docker (or Kubernetes) on the new Raspberry Pi Model 4, because you will not be able to run the many ARM64 images on Docker Hub, e.g. OpenJDK. Fear not, you can change this! Here is how.</summary></entry><entry><title type="html">Provisioning a Kubernetes cluster on Raspberry Pi with Ansible</title><link href="https://illegalexception.schlichtherle.de/kubernetes/2019/09/12/provisioning-a-kubernetes-cluster-on-raspberry-pi-with-ansible/" rel="alternate" type="text/html" title="Provisioning a Kubernetes cluster on Raspberry Pi with Ansible" /><published>2019-09-12T00:00:00+02:00</published><updated>2019-09-12T00:00:00+02:00</updated><id>https://illegalexception.schlichtherle.de/kubernetes/2019/09/12/provisioning-a-kubernetes-cluster-on-raspberry-pi-with-ansible</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/kubernetes/2019/09/12/provisioning-a-kubernetes-cluster-on-raspberry-pi-with-ansible/">&lt;p&gt;The new &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-4-model-b/&quot;&gt;Raspberry Pi model 4B&lt;/a&gt; is out and it’s a 
significant upgrade:
Among other improvements, the new model has up to 4GB, &lt;em&gt;real&lt;/em&gt; Gigabit Ethernet (no more throttling to USB 2.0 bandwidth) 
and a quad core Cortex-A72 (ARM64v8) CPU running at 1.5 GHz - see 
&lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-4-model-b/specifications/&quot;&gt;specifications&lt;/a&gt;.
With all this power, why not build a &lt;a href=&quot;https://kubernetes.io&quot;&gt;Kubernetes&lt;/a&gt; cluster with it?
Well, here we go:
This posting shows you how you can setup a multi-node Kubernetes cluster on an array of Raspberry Pi’s in minutes!&lt;/p&gt;

&lt;p&gt;To automate the provisioning, I have created an
&lt;a href=&quot;https://docs.ansible.com/ansible/latest/user_guide/playbooks.html&quot;&gt;Ansible playbook&lt;/a&gt; which sets up either original
Kubernetes (subsequently called k8s) or &lt;a href=&quot;https://k3s.io&quot;&gt;k3s&lt;/a&gt;, which is a certified Kubernetes variant with a smaller
memory footprint.
The playbook even supports partitioning your nodes so that some may form a k8s cluster while others form a k3s cluster.
This can be useful for testing purposes.&lt;/p&gt;

&lt;p&gt;The minimum number of nodes in any cluster is one for a single master node, but you should have at least two nodes, one
master node and one worker node.
The more worker nodes you can add, the better of course.
At the time of this writing however, the playbook only supports a single master node.
If you want more than one master node, then you need to manually reconfigure the cluster post setup.&lt;/p&gt;

&lt;p&gt;Regardless of the Kubernetes variant, the playbook installs &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt; as the container runtime,
so that you can take advantage of Docker-specific features.
For example, the playbook supports configuring an external
&lt;a href=&quot;https://docs.docker.com/registry/recipes/mirror/&quot;&gt;Docker registry mirror&lt;/a&gt;.
This can be used to reduce network traffic as otherwise every worker node individually pulls the same Docker images.
For the same reason, the playbook also supports configuring an external HTTP(S) proxy.
This can be used to make the provisioning a lot faster and more robust as every network operation
(e.g. installing packages) is effectively done at most once for the first node while subsequent nodes will be served
from the proxy’s cache.&lt;/p&gt;

&lt;p&gt;If you need a &lt;a href=&quot;https://docs.docker.com/registry/deploying/&quot;&gt;private Docker registry&lt;/a&gt;, then this can be configured using
&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/&quot;&gt;image pull secrets&lt;/a&gt; in a 
post-master-up event hook (see &lt;a href=&quot;#configuring-event-hooks&quot;&gt;below&lt;/a&gt;).
I recommend
&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account&quot;&gt;adding image pull secrets to a service account&lt;/a&gt;,
so that you don’t have to configure this for each pod individually.&lt;/p&gt;

&lt;p&gt;For container networking, the playbook configures &lt;a href=&quot;https://github.com/coreos/flannel&quot;&gt;Flannel&lt;/a&gt;.
This is the only choice with k3s anyway.&lt;/p&gt;

&lt;p&gt;I have also created another playbook to properly tear down individual nodes or even the entire cluster.
This allows you to re-purpose your hardware, e.g. a worker node can leave one cluster and join another or you can use it
for something else.&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;h3 id=&quot;hardware&quot;&gt;Hardware&lt;/h3&gt;

&lt;p&gt;The playbooks have been designed so that they do not depend on a particular hardware:
They should work on anything which runs a Debian based Linux distribution, including EC2 instances on AWS.
As a proof-of-concept, I’m running Kubernetes on an array of six Raspberry Pi 4’s with 4 GB memory each.
Actually, as little as 1 GB memory is enough to run Kubernetes, but then there is not enough free memory to run many
interesting apps.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2019/09/raspi-array-1920x3406.jpg&quot;&gt;
        &lt;picture&gt;
            &lt;source media=&quot;(min-width: 576px)&quot; srcset=&quot;/assets/2019/09/raspi-array-0510x0905.jpg&quot; /&gt;
            &lt;img src=&quot;/assets/2019/09/raspi-array-0320x0568.jpg&quot; title=&quot;An array of Raspberry Pi Model 4B&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Also, for best performance, the Pi’s should be connected via Ethernet cable, not WiFi.
There is a caveat, however:
With Raspberry Pi 3 and earlier models, the Ethernet port is connected to the USB 2.0 controller, which effectively
limits the available bandwidth to about 224 Mbps.
In comparison, the Raspberry Pi 4 can sustain over 900 Mbps with adequate cooling.
For my setup I have chosen the Power-over-Ethernet (PoE) HATs, which have a temperature-controlled fan.&lt;/p&gt;

&lt;p&gt;All the performance is waisted if you go for a cheap switch:
The problem with cheap switches is that their backplane often doesn’t provide enough bandwidth for full-duplex 
transmission on all ports in parallel - this is the same problem as with the Ethernet port on older Raspberry Pi models.&lt;br /&gt;
For best results, you need a switch with at least N + 1 ports (for N Pis’ plus an uplink to your router) and a backplane 
bandwidth of at least 2 * (N + 1) Gbps.
For my setup I have chosen the &lt;a href=&quot;https://www.trendnet.com/products/product-detail?prod=195_TPE-TG80g&quot;&gt;TRENDnet TPE-TG80g&lt;/a&gt;,
which is an unmanaged switch with 8 Gigabit Ethernet ports.
Its backplane has a bandwidth capacity of 16 Gbps, so all its ports can transmit data at full bandwidth in parallel.
The ports also feature Power-over-Ethernet Plus (PoE+) with up to 30 Watts per port and a power budget of 123 Watts.
The Pi’s typically need less than ten Watts, so the 123 Watts power budget is perfectly fine.
The little extra money I’ve spent for the switch (&lt;a href=&quot;https://www.amazon.com/gp/product/B008OJM0T8&quot;&gt;~115 USD on Amazon&lt;/a&gt;) 
I’ve probably saved on power supplies for the Pi’s - I don’t need any!&lt;/p&gt;

&lt;p&gt;Last, but not least, all the performance is waisted again unless you also go for some high performance microSDXC card.
I have chosen 
&lt;a href=&quot;https://www.sandisk.com/home/memory-cards/microsd-cards/extremepro-microsd&quot;&gt;SanDisk Extreme PRO microSDXC UHS-I&lt;/a&gt; with 
a capacity of 64 GB.
For storing application data, I use an external NAS, though.&lt;/p&gt;

&lt;h3 id=&quot;software&quot;&gt;Software&lt;/h3&gt;

&lt;p&gt;As said before, the playbooks have been designed to work on any Debian based Linux distribution.
However, at the time of this writing, only Raspbian Buster (32 bit, ARMHF/ARM32/ARM architecture) and Ubuntu 18.04.3 LTS
(64 bit, AARCH64/ARM64 architecture) have been tested.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ kubectl --kubeconfig=build/k8s-config.yml get nodes -o wide
NAME    STATUS   ROLES    AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION   CONTAINER-RUNTIME
node1   Ready    &amp;lt;none&amp;gt;   83m   v1.15.3   192.168.71.21   &amp;lt;none&amp;gt;        Ubuntu 18.04.3 LTS               4.19.71-v8+      docker://18.9.9
node2   Ready    &amp;lt;none&amp;gt;   83m   v1.15.3   192.168.71.22   &amp;lt;none&amp;gt;        Ubuntu 18.04.3 LTS               4.19.71-v8+      docker://18.9.9
node3   Ready    &amp;lt;none&amp;gt;   83m   v1.15.3   192.168.71.23   &amp;lt;none&amp;gt;        Ubuntu 18.04.3 LTS               4.19.71-v8+      docker://18.9.9
node4   Ready    &amp;lt;none&amp;gt;   83m   v1.15.3   192.168.71.24   &amp;lt;none&amp;gt;        Ubuntu 18.04.3 LTS               4.19.71-v8+      docker://18.9.9
node5   Ready    master   84m   v1.15.3   192.168.71.25   &amp;lt;none&amp;gt;        Raspbian GNU/Linux 10 (buster)   4.19.71-v7l+     docker://18.9.0
node6   Ready    &amp;lt;none&amp;gt;   83m   v1.15.3   192.168.71.26   &amp;lt;none&amp;gt;        Ubuntu 18.04.3 LTS               4.19.71-v8+      docker://18.9.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, I’m running k8s version 1.15.3 on all nodes.
You can also use k3s with my Ansible playbooks, but I figured that it may be not worth it unless you want to run a
single node cluster:
While k3s has a significantly smaller memory footprint on a master node, there’s hardly any difference on a worker node.
On the other hand, there are subtle differences in the default setup, like k8s uses a self-signed certificate to 
authenticate users whereas k3s uses a username and a password.
I don’t want to risk bad surprises later, so I went with the original.
You may beg to differ, of course.&lt;/p&gt;

&lt;p&gt;The worker nodes run on Ubuntu 18.04.3 LTS / 64 bit, so I can benefit from the bigger portfolio of Docker images for its 
ARM64 architecture.
At the time of this writing, there is no official image from Canonical which supports the Raspberry Pi 4 yet, so I’m
using an &lt;a href=&quot;https://github.com/TheRemote/Ubuntu-Server-raspi4-unofficial/releases&quot;&gt;unofficial image&lt;/a&gt; which has been kindly
provided by James A. Chambers on GitHub.&lt;/p&gt;

&lt;p&gt;Choosing the right architecture for the worker nodes is critical:
A lot of my software is written in Java, so I want to run an official OpenJDK image.
Unfortunately, it’s not available for ARM32, so I choose ARM64 and hence, Ubuntu.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ kubectl --kubeconfig build/k8s-config.yml run -it --restart=Never --image=openjdk:11-jdk test
If you don't see a command prompt, try pressing enter.
|  Welcome to JShell -- Version 11.0.4
|  For an introduction type: /help intro

jshell&amp;gt; /exit
|  Goodbye
$ kubectl --kubeconfig build/k8s-config.yml get pod test -o wide
NAME   READY   STATUS      RESTARTS   AGE    IP           NODE    NOMINATED NODE   READINESS GATES
test   0/1     Completed   0          2m6s   10.244.1.2   node4   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
$ kubectl --kubeconfig build/k8s-config.yml delete pod test
pod &quot;test&quot; deleted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Technically, ARM64 is not necessarily the best choice however:
At the time of this writing, the Raspberry Pi 4 ships with max 4 GB, so a 32 bit image is good enough to address all of 
its memory.
With 64 bit however, the Docker images just get bigger, more (cache) memory is consumed to hold the bigger pointers and
more bandwidth is consumed to transfer them to CPU registers and back.
Therefore, 32 bit images should save you some memory and perform slightly better on the current hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; You can still run ARM32 binaries on an ARM64 architecture though, so choosing a 64 bit architecture gives you
more options.&lt;/p&gt;

&lt;p&gt;The master node runs on Raspbian Buster Lite / 32 bit, so its an ARM32 architecture.
This is the &lt;a href=&quot;https://www.raspberrypi.org/downloads/raspbian/&quot;&gt;official image&lt;/a&gt; from the Raspberry foundation. 
As said above, ARM32 images should perform slightly better and a master node normally doesn’t run any work load, so
the different portfolio of Docker images is irrelevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; Unfortunately, this breaks the container networking (Flannel) for some reason, so CoreDNS complains that it 
can’t talk to the external DNS servers (I have one on my NAS and one on my router).
After some hours of analysis and failed experiments, my successful resolution is to teardown and setup the whole cluster
again with &lt;em&gt;all&lt;/em&gt; nodes running on Ubuntu/ARM64!&lt;/p&gt;

&lt;p&gt;As for the playbooks, I’m using Ansible 2.8.4 with Python 3.7.3 on macOS 10.14.6.
Other versions may work, but haven’t been tested.&lt;/p&gt;

&lt;h2 id=&quot;preparing-the-nodes&quot;&gt;Preparing the Nodes&lt;/h2&gt;

&lt;p&gt;Kubernetes generally requires that the IP addresses of the nodes do not change.
The easiest way to achieve this is to pin the IP addresses in your DHCP server.
Also, before you can use the Ansible playbooks, you need to be able to login to all the nodes using SSH.
The easiest way to achieve that is to use the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-copy-id pi@$ip_address&lt;/code&gt; for all Raspbian nodes and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-copy-id ubuntu@$ip_address&lt;/code&gt; for all Ubuntu nodes, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ip_address&lt;/code&gt; is the IP address of the respective node.
No further preparations are required - the rest is handled by the Ansible playbooks.&lt;/p&gt;

&lt;h2 id=&quot;checking-out-the-repository&quot;&gt;Checking Out the Repository&lt;/h2&gt;

&lt;p&gt;First, you need to check out the &lt;a href=&quot;https://github.com/christian-schlichtherle/kalaxy&quot;&gt;Git repository&lt;/a&gt; with the Ansible
playbooks:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone git@github.com:christian-schlichtherle/kalaxy.git
cd kalaxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;configuring-the-ansible-playbooks&quot;&gt;Configuring the Ansible Playbooks&lt;/h2&gt;

&lt;p&gt;Next, use your editor-of-choice to edit the YAML file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inventory.yml&lt;/code&gt;.
This is a local Ansible inventory which needs to reflect your environment and configuration choices.
Here’s mine:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Use only IP addresses here!&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.21&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.22&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.23&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.24&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.25&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;node6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;192.168.71.26&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;k3s_master&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Only one master!&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;k3s_worker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;k8s_master&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Only one master!&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;k8s_worker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;raspberry_pi&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;raspbian&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ansible_ssh_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pi&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;boot_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/boot&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;docker_apt_repository&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;deb&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://download.docker.com/linux/raspbian&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;stretch&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;stable&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;docker_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5:18.09.0~3-0~raspbian-stretch&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ubuntu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ansible_ssh_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;boot_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/boot/firmware&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ansible_python_interpreter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/usr/bin/python3&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#containerd_version: 1.2.6-3&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Make sure to match the `no_proxy` pattern or otherwise expect Docker images to be proxied twice:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#docker_registry_mirror_url: https://my-docker-registry-mirror.local/&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#docker_version: &quot;5:18.09.9~3-0~{{ ansible_lsb.id.lower() }}-{{ ansible_lsb.codename }}&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#k3s_version: v0.9.0-rc2 # https://github.com/rancher/k3s/releases&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#k8s_version: 1.15.3-00&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#kuberneted_cni_version: 0.7.5-00&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Make sure to match all nodes and the `docker_registry_mirror_url`:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#no_proxy: 10.0.0.0/8,127.0.0.0/8,192.168.0.0/16,*.local,localhost&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Make sure to use an IP address, not a hostname, or otherwise expect the Kubernetes setup to fail:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#proxy_url: http://10.0.0.1:3128/&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#remove_docker_directory: yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the bare minimum, you need to update the file as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.hosts&lt;/code&gt; maps all your node names (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node1&lt;/code&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node6&lt;/code&gt; for me) to their IP addresses.
Don’t use host names instead of IP addresses!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.children&lt;/code&gt; assigns nodes to groups:
For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.children.k8s_worker.hosts&lt;/code&gt; lists all the worker nodes of the k8s cluster, 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.children.raspbian.hosts&lt;/code&gt; lists all the nodes running on Raspbian,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.children.raspberry_pi.hosts&lt;/code&gt; lists all the nodes running on Raspberry Pi hardware etc.&lt;/p&gt;

    &lt;p&gt;A node can be a member of multiple groups:
For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node5&lt;/code&gt; is a member of the groups &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s_master&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raspbian&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raspberry_pi&lt;/code&gt; to declare that this is a
Raspberry Pi running Raspbian which shall become the master of a k8s cluster.&lt;/p&gt;

    &lt;p&gt;Mind you that the groups &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3s_master&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s_master&lt;/code&gt; can contain at most one host - multi-master setup is not
supported!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Optional: You can configure to use an external HTTP(S) proxy by uncommenting and setting the variables
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.vars.no_proxy&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.vars.proxy_url&lt;/code&gt;.
Make sure to use an IP address, not a hostname in the proxy URL, or otherwise expect the Kubernetes setup to fail for 
some reason.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Optional: You can configure to use an external Docker registry mirror by uncommenting and setting the variable
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.vars.docker_registry_mirror_url&lt;/code&gt;.
Make sure to match the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no_proxy&lt;/code&gt; pattern or otherwise expect Docker images to be double proxied.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Optional: You can override the versions for Docker, k3s and k8s by uncommenting and setting the respective variables 
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.vars&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all.children.raspbian.vars&lt;/code&gt;.
The playbooks will install and pin these particular versions, then.&lt;/p&gt;

    &lt;p&gt;Mind you that Kubernetes generally doesn’t support the latest Docker release, so it’s not always advisable to select
the latest version.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;verifying-your-configuration&quot;&gt;Verifying Your Configuration&lt;/h2&gt;

&lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible all -m ping&lt;/code&gt; to verify your configuration.
For my setup, it shows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ansible all -m ping
node1 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
node4 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
node3 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
node5 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
node2 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
node6 | SUCCESS =&amp;gt; {
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this command, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all&lt;/code&gt; refers to the same-named group in the inventory.
You should repeat this command for all other groups to verify their members, i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3s_master&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3s_worker&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s_master&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s_worker&lt;/code&gt; etc.&lt;/p&gt;

&lt;h2 id=&quot;configuring-event-hooks&quot;&gt;Configuring Event Hooks&lt;/h2&gt;

&lt;p&gt;An event hook is a custom Kubernetes resource manifest which gets supplied to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply&lt;/code&gt; command whenever a
playbook triggers a certain event.
This feature can be used to configure the cluster during setup, for example to create an image pull secret for 
accessing a private Docker registry and applying it to the service account &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The playbooks trigger the following events:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post-master-up&lt;/code&gt;: Triggered after the master node has been setup, but before the worker nodes join the cluster&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pre-master-down&lt;/code&gt;: Triggered after the worker nodes left the cluster, but before the master node gets teared down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following event hook files will be tried in order in response to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post-master-up&lt;/code&gt; event (changed in v0.2.3):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/k3s-post-master-up.yml&lt;/code&gt; (only on k3s)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/k8s-post-master-up.yml&lt;/code&gt; (only on k8s)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/post-master-up.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following event hook files will be tried in order in response to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pre-master-down&lt;/code&gt; event (changed in v0.2.3):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/pre-master-down.yml&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/k8s-pre-master-down.yml&lt;/code&gt; (only on k8s)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hooks/k3s-pre-master-down.yml&lt;/code&gt; (only on k3s)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Like Ansible tasks, event hooks need to be idempotent.
This requirements is ensured by Kubernetes.&lt;/p&gt;

&lt;h2 id=&quot;provisioning-your-clusters&quot;&gt;Provisioning Your Cluster(s)&lt;/h2&gt;

&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;Finally, you are ready to setup your k3s or k8s cluster:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ make # or `make up`
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This takes some minutes, mostly depending on your Internet bandwidth and the storage throughput.
As a side effect, unless the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s_master&lt;/code&gt; group is empty, this command creates the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/k8s-config.yml&lt;/code&gt;.
Likewise, unless the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k3s_master&lt;/code&gt; group is empty, this also creates the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/k3s-config.yml&lt;/code&gt;.
You need these configuration files to connect to your respective cluster.
For example, to talk to the k8s cluster:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ export KUBECONFIG=&quot;$PWD/build/k8s-config.yml&quot;
$ kubectl get nodes
NAME    STATUS   ROLES    AGE     VERSION
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This lists all your nodes in the k8s cluster.
Watch out for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATUS&lt;/code&gt; column - it should eventually show &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;READY&lt;/code&gt; for all the nodes in the cluster.
If not, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl describe node $node_name&lt;/code&gt; and check the shown events.&lt;/p&gt;

&lt;h3 id=&quot;teardown&quot;&gt;Teardown&lt;/h3&gt;

&lt;p&gt;To tear down your entire cluster(s):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ make down
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is rarely useful unless you want to do something completely different with all your nodes.
Typically, you will want to limit this command to a single node, e.g. if you want to re-purpose or upgrade the node.
You can do this by providing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPTS&lt;/code&gt; variable to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make OPTS=--limit=node1 down
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--limit=node1&lt;/code&gt; is passed down to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible-playbook&lt;/code&gt; command which subsequently tears down &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node1&lt;/code&gt; only,
properly draining and deleting it from the cluster first.&lt;/p&gt;

&lt;h2 id=&quot;installing-the-kubernetes-dashboard&quot;&gt;Installing the Kubernetes Dashboard&lt;/h2&gt;

&lt;p&gt;Now that you have a working cluster, you can install the Kubernetes dashboard like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ kubectl apply --filename https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, follow these &lt;strike&gt;painful&lt;/strike&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/&quot;&gt;standard instructions&lt;/a&gt;
to get an access token.
Once you’ve passed this surgery, you can access the Kubernetes Dashboard at 
&lt;a href=&quot;&quot;&gt;http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/&lt;/a&gt;
and browse your cluster.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;/assets/2019/09/k8s-dashboard-1920x1200.png&quot;&gt;
        &lt;picture&gt;
            &lt;source media=&quot;(min-width: 1200px)&quot; srcset=&quot;/assets/2019/09/k8s-dashboard-1110x0694.png&quot; /&gt;
            &lt;source media=&quot;(min-width: 992px)&quot; srcset=&quot;/assets/2019/09/k8s-dashboard-0930x0581.png&quot; /&gt;
            &lt;source media=&quot;(min-width: 768px)&quot; srcset=&quot;/assets/2019/09/k8s-dashboard-0690x0431.png&quot; /&gt;
            &lt;source media=&quot;(min-width: 576px)&quot; srcset=&quot;/assets/2019/09/k8s-dashboard-0510x0319.png&quot; /&gt;
            &lt;img src=&quot;/assets/2019/09/k8s-dashboard-0320x0200.png&quot; title=&quot;Kubernetes Dashboard showing node6&quot; /&gt;
        &lt;/picture&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this post, I have shown how to provision a Kubernetes cluster on an array of Raspberry Pi’s using Ansible 
playbooks.
The built cluster(s) support HTTP(S) proxies, a Docker registry mirror and a private Docker registry via a 
post-master-up event hook.
By configuring the Ansible inventory, the same playbooks can be reused for a variety of Kubernetes variants and Linux
distributions.
Last, but not least, the same playbooks can be used with any hardware which runs a Debian based Linux distribution, not
just the Raspberry Pi.&lt;/p&gt;

&lt;p&gt;If you have been encountering any issues with the Ansible playbooks, kindly refer to the
&lt;a href=&quot;https://github.com/christian-schlichtherle/kalaxy/issues&quot;&gt;issue tracker&lt;/a&gt; on GitHub.&lt;/p&gt;</content><author><name></name></author><category term="kubernetes" /><category term="how-to" /><category term="raspi" /><category term="raspberry-pi" /><category term="RPi4" /><category term="k3s" /><category term="k8s" /><category term="ansible" /><category term="infrastructure-as-code" /><category term="iac" /><summary type="html">The new Raspberry Pi model 4B is out and it’s a significant upgrade: Among other improvements, the new model has up to 4GB, real Gigabit Ethernet (no more throttling to USB 2.0 bandwidth) and a quad core Cortex-A72 (ARM64v8) CPU running at 1.5 GHz - see specifications. With all this power, why not build a Kubernetes cluster with it? Well, here we go: This posting shows you how you can setup a multi-node Kubernetes cluster on an array of Raspberry Pi’s in minutes!</summary></entry><entry><title type="html">Blog Relaunch</title><link href="https://illegalexception.schlichtherle.de/blog/2018/06/02/blog-relaunch/" rel="alternate" type="text/html" title="Blog Relaunch" /><published>2018-06-02T15:43:00+02:00</published><updated>2018-06-02T15:43:00+02:00</updated><id>https://illegalexception.schlichtherle.de/blog/2018/06/02/blog-relaunch</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/blog/2018/06/02/blog-relaunch/">&lt;p&gt;Welcome to my relaunched blog!&lt;/p&gt;

&lt;p&gt;After years of laying dormant, I finally allocated the time to migrate my old blog from
&lt;a href=&quot;https://wordpress.com&quot;&gt;Wordpress&lt;/a&gt; with &lt;a href=&quot;https://mysql.com&quot;&gt;MySQL&lt;/a&gt; to &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; 
with &lt;a href=&quot;https://webpack.js.org&quot;&gt;Webpack&lt;/a&gt; and &lt;a href=&quot;https://getbootstrap.com&quot;&gt;Bootstrap&lt;/a&gt;.
Finally, good riddance to a slow, non-responsive web site with potentially lots of security issues, and a sincere 
welcome to a fast, responsive and secure web site.&lt;/p&gt;

&lt;p&gt;The new design is a lot cleaner than the old one, but it’s far from perfect yet.
I hope to find some more time eventually for updating this blog not only with new content, but also with design 
improvements.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="blog" /><summary type="html">Welcome to my relaunched blog!</summary></entry><entry><title type="html">TrueLicense 2.4 released</title><link href="https://illegalexception.schlichtherle.de/truelicense/2015/04/02/truelicense-2_4/" rel="alternate" type="text/html" title="TrueLicense 2.4 released" /><published>2015-04-02T17:22:15+02:00</published><updated>2015-04-02T17:22:15+02:00</updated><id>https://illegalexception.schlichtherle.de/truelicense/2015/04/02/truelicense-2_4</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/truelicense/2015/04/02/truelicense-2_4/">&lt;p&gt;I am very proud to announce the release of &lt;a href=&quot;http://truelicense.net/index.html&quot;&gt;TrueLicense&lt;/a&gt; version 2.4!&lt;/p&gt;

&lt;h2 id=&quot;so-what-is-so-special-about-this-release&quot;&gt;So what is so special about this release?&lt;/h2&gt;

&lt;p&gt;If you have tried TrueLicense before, then you sure have used the
&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/index.html&quot;&gt;TrueLicense Maven Archetype&lt;/a&gt;.
This is a code generator which generates a custom licensing schema for your software product.
However, in previous versions, the generated licensing schema was fix and you had to edit the generated code to do even
simple things like changing passwords.
Also, the key stores were fix and so you had to edit them, too.
Overall, the old archetype exploited only a very small fraction of the
&lt;a href=&quot;http://truelicense.net/truelicense-core/index.html&quot;&gt;TrueLicense Core&lt;/a&gt; API and yet it was very cumbersome and
error-prone to work with. Well, that’s how it &lt;em&gt;was&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;For version 2.4, the TrueLicense Maven Archetype has been completely rewritten in order to exploit the full power of the
TrueLicense Core API and yet be as easy to work with as possible.
As a result, there is now a set of
&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/generating.html#Property_Reference&quot;&gt;thirty properties&lt;/a&gt; for
customizing the generated licensing schema!
Passwords and key stores are automatically obfuscated and generated from these properties when building the project, so
no code editing is required anymore!&lt;/p&gt;

&lt;h2 id=&quot;isnt-configuring-thirty-properties-cumbersome-too&quot;&gt;Isn’t configuring thirty properties cumbersome, too?!&lt;/h2&gt;

&lt;p&gt;You don’t need to worry about the number of properties.
There are reasonable default values set wherever possible.
As a result, you don’t need to configure more properties than before in order to generate a custom licensing schema for
your software product.
But this time, the generated project automatically generates unique key stores and so you can immediately use the JARs
after running the build.
Here’s a minimal, yet fully working example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn archetype:generate &lt;span class=&quot;nt&quot;&gt;-B&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DarchetypeArtifactId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;truelicense-maven-archetype &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DarchetypeGroupId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;net.java.truelicense &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DarchetypeVersion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2.4 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DartifactId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;license &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DcompanyName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Company Inc.&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DdefaultPassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;test1234 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DgroupId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;com.company.product &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-DlicensingSubject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Product 1&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-Dversion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1.0-SNAPSHOT
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...]
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;license
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...]
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-keymgr&lt;/span&gt;/target/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-keymgr-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-guarded&lt;/span&gt;.jar wizard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last command starts the internationalized license wizard GUI.
You can see some screenshots in the &lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/using-gui.html&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;did-you-update-the-documentation&quot;&gt;Did you update the documentation?&lt;/h2&gt;

&lt;p&gt;Sure!
To help you fully exploit all options with my shiny new code generator, I completely rewrote the documentation for the
TrueLicense Maven Archetype.
There are now twelve pages explaining all the details, including the
&lt;abbr title=&quot;Command Line Interface&quot;&gt;&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/using-cli.html&quot;&gt;CLI&lt;/a&gt;&lt;/abbr&gt;,
&lt;abbr title=&quot;Graphical User Interface&quot;&gt;&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/using-gui.html&quot;&gt;GUI&lt;/a&gt;&lt;/abbr&gt;,
&lt;abbr title=&quot;Web Service Interface&quot;&gt;&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/using-wsi.html&quot;&gt;WSI&lt;/a&gt;&lt;/abbr&gt;,
&lt;abbr title=&quot;Application Programming Interface&quot;&gt;&lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/using-api.html&quot;&gt;API&lt;/a&gt;&lt;/abbr&gt;
and last, but not least the &lt;a href=&quot;http://truelicense.net/truelicense-maven-archetype/certifying.html&quot;&gt;certification&lt;/a&gt; of your
custom licensing schema.&lt;/p&gt;

&lt;p&gt;Finally, if you followed any of these links, you sure noticed the new &lt;a href=&quot;http://truelicense.net&quot;&gt;web site&lt;/a&gt; design.
I have spend some time in order to make sure the new TrueLicense web site provides you with an improved experience, just
as the TrueLicense API does.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="truelicense" /><category term="announcements" /><category term="feature" /><category term="java" /><category term="maven" /><category term="release-notes" /><category term="scala" /><summary type="html">I am very proud to announce the release of TrueLicense version 2.4!</summary></entry><entry><title type="html">Modeling Wizard Dialogs</title><link href="https://illegalexception.schlichtherle.de/patterns/2013/07/04/modeling-wizard-dialogs/" rel="alternate" type="text/html" title="Modeling Wizard Dialogs" /><published>2013-07-04T18:40:36+02:00</published><updated>2013-07-04T18:40:36+02:00</updated><id>https://illegalexception.schlichtherle.de/patterns/2013/07/04/modeling-wizard-dialogs</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/patterns/2013/07/04/modeling-wizard-dialogs/">&lt;p&gt;Wizard dialogs are a very common design pattern for user interfaces, e.g. for the GUI of software installation tools.
However, although wizard dialogs are so ubiquitious these days, component frameworks such as Swing or JSF do not provide
any support for them, so you have to turn to a third party library or roll your own.
In this blog posting, I am modeling an abstraction for internationalized wizard dialogs which is independent of the
particular user interface technology and easy to implement and use, e.g. in Swing based desktop applications or JSF
based server applications.&lt;/p&gt;

&lt;h2 id=&quot;concept&quot;&gt;Concept&lt;/h2&gt;

&lt;p&gt;The basic idea of a wizard dialog is to to split a larger task into multiple small, navigatable steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The task may be to execute a parameterized batch job.
Then each step would prompt the user for some parameters and the final step would submit the batch job for execution.
Alternatively, the user may cancel the wizard dialog before the batch job gets executed.&lt;/li&gt;
  &lt;li&gt;The steps may depend on each other so that they represent the vertices in a directed graph of job states with the
wizard dialog as its stateful engine.&lt;/li&gt;
  &lt;li&gt;For navigation, the wizard dialog displays user interface components which, if enabled, switch to the previous step or
the next step.
Depending on the user interface technology, a component to cancel the wizard dialog may need to be displayed, too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;use-cases&quot;&gt;Use Cases&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;A software installation wizard dialog may prompt the user to decide which components to install at which location and
finally perform the software installation based upon these parameters.&lt;/li&gt;
  &lt;li&gt;A license management wizard dialog may prompt the user to decide if a license key for the software should get
installed, displayed or (optionally) uninstalled.
The next step may then depend not only on the user’s choice, but also whether a license key is currently installed or
not and if it’s valid or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/assets/2013/07/Screenshot-518x438.png&quot;&gt;&lt;picture&gt;
    &lt;source media=&quot;(min-width: 518px)&quot; srcset=&quot;/assets/2013/07/Screenshot-518x438.png&quot; /&gt;
    &lt;img src=&quot;/assets/2013/07/Screenshot-300x143.png&quot; title=&quot;Sample Wizard Dialog For License Management&quot; /&gt;
&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In either case, a Swing based wizard dialog may display three buttons which enable the user to switch to the previous
step, the next step or cancel the wizard dialog altogether.
In contrast, a JSF based wizard dialog need not display a cancel button because cancelling the dialog is simply done by
leaving or closing the browser page.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;I am going to define an abstraction for wizard dialogs which, in addition to the use cases, must meet the following
requirements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The abstraction must not depend on a particular user interface technology so that there can be different
implementations.&lt;/li&gt;
  &lt;li&gt;The abstraction must not depend on a particular language so that an implementation can be internationalized.&lt;/li&gt;
  &lt;li&gt;Each step should be represented by a state of a stateful engine which meets the following requirements:
    &lt;ul&gt;
      &lt;li&gt;The engine must not depend on a particular type of state so that the implementation can freely choose it.&lt;/li&gt;
      &lt;li&gt;The engine must maintain the current state.&lt;/li&gt;
      &lt;li&gt;The previous and the next state should depend on the current state and user input so that the engine walks the
user through a directed graph of steps.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;design&quot;&gt;Design&lt;/h2&gt;

&lt;p&gt;I am going to apply a variant of the &lt;a href=&quot;http://en.wikipedia.org/wiki/Model_View_Controller&quot;&gt;Model-View-Controller&lt;/a&gt; (MVC)
pattern because it’s a natural fit for the implementation of a stateful engine for a user interface.
Im also going to define the core abstractions as Java interfaces, not classes, because interfaces are most versatile
when implementing the abstraction using different user interface technologies.&lt;/p&gt;

&lt;p&gt;As a simplification to the MVC pattern, I will not apply the &lt;a href=&quot;http://en.wikipedia.org/wiki/Observer_Pattern&quot;&gt;Observer&lt;/a&gt;
pattern.
That’s because (a) it adds a lot of complexity and constraints to the design and (b) you can almost always substitute it
with the more powerful and simpler &lt;a href=&quot;http://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;Decorator&lt;/a&gt; pattern (although this
discussion deserves another blog posting).&lt;/p&gt;

&lt;h3 id=&quot;model&quot;&gt;Model&lt;/h3&gt;

&lt;p&gt;The wizard model is nothing but a holder for the current state and a map of views for each state:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The current state is basically a property of the type parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;, although I didn’t use the typical
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getCurrentState&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setCurrentState&lt;/code&gt; naming pattern because I think it’s a thing of the past.
The type parameter value is completely opaque, that is, the model does not know anything about this type.
As you’ll see below, it is good practice to use an enum class as the value of this type parameter.&lt;/p&gt;

&lt;p&gt;The map of views is basically a property of the type parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;V&lt;/code&gt; which is indexed by states.
Again, I didn’t use the typical &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getView&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setView&lt;/code&gt; name pattern and the value of the type parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;V&lt;/code&gt; is completely
opaque.&lt;/p&gt;

&lt;p&gt;This interface is accompanied by a reusable basic implementation:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicWizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Enum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicWizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getEnumConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BasicWizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;views&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When subclassing this class, you need to provide the map for the views and the initial current state.&lt;/p&gt;

&lt;p&gt;For convenience, a static constructor is provided which accepts an enum class as its sole parameter.
It then passes an enum map and the first ordinal enum to the protected constructor.
For example, with the following enum class for license management…&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LicenseWizardState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;welcome&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uninstall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… you could use the static constructor like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LicenseWizardState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicWizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LicenseWizardState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The static constructor saves you from typing the generic type parameters twice and the protected constructor retains the
ability to use non-enum type states with this class if you want to.&lt;/p&gt;

&lt;h3 id=&quot;view&quot;&gt;View&lt;/h3&gt;

&lt;p&gt;The wizard view is another simple interface:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;backState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nextState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(back|next)State&lt;/code&gt; methods enable the view to dynamically resolve the previous and next state, which is modeled as
an opaque type parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; again.
If switching to a previous or next state is not available, e.g. because the user needs to provide some input first, then
they need to return the current state.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on(before|after)Switch&lt;/code&gt; methods are event methods which get called by the controller to indicate state change.
A typical implementation may initialize and/or hide/display this view.&lt;/p&gt;

&lt;p&gt;The wizard view is completely agnostic to the user interface technology.
This enables you to use this abstraction with Swing, JSF or any other user interface technology, even a command line
interface.
Because of this and for the sake of brevity, there is no basic implementation provided here.&lt;/p&gt;

&lt;h3 id=&quot;controller&quot;&gt;Controller&lt;/h3&gt;

&lt;p&gt;Last, but not least, the wizard controller interface looks like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchBackEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchNextEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchBack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchNext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The boolean &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch(Back|Next)Enabled&lt;/code&gt; method return true if and only if the respective &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(back|next)State&lt;/code&gt; method of the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardView&lt;/code&gt; interface returns a different state than the current state.
This may be used to enable or disable the respective button of the user interface.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch(Back|Next)&lt;/code&gt; methods switch the state of the engine from the current state to the previous or next
state respectively.
Calling these methods cause the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on(Before|After)StateSwitch&lt;/code&gt; methods of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardView&lt;/code&gt; interface to be called.&lt;/p&gt;

&lt;p&gt;In a typical implementation, the switch methods are the actions for the “back” and “next” buttons.
You may wonder then why there is no action for a “cancel” button.
This is because whether or not a “cancel” button is displayed is an implementation detail:
In a desktop user interface (e.g. Swing based), it’ll most likely exist, whereas in a web app (e.g. JSF based), this
wouldn’t make sense because cancelling the dialog is simply done by abandoning the browser session.&lt;/p&gt;

&lt;p&gt;Although the contract for this interface implicitly depends on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardModel&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardView&lt;/code&gt; interfaces, it doesn’t
have an explicit dependency on these interfaces.
Hence it also doesn’t know about their type parameters, that is it doesn’t know anything about the type of the wizard
states or wizard views.&lt;/p&gt;

&lt;p&gt;Again, this interface is accompanied by a reusable basic implementation:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicWizardController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardController&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BasicWizardController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

            &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WizardModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchBackEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchBack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchNextEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nextState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchNext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nextState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;fireBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;fireAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fireBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;currentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;onBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onBeforeStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fireAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;onAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;currentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onAfterStateSwitch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;backState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;backState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nextState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BasicWizardController&lt;/code&gt; is an abstract class because its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model&lt;/code&gt; property method needs to be implemented in a subclass.
For convenience, there is again a static constructor which accepts a generic wizard model as it’s sole parameter and
implements a subclass for it.&lt;/p&gt;

&lt;p&gt;Note that this time the type parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;V&lt;/code&gt; needs to extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardView&amp;lt;S&amp;gt;&lt;/code&gt;.
This is required because the controller needs to ask the view for the previous and next states of the current state.
This is a deviation from the pure MVC pattern where the controller would have to turn to the model to figure this.
However, for this use case the deviation is simpler to implement.&lt;/p&gt;

&lt;p&gt;The remainder of this class is pretty straightforward.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on(Before|After)StateSwitch&lt;/code&gt; methods are empty templates which mean to do the same thing as the equal named methods
of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WizardView&lt;/code&gt; interface, yet they are called on the controller, not the view.
Depending on the user interface technology, this may be more convenient.
E.g. in a Swing based wizard, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onAfterStateSwitch&lt;/code&gt; method may be overridden to switch the tab of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CardPanel&lt;/code&gt;
which hosts the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JPanel&lt;/code&gt;s for the different views.&lt;/p&gt;

&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;That’s it, just three interfaces with just four methods each and two basic implementation classes!
Given such a leightweight (or thin) abstraction, you may ask why you would want to use it?
Here are some benefits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reusability: The abstraction is agnostic to the user interface and language, so it can be applied to many different
problem domains.&lt;/li&gt;
  &lt;li&gt;Simplicity: The interfaces have just four methods each and the rather complex Observer pattern has been carefully
avoided.&lt;/li&gt;
  &lt;li&gt;Don’t-repeat-yourself: The basic implementation classes save you from repeating and testing essential functionality.&lt;/li&gt;
  &lt;li&gt;Proven Practice: The abstraction has been used to implement wizard dialogs for license management based on Swing and
JSF in my &lt;a href=&quot;http://truelicense.net&quot;&gt;TrueLicense&lt;/a&gt; product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="patterns" /><category term="java" /><category term="mvc" /><category term="model-view-controller" /><category term="wizard-dialog" /><summary type="html">Wizard dialogs are a very common design pattern for user interfaces, e.g. for the GUI of software installation tools. However, although wizard dialogs are so ubiquitious these days, component frameworks such as Swing or JSF do not provide any support for them, so you have to turn to a third party library or roll your own. In this blog posting, I am modeling an abstraction for internationalized wizard dialogs which is independent of the particular user interface technology and easy to implement and use, e.g. in Swing based desktop applications or JSF based server applications.</summary></entry><entry><title type="html">Guice Demo and Alternative DSL</title><link href="https://illegalexception.schlichtherle.de/java/2013/06/19/guice-demo-and-alternative-dsl/" rel="alternate" type="text/html" title="Guice Demo and Alternative DSL" /><published>2013-06-19T19:59:12+02:00</published><updated>2013-06-19T19:59:12+02:00</updated><id>https://illegalexception.schlichtherle.de/java/2013/06/19/guice-demo-and-alternative-dsl</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/java/2013/06/19/guice-demo-and-alternative-dsl/">&lt;p&gt;I just created a demo for &lt;a href=&quot;http://code.google.com/p/google-guice/&quot;&gt;Guice&lt;/a&gt;, Google’s popular dependency injection framework.
The &lt;a href=&quot;https://github.com/christian-schlichtherle/guice-demo&quot;&gt;Guice Demo&lt;/a&gt; is a bit more complex and realistic than the samples in the Guice Wiki.
The &lt;a href=&quot;http://christian-schlichtherle.github.io/guice-demo/&quot;&gt;documentation&lt;/a&gt; discusses some general aspects of applying the Dependency Inversion
Principle.&lt;/p&gt;

&lt;p&gt;As an aftermath of this demo, I’ve also created &lt;a href=&quot;https://github.com/christian-schlichtherle/guicer&quot;&gt;Guicer&lt;/a&gt;, an alternative Domain Specific Language (DSL) for configuring
a Guice &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Injector&lt;/code&gt; which is simpler and more concise than the original Guice DSL.
The &lt;a href=&quot;https://guicer.java.net&quot;&gt;documentation&lt;/a&gt; introduces you to the DSL.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="java" /><category term="guice" /><category term="guice-demo" /><category term="guicer" /><category term="dependency-injection" /><summary type="html">I just created a demo for Guice, Google’s popular dependency injection framework. The Guice Demo is a bit more complex and realistic than the samples in the Guice Wiki. The documentation discusses some general aspects of applying the Dependency Inversion Principle.</summary></entry><entry><title type="html">How To Use Markdown Syntax With The Maven Site Plugin</title><link href="https://illegalexception.schlichtherle.de/maven/2013/06/17/how-to-use-markdown-syntax-with-the-maven-site-plugin/" rel="alternate" type="text/html" title="How To Use Markdown Syntax With The Maven Site Plugin" /><published>2013-06-17T15:14:53+02:00</published><updated>2013-06-17T15:14:53+02:00</updated><id>https://illegalexception.schlichtherle.de/maven/2013/06/17/how-to-use-markdown-syntax-with-the-maven-site-plugin</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/maven/2013/06/17/how-to-use-markdown-syntax-with-the-maven-site-plugin/">&lt;p&gt;At current, there is very little information about how to use Markdown syntax to author pages for a project site with
the maven-site-plugin.
With this blog posting, I am going to change this - at least a little bit. ;-)&lt;/p&gt;

&lt;p&gt;First, you should use version 3.3 of the Maven Site Plugin because it already defines a dependency on the Doxia Module
Markdown, version 1.4.
In your POM, add the following lines:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;project&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;pluginManagement&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-site-plugin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.3&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
                ...
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/pluginManagement&amp;gt;&lt;/span&gt;
        ...
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
    ...
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, in your project directory, create the directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/site/markdown&lt;/code&gt;.
This will host your Markdown files.
Markdown files may end with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md.vm&lt;/code&gt; extension.
In the latter case, it gets preprocessed with the Velocity template engine, so you can evaluate some macros or
expressions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${project.name}&lt;/code&gt; in your Markdown files.&lt;/p&gt;

&lt;p&gt;Next, to exploit the power of the maven-site-plugin, create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.md.vm&lt;/code&gt; in this directory.
This is the start page of the project site.
Note that you don’t need to create a site descriptor for this sample.
Make the contents of the file look like this:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#set($h1 = '#')
#set($h2 = '##')
#set($h3 = '###')
#set($h4 = '####')
&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Welcome to ${project.name}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

$h1 Welcome to ${project.name}

Lorem ipsem dolor sit amet...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There needs to be some explanation here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Because the file ends with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vm&lt;/code&gt; extension, it gets processed by the Velocity template engine.
Unfortunately, Velocity uses a single hash character &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; as the prefix for macros and two hash characters as the
prefix for comments.
To escape these, I have chosen to define a variable for each heading level: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$h1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$h2&lt;/code&gt;, etc.&lt;/li&gt;
  &lt;li&gt;You sure want to define some title in an HTML head, don’t you?
Unfortunately, Markdown doesn’t directly support this.
However, it does support to embed HTML tags in a page.
Fortunately, if the tag is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; then the Doxia Module Markdown will put its contents into the HTML head.&lt;/li&gt;
  &lt;li&gt;In a Velocity template, the maven-site-plugin supports accessing the POM via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${project.X}&lt;/code&gt; expressions, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; is
the element you want to access.
I have used this vehicle to quote the project name in the HTML title and level one heading of the document.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, to start your sample site, run the following command …&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mvn site:run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and point your browser to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8080&lt;/code&gt; to see the magic happening.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="maven" /><category term="doxia" /><category term="doxia-module-markdown" /><category term="maven-site-plugin" /><category term="markdown" /><summary type="html">At current, there is very little information about how to use Markdown syntax to author pages for a project site with the maven-site-plugin. With this blog posting, I am going to change this - at least a little bit. ;-)</summary></entry><entry><title type="html">On Domain Specific Languages in Java: Lambda Expressions</title><link href="https://illegalexception.schlichtherle.de/patterns/2013/06/06/on-domain-specific-languages-in-java-part-3-lambda-expressions/" rel="alternate" type="text/html" title="On Domain Specific Languages in Java: Lambda Expressions" /><published>2013-06-06T21:01:11+02:00</published><updated>2013-06-06T21:01:11+02:00</updated><id>https://illegalexception.schlichtherle.de/patterns/2013/06/06/on-domain-specific-languages-in-java-part-3-lambda-expressions</id><content type="html" xml:base="https://illegalexception.schlichtherle.de/patterns/2013/06/06/on-domain-specific-languages-in-java-part-3-lambda-expressions/">&lt;p&gt;In this final part of the
&lt;a href=&quot;/patterns/2013/06/06/on-domain-specific-languages-in-java-part-1-method-chaining/&quot;&gt;series&lt;/a&gt; I am
going to look into the Lambda Expressions pattern to define an internal DSL for constructing object graphs.
I am also going to provide some conclusions for the three patterns.&lt;/p&gt;

&lt;p&gt;Lambda Expressions are all the rage these days.
The tide started to rise with the paradigm shift towards multi-threaded applications.
It’s arguably simpler to create a thread-safe application if it’s solely composed of pure functions (i.e. methods
without side effects) than the traditional way of composing and sharing potentially mutable objects.
Although you can certainly write functional code in a non-functional language like Java 7, it gets a lot easier with
Lambda Expressions in Java 8.&lt;/p&gt;

&lt;p&gt;Let’s look at the following
&lt;a href=&quot;http://blog.sanaulla.info/2013/05/30/creating-internal-dsls-in-java-java-8-adopting-martin-fowlers-approach/#lambda&quot;&gt;example code&lt;/a&gt;
from Mohamed Sanaulla’s excellent blog posting again:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12.3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;10.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First, the good parts:
Like with the Method Chaining pattern (if done right), the order of statements is irrelevant:
You can swap them at will and it will still construct virtually the same object graph.&lt;/p&gt;

&lt;p&gt;Another advantage is that you can provide some context with configurable default values for some properties of the
constructed object graph.
Consider this example:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GraphBuilder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Consumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;EdgeBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;EdgeBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eBuilder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EdgeBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;eBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;defaultWeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// configure default value&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;eConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;eBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this modified example from Mohamed Sanaulla, I have added the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eBuilder.weight(defaultWeight)&lt;/code&gt; statement.
The effect is that if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eConsumer&lt;/code&gt; does not call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weight(Double w)&lt;/code&gt; method, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaultWeight&lt;/code&gt; will prevail.
Note that because this is an instance field, I do not need to share mutable state with other threads (I am assuming that
there is a getter and a setter method for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaultWeight&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;As you can see, the Lambda Expressions pattern is pretty much on par with the Method Chaining pattern.
My biggest grief about it is that it’s a lot more code to write and read.
Compare the initial example to the equivalent expression with Method Chaining again (I am using my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Builder&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Injection&lt;/code&gt; interfaces again):&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12.3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;10.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code has the same number of lines (twelve), yet it doesn’t need the ugly function and method call notation.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Finally, let’s compare the three patterns Method Chaining, Nested Functions and Lambda Expressions for defining an
internal DSL for constructing object graphs:
Among the three, the Nested Functions pattern clearly wins the competition for conciseness.
As a reminder, here is the equivalent expression using the Nested Functions pattern to construct the object graph:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12.3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;10.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is just four lines of code instead of twelve - hooray!
However, the Nested Functions pattern has two serious disadvantages:
First, it can’t disambiguate parameters of the same type, so it can’t help you to avoid mistakes in their ordering.
Second, it can’t easily provide some configurable context to the construction procedure without sharing mutable state.
Considering the shift of paradigm to multithreaded applications, the last point is probably the most important.
Therefore, I would only choose this pattern if these points do not apply and I could be confident that they still don’t
apply when considering foreseeable changes in future versions of my code.&lt;/p&gt;

&lt;p&gt;The Method Chaining and Lambda Expressions pattern do much better in this respect.
Though they are functionally equivalent, the Method Chaining pattern is more concise.
And then there is another difference:
The Method Chaining pattern naturally restricts you to do only object graph construction (anything else will not
compile) and the IDE can easily guide you through this procedure with code completion.
In contrast, there are no such constraints with the Lambda Expressions pattern.
You may think this is an advantage of the Lambda Expressions pattern, but I consider this being a disadvantage:
My argument is that the purpose is object construction here and anything else should be inhibited (e.g. compiling your
claim for tax refunds).&lt;/p&gt;

&lt;p&gt;So the winner is… (drum roll and fanfare): The Method Chaining pattern! (your mileage may vary)&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><author><name></name></author><category term="patterns" /><category term="dsl" /><category term="java" /><category term="lambda-expressions" /><summary type="html">In this final part of the series I am going to look into the Lambda Expressions pattern to define an internal DSL for constructing object graphs. I am also going to provide some conclusions for the three patterns.</summary></entry></feed>