<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The Boy Wonders</title>
    <description>A Blog by Robin Howlett</description>
    <link>https://robinhowlett.com/</link>
    <atom:link href="https://robinhowlett.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 26 Aug 2025 00:51:48 +0000</pubDate>
    <lastBuildDate>Tue, 26 Aug 2025 00:51:48 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Accessible communication with Twilio&apos;s Programmable Messaging API, JBang, and PicoCLI</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/g3GA6Ms.png&quot; alt=&quot;demo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the mid-to-late 1990s when I was in secondary school in Ireland, I chose to participate in the optional one-year Transition Year (TY) school program. TY lets students combine a regular academic school year with opportunities to participate in independent activites, including volunteer engagements.&lt;/p&gt;

&lt;p&gt;I worked one month with the &lt;a href=&quot;https://www.rehab.ie&quot;&gt;Rehab Group&lt;/a&gt;, a charity that provides people with a disability or disadvantage educational services and professional training. As I had developed a decent familiarity with personal computers by then, my responsibility was to train basic computer skills.&lt;/p&gt;

&lt;p&gt;Those individuals’ disadvantages included prosthetic limbs, speech impediments, and learning difficulties, among others. Many had never used a computer before. For those that struggled to type, I introduced voice recognition software (Dragon Dictate), so they could speak into a microphone to “write” emails to relatives. I showed them how to use Microsoft Word and find information using a web browser.&lt;/p&gt;

&lt;p&gt;There was one incident however that stuck with me all these years later. An elderly gentleman entered the training room and sat down at the computer, visibly nervous.&lt;/p&gt;

&lt;p&gt;The first thing I did with every person was to ask them turn on the desktop computer via a button on the front of the box. Most pressed the button without issue, but he was extremely hesitant to touch the device.&lt;/p&gt;

&lt;p&gt;I demonstrated the various components - the monitor, the keyboard, the mouse. He expressed concern that if he did the wrong thing, would the computer “blow up”? I reassured him that we were safe and that the computer would not physically harm him.&lt;/p&gt;

&lt;p&gt;Once Windows had loaded and the desktop was displayed, it was time for the first lesson - opening an application.&lt;/p&gt;

&lt;p&gt;“Move the mouse to the Start Menu over here please”, I said.&lt;/p&gt;

&lt;p&gt;He glanced at me, nodded, and looked at the mouse. He then picked it up, raising it into the air and held it to the bottom-left corner of the monitor’s screen.&lt;/p&gt;

&lt;p&gt;I do not tell this story to mock him. What I realized that day is the interfaces we use with computers should not be assumed to be natural. That the instructional language we use is often abstract and assumes a level of technical familiarity above what people may be comfortable with, or even capable of.&lt;/p&gt;

&lt;p&gt;Ever since then, I’ve always been drawn to designs and solutions that leveraged technology in a manner that people like that gentleman at Rehab could avail of.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;reporting-disease-cases-in-remote-areas-of-south-east-asia-using-paper-wheels-and-sms&quot;&gt;Reporting disease cases in remote areas of South-East Asia using paper wheels and SMS&lt;/h2&gt;

&lt;p&gt;In that vein, I loved recently discovering InSTEDD’s &lt;a href=&quot;https://instedd.org/about-us/team/staff/nicolas-di-tada&quot;&gt;Nicolás di Tada’s&lt;/a&gt; blog post from 2010, &lt;a href=&quot;http://ndt.instedd.org/2010/05/it-without-software.html?m=1&quot;&gt;“IT without Software”&lt;/a&gt; describing how his team needed to build a system for workers at remote health centers in Thailand and Cambodia to report disease cases data in a semi-structured way.&lt;/p&gt;

&lt;p&gt;Most case reports were being communicated by phone calls to the district offices, which aggregated the data by province, losing the fidelity of the original health center’s report.&lt;/p&gt;

&lt;p&gt;The team wished to use SMS as the primary communication medium, but there were several challenges identified that needed to be addressed in determining a reporting syntax, including:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;Most people do not know how to send SMS.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Some of them do not know how to read an incoming SMS.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Support for Khmer and Thai characters is not common in the handsets and carriers most people use.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Even if there is support for the characters, writing SMS using them is much more difficult than writing in English due to the amount of letters in the alphabet.&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The InSTEDD team devised an ingenious solution to this - physical reporting wheels made of basic materials like paper or card-stock:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://3.bp.blogspot.com/_vWSpQpYPpFE/TBuG_CFNCEI/AAAAAAAAAB8/dtZ6hLucLKg/s400/reporting-wheel.png&quot; alt=&quot;recording-wheel-1&quot; /&gt;
&lt;img src=&quot;http://2.bp.blogspot.com/_vWSpQpYPpFE/TBuG_vzCu3I/AAAAAAAAACM/M5NdKLOibnc/s400/reporting-wheel3.png&quot; alt=&quot;recording-wheel-2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The wheels were cheap and easy to build but sturdy, with no batteries required and intuitive within minutes. You may have seen children using similar devices, called decoder wheels, to create “secret codes” to exchange with each other.&lt;/p&gt;

&lt;p&gt;The reporting wheel the team created enabled health center workers align 3 independently-rotating wheels, each with a 3-digit code for choosing one of an enumerated set values for the respective data point (in this case, the day of the month, the disease, and the number of cases being reported).&lt;/p&gt;

&lt;p&gt;Once each value has been chosen and aligned with a indicator, the health worker would have a nine-digit code that codifies the data values and could send that code via SMS to a cell phone number. The service will then reverse engineer that code back into the original data values.&lt;/p&gt;

&lt;h3 id=&quot;codification-of-the-nine-digit-message&quot;&gt;Codification of the nine-digit message&lt;/h3&gt;

&lt;p&gt;Most interestingly of all, the system also needed to address some major usability aspects:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How could typos or data entry mistakes be identified to prevent misreporting disease case data?&lt;/li&gt;
  &lt;li&gt;How could this solution scale to different kinds of reports without having to ask the user to identify the type of wheel being used?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While not outlined explicity in the post, the images above provide the answer. For each wheel, the first code value is a prime number and each subsequent value is a multiple of it. The other wheels also started with a prime number - indeed, sequential primes are being used - 23, 29, 31.&lt;/p&gt;

&lt;p&gt;This seemed like a fun idea to experiment with to try out Twilio’s SDK and some other libraries that had recently caught my attention.&lt;/p&gt;

&lt;h2 id=&quot;scriptable-java-command-line-tools-with-jbang-and-picocli&quot;&gt;Scriptable Java command-line tools with JBang and PicoCLI&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jbang.dev/&quot;&gt;JBang&lt;/a&gt;, by &lt;a href=&quot;https://twitter.com/maxandersen&quot;&gt;Max Andersen&lt;/a&gt;, is one of my favorite open-source projects of recent years. It’s premise is simple - make scripting with Java as fast and as easy as other languages like python or kotlin.&lt;/p&gt;

&lt;p&gt;At SnapLogic, it’s been brilliant for my team to use it for rapid exploration of various SDKs and APIs, and to reproduce specific scenarios quickly.&lt;/p&gt;

&lt;p&gt;In fact, I believe I first heard about JBang via Twilio’s Developer Evangelist, &lt;a href=&quot;https://www.twitch.tv/maximumgilliard&quot;&gt;Matthew Gillard’s Twitch channel&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;simulating-the-reporting-wheel&quot;&gt;Simulating the Reporting Wheel&lt;/h3&gt;

&lt;p&gt;The first action was to build a script to simulate the reporting wheel demonstrated above. I wanted a command-line interface (CLI) approach where the user could provide the data points as arguments or be prompted for each one, validating the input as it went.&lt;/p&gt;

&lt;p&gt;JBang’s landing page demo used &lt;a href=&quot;https://picocli.info/&quot;&gt;PicoCLI&lt;/a&gt; so that was good enough for me and I was able to quickly define the options the user would provide:&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;c1&quot;&gt;//usr/bin/env jbang &quot;$0&quot; &quot;$@&quot; ; exit $?&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//JAVA 11+&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//DEPS info.picocli:picocli:4.2.0&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//SOURCES Disease.java&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.concurrent.Callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;picocli.CommandLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;picocli.CommandLine.Command&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Command&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ReportingWheel&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mixinStandardHelpOptions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SendSms 0.1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;An interactive CLI to simulate using a reporting wheel to generate 9-digit&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot; codes to send via SMS&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReportingWheel&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&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;@CommandLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;names&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;s&quot;&gt;&quot;-d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--day&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;The @|bold numeric day|@ of the month&quot;&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;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@CommandLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;names&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;s&quot;&gt;&quot;-di&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--disease&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;The @|bold disease code|@ you are reporting&quot;&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@CommandLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;names&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;s&quot;&gt;&quot;-c&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--cases&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;The @|bold number of cases|@ to report for that day&quot;&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;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numCases&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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&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;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exitCode&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;CommandLine&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;ReportingWheel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exitCode&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;JBang will download Java 11 if the user does not have it installed, it will download the PicoCLI dependencies need to compile the code and package it into an executable JAR file.&lt;/p&gt;

&lt;h3 id=&quot;codification-of-the-message-via-prime-numbers&quot;&gt;Codification of the message via Prime numbers&lt;/h3&gt;

&lt;p&gt;I wanted to simulate using different reporting wheel types targeting the same reporting service number, so I randomized which prime would be used as the intial seed value for the first “wheel” value. The next 2 prime numbers would then be used as the prime seeds for the second and third wheel values respectively.&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;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&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;c1&quot;&gt;// primes &amp;lt; 32 since there are 1000 possible values for a 3-digit code, but a max &quot;day&quot;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// value of 31 so 1000/31 = 32.25...&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// There are only 7 diseases and 20 valid &quot;cases&quot; numbers permitted, so we go with 31 as max&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&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;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// we&apos;ll be randomly selecting 3 sequential primes, so the max 1st index is 3 from the end&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// this is randomized to simulate different wheels being used for different purposes&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// the point is the user doesn&apos;t need to do anything other than text the 9-digit number&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// (no secret key etc. needed)&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&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;Random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;daySeed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseasesSeed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesSeed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// check if any command-line inputs are valid and if not, ask for them until they are&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// then simulate each data point being selected on a physical reporting wheel and&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// codified to a 3-digit, zero-padded number&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;validateDayInputAskAgainIfNeeded&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%03d&quot;&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;dayOfMonth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;daySeed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// e.g. 003&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printlnAnsi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@|green day=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;, code=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;|@&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lineSeparator&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 how I would ask the user for input, re-asking until valid input values had been provided:&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;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validateDayInputAskAgainIfNeeded&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&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;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&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;printlnAnsi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@|red Missing/invalid day provided (1-31 required)|@&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;askForDayOfMonth&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;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&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;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&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;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;askForDayOfMonth&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;readLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;What day of the month is this report for?: &quot;&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&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;Using PicoCLI like this allowed both a direct and interactive choice of user input. For example, consider the direct invocation:&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;&amp;gt; jbang ReportingWheel.java -d 26 -di m -c 9
day=26, code=182

disease=MALARIA, code=033

cases=9, code=117

Please text this code 182033117 to +14158493243
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;compared to the interactive approach:&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;&amp;gt; jbang ReportingWheel.java 
Missing/invalid day provided (1-31 required)
What day of the month is this report for?: 99

Missing/invalid day provided (1-31 required)
What day of the month is this report for?: -4

Missing/invalid day provided (1-31 required)
What day of the month is this report for?: 26
day=26, code=130

Missing/invalid disease code provided
Which disease are you reporting? (use the single-letter code only): 
c: CHOLERA
d: DENGUE
m: MALARIA
j: JP_ENCEPH
t: TYPHOID
h: HEPATITIS
v: COVID19
m
disease=MALARIA, code=021

Missing/invalid cases metric provided (1-20 required)
How many cases are you reporting?: 9
cases=9, code=099

Please text this code 130021099 to +14158493243
THANK YOU FOR YOUR REPORT!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You may notice that the nine-digit codes generated are different (“182033117” vs “130021099”) for the same input values, but that is due to the randomized prime seed selection simulating different wheel types being used (even though the data points being used are the same).&lt;/p&gt;

&lt;h3 id=&quot;decoding-the-message-and-replying-via-twilio-enabled-sms&quot;&gt;Decoding the message and replying via Twilio-enabled SMS&lt;/h3&gt;

&lt;p&gt;To decode the message being sent, I needed a few things first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A phone number to send SMS messages to&lt;/li&gt;
  &lt;li&gt;A programmable mechanism to receive the message that was sent&lt;/li&gt;
  &lt;li&gt;A way to reply to the received SMS with the decoded data values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My team had built the &lt;a href=&quot;https://community.snaplogic.com/t/launched-twilio-snap-pack/9645&quot;&gt;Twilio Snap Pack&lt;/a&gt; and had reported on the excellent quality of the APIs and SDKs provided.&lt;/p&gt;

&lt;p&gt;I purchased an SMS-capable &lt;a href=&quot;https://www.twilio.com/docs/phone-numbers&quot;&gt;phone number&lt;/a&gt; from Twilio for $1 in about ten seconds.&lt;/p&gt;

&lt;p&gt;Next, I read up about &lt;a href=&quot;https://www.twilio.com/docs/glossary/what-is-a-webhook&quot;&gt;webhooks&lt;/a&gt; that could be used to receive callback requests when messages had been received by the newly-purchased Twilio number. I didn’t really want to set up a server on the public internet for this, but Twilio’s CLI came to the rescue with an excellent developer-friendly feature.&lt;/p&gt;

&lt;p&gt;The tool had integrated with &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt; to create a “tunnel” between ngrok and my laptop. Any requests to the assigned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngrok.io&lt;/code&gt; endpoint would be “forwarded” to the web service running on my local machine. This allows real local debugging in my IDE rather than relying on webhook-capture sites like &lt;a href=&quot;https://requestbin.com/&quot;&gt;RequestBin.com (Pipedream)&lt;/a&gt;.&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;&amp;gt; twilio phone-numbers:update &quot;+14158493243&quot; --sms-url=&quot;http://localhost:4567/sms&quot;
SID                                 Result   SMS URL                          
PN14dcf0f9df39d2ae7f207084519db4da  Success  https://dba7b30ce2c2.ngrok.io/sms
ngrok is running. Open http://127.0.0.1:4040 to view tunnel activity.
Press CTRL-C to exit.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This updated my Messaging Service’s configuration automatically:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Kujx7oY.png&quot; alt=&quot;clever&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, it was time to build the web service that would receive the webhook request from Twilio containing the codified message. I tweaked &lt;a href=&quot;https://www.twilio.com/docs/sms/quickstart/java?code-sample=code-respond-to-an-incoming-text-message&amp;amp;code-language=Java&amp;amp;code-sdk-version=8.x&quot;&gt;an existing Twilio example&lt;/a&gt; that used the &lt;a href=&quot;http://sparkjava.com/&quot;&gt;Spark Java Framework&lt;/a&gt; to create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/sms&lt;/code&gt; API endpoint that received the POST request:&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;cm&quot;&gt;/**
 * This Spark web server provides an /sms endpoint to receive Twilio webhook callbacks and
 * decode the codified message, replying to the SMS with the decoded data in a text
*/&lt;/span&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;ReportingServer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&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;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&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;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&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;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Health Check&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sms&quot;&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;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webhookParts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;amp;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webhookPart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webhookParts&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;webhookPart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Body=&quot;&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webhookPart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;=&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;DecodedMessage&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decodeMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&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;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Oops! Your message does not appear to be valid.&quot;&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;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;break&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;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&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;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// do nothing for now&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;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;An error was encountered&quot;&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;nc&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&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;Body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;responseText&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;span class=&quot;nc&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sms&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;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&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;span class=&quot;nc&quot;&gt;MessagingResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;twiml&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;MessagingResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sms&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;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;twiml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toXml&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;Run the server via JBang like so - it will start listening for requests from the Twilio webhook:&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;&amp;gt; jbang ReportingServer.java 
[jbang] Building jar...


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My approach to reverse engineer the codified message is a little difficult to explain consisely but it follows this logic:&lt;/p&gt;

&lt;h4 id=&quot;build-a-cache-of-all-the-prime-number-seeds-by-each-of-the-possible-day-of-month-3-digit-codes&quot;&gt;Build a cache of all the prime number seeds by each of the possible day-of-month 3-digit codes&lt;/h4&gt;

&lt;p&gt;I didn’t want to blindly build an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n * n * n&lt;/code&gt; hashtable for all possible combinations of 9-digit codes - I figured there probably was a more efficient approach.&lt;/p&gt;

&lt;p&gt;For example, for prime number seed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;17&lt;/code&gt;, the codified values for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;017&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;034&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;051&lt;/code&gt; and so on. My first cache map would then have entries with keys &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;017&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;034&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;051&lt;/code&gt;, with each mapping to a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;17&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, taking the first three digits of the codified message, I could figure out which prime number seed or seeds was potentially used.&lt;/p&gt;

&lt;p&gt;I would need to handle collisions - consider the code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;006&lt;/code&gt;. It could mean the 3rd day of the month when the prime seed was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;, or it could also mean it was the 2nd day of the month when the prime seed was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Knowing which prime seed was used could be determined by examining the remaining digits of the codified message (identifying the diseases and number-of-cases).&lt;/p&gt;

&lt;h4 id=&quot;build-a-cache-of-all-the-possible-disease-and-number-of-cases-3-digit-codes-for-each-prime-number&quot;&gt;Build a cache of all the possible disease and number-of-cases 3-digit codes for each prime number&lt;/h4&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;006&lt;/code&gt; example, we know that the prime number seed used for the day-of-month value was either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;. And since the code generation logic uses sequential prime numbers for the 3-digit codes for disease and number-of-cases, then the middle 3-digit code must be one of the possible values when the prime number seed is either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; (the prime after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;) or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt; (the prime after 3).&lt;/p&gt;

&lt;p&gt;Similarly, last 3-digits for the number-of-cases value would use prime number seeds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other two cache maps would then have keys &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7&lt;/code&gt; etc. whose values were lists of codified number multiples e.g.&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;3 =&amp;gt; [003, 006, 009, ...]
5 =&amp;gt; [005, 010, 015, ...]
7 =&amp;gt; [007, 014, 021, ...]
...
20 =&amp;gt; [020, 040, 060, ...] (for number-of-cases only)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&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;cm&quot;&gt;/**
 * Messages are encoded by first selecting a random prime number &amp;lt; 32 for the day-of-month seed,
 * and then using the next two primes for the disease and number-of-cases seeds. That means that
 * if you can identify the prime number used for the day-of-month 3-digit code, you know what
 * are the possible valid codes for the other values provided too, and the entire message can be
 * both decoded and validated (since a typo in a code provided by a user will result in a value
 * being provided that doesn&apos;t adhere to the sequential-prime-seed rule)
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DecodedMessage&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;decodeMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedMessage&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;c1&quot;&gt;// these data structures will be used for efficient lookups to reverse engineer&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// the codified message to the original day-of-month, disease, and number-of-cases data&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// values that the reporting user originally chose.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// It does this by attempting to figure which prime number was used as the day-of-month seed&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// and then validates the user&apos;s message by confirming that the respective 3-digit codes&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// for the disease and number-of-cases are legal values&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// key = 3 digit code for each day of month (1-31), value = map(key=index in primes&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// array, value=multiplier)&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;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesByDayCode&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;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// key = index in primes array, value = list(valid disease codes for equivalent prime)&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrimeIndex&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;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// key = index in primes array, value = list(valid cases codes for equivalent prime)&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodesByPrimeIndex&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;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// for each permissible prime number, populate the above data structures&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&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;n&quot;&gt;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&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;c1&quot;&gt;// the day codes will only use the 1st to the third-last prime indexes for 9-digit codes&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;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;c1&quot;&gt;// for each day of the month, build the lookup cache for primes by legal 3-digit&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// day-of-month codes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;primeIndexesByDayCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildPrimeIndexLookupsByDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndexesByDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;primeIndex&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;c1&quot;&gt;// the disease codes will only use the 2nd to the second-last prime indexes for 9-digits&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;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&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;c1&quot;&gt;// for each prime, build a cache of legal 3-digit disease codes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrimeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;buildDiseaseCodeLookupByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diseaseCodesByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;primeIndex&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;c1&quot;&gt;// the cases codes will only use the 3rd to the last prime indexes for 9-digit codes&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;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&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;c1&quot;&gt;// for each prime, build a cache of legal 3-digit number-of-cases codes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;casesCodesByPrimeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildPrimeLookupByCasesCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;casesCodesByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;primeIndex&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;The cache-building logic 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;cm&quot;&gt;/**
 * For each day of the month, generate a 3-digit-code and note which prime seeds would result in
 * that code being a legal value
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&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;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildPrimeIndexLookupsByDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&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;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayCodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&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;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayIndex&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%03d&quot;&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;dayIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;dayCodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dayCode&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;dayCodes&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;dayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesForDayCode&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;primeIndexesForDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;dayCodes&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;dayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesForDayCode&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayCodes&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;c1&quot;&gt;// as above&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildDiseaseCodeLookupByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&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;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Disease&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Disease&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%03d&quot;&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;dis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&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;mi&quot;&gt;1&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;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;diseaseCodesByPrime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;diseaseCodesByPrime&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;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diseaseCode&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodes&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;diseaseCodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diseaseCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrime&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;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodes&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrime&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;c1&quot;&gt;// and as above also&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildPrimeLookupByCasesCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&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;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodesByPrime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&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;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesIndex&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;%03d&quot;&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;casesIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;casesCodesByPrime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&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;casesCodesByPrime&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;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;casesCode&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodes&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;casesCodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;casesCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;casesCodesByPrime&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;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodes&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodesByPrime&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;h4 id=&quot;identify-the-prime-seeds-used-in-the-original-message&quot;&gt;Identify the prime seeds used in the original message&lt;/h4&gt;

&lt;p&gt;Through a process of elimiation, only one combination will be valid. Once that combination is found, we can fully decode the message via the indexes of the matched codes. If no combinations applied, then the code received is invalid (i.e. likely a typo occurred):&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;c1&quot;&gt;// the first 3 digits correspond the codified day of the month&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impliedDayCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;substring&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;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// check if the codified message is valid by checking if each of the day-of-month,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// disease, and number-of-cases 3-digit codes are legal; if they all are, then the message&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// is valid and we can reverse engineer the user&apos;s original selections&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;primeIndexesByDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedDayCode&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;c1&quot;&gt;// the primes that are legal for this day-of-month code&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesForDayCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesByDayCode&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;impliedDayCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primeIndexesForDayCode&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;c1&quot;&gt;// now check if the disease code is legal&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;diseaseCodesByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&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;c1&quot;&gt;// get the legal disease codes for the next sequential prime&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;diseaseCodeIndexesByCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;diseaseCodesByPrimeIndex&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;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impliedDiseaseCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&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;diseaseCodeIndexesByCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedDiseaseCode&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;c1&quot;&gt;// finally check if the number-of-cases code is legal&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;casesCodesByPrimeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;casesCodeIndexesByCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;casesCodesByPrimeIndex&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;primeIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impliedCasesCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;codifiedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&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;casesCodeIndexesByCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedCasesCode&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;c1&quot;&gt;// if we have reached this point, then all 3-digit codes are&lt;/span&gt;
                        &lt;span class=&quot;c1&quot;&gt;// legal and therefore the message is legal and valid. We also know&lt;/span&gt;
                        &lt;span class=&quot;c1&quot;&gt;// now what prime number seeds were used by the original user&apos;s&lt;/span&gt;
                        &lt;span class=&quot;c1&quot;&gt;// reporting wheel and therefore can figure out what the original&lt;/span&gt;
                        &lt;span class=&quot;c1&quot;&gt;// non-encoded values were for day-of-month, disease, and&lt;/span&gt;
                        &lt;span class=&quot;c1&quot;&gt;// number-of-cases&lt;/span&gt;
                        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                                &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedDayCode&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;primes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primeIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;Disease&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;disease&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                                &lt;span class=&quot;nc&quot;&gt;Disease&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diseaseCodeIndexesByCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedDiseaseCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)];&lt;/span&gt;
                        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numberOfCases&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
                                &lt;span class=&quot;n&quot;&gt;casesCodeIndexesByCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impliedCasesCode&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;mi&quot;&gt;1&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;nf&quot;&gt;DecodedMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dayOfMonth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;disease&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numberOfCases&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;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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;h4 id=&quot;reply-via-twilio-sms&quot;&gt;Reply via Twilio SMS&lt;/h4&gt;

&lt;p&gt;Finally, we have what we need to reply to the sender, confirming the original data values that the health worker chose on the reporting wheel and thanking them for their report:&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;Body&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&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;Body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;responseText&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;span class=&quot;nc&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sms&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;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&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;span class=&quot;nc&quot;&gt;MessagingResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;twiml&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;MessagingResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sms&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;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;twiml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toXml&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;img src=&quot;https://i.imgur.com/7QjRDtB.png&quot; alt=&quot;valid&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If the codified message received was invalid, we would let them know also so they can check their wheel and resend again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/qQW6sXk.png&quot; alt=&quot;invalid&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The code used in this project is available here: https://github.com/robinhowlett/accessible-sms&lt;/p&gt;

&lt;h2 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h2&gt;

&lt;p&gt;There wasn’t anything particularly brilliant on my part about this application. Yes, the prime number seed approach can require some focus to follow, but the idea came from the InSTEDD team and I just applied a simplified version of it.&lt;/p&gt;

&lt;p&gt;Twilio also did all the heavy lifting with running virtual phone numbers, on-message webhooks, ngrok integration and a well-designed SDK.&lt;/p&gt;

&lt;p&gt;I’m sure there is plenty of other material out there showing much more sophisticated demonstrations of communication platforms with richer user interfaces and experiences.&lt;/p&gt;

&lt;p&gt;But what is worth remembering is that when communications techology is used in a way that accomodates &lt;em&gt;everybody&lt;/em&gt;, even those sometimes forgotten because of resources, language, education, opportunity or accessibility, it can still provide life-changing benefits to real people, even in the farthest reaches of the globe.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Jul 2021 00:00:00 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2021/07/26/accessible-communication-sms-with-twilio-jbang-picocli/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2021/07/26/accessible-communication-sms-with-twilio-jbang-picocli/</guid>
        
        <category>code</category>
        
        <category>java</category>
        
        
      </item>
    
      <item>
        <title>Parsing structured data within PDF documents with Apache PDFBox</title>
        <description>&lt;p&gt;PDF continues to be a popular document publishing format because users see them as the digital equivalent of paper documents. Unlike websites, often what you see on the PDF will be exactly how it will be printed on a physical page, with the added benefits of easily distributable files and near-ubiquitous support of software able to read this format on almost any standard digital device.&lt;/p&gt;

&lt;p&gt;However, when information, especially structured data, is contained within a PDF document and one wishes to extract that content, the format becomes quite difficult for developers to interact with.&lt;/p&gt;

&lt;p&gt;In this post, I outline a real-world example of parsing a large PDF file that contains repeated tables of data. I show how the raw text can be extracted and then detail much more low-level control over the text characters positioned within the pages. I also touch on the actual mechanics of working through a problem like this - using tools like Excel to explore and analyze both the nature of the PDF, as well as the vagaries of the data itself.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/bcbc_snippet_2018.png&quot; alt=&quot;BCBC Results Snippet&quot; /&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;breeders-cup-betting-challenge&quot;&gt;Breeders’ Cup Betting Challenge&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.breederscup.com/bcbc&quot;&gt;Breeders’ Cup Betting Challenge (BCBC)&lt;/a&gt; is an annual $10,000 buy-in, live-money horse racing handicapping tournament tied to the two-day, 14-race $30 million Breeders’ Cup World Championships event. In 2018, 391 entries competed for the $1 million prize pool. It would also be the first time that the Breeders’ Cup had &lt;a href=&quot;https://www.breederscup.com/article/breeders-cup-announces-rule-changes-2018-breeders-cup-betting-challenge&quot;&gt;taken the decision to publish all the players’ tournament wagers placed&lt;/a&gt; at the conclusion of the event.&lt;/p&gt;

&lt;p&gt;A few days after the competition ended, &lt;a href=&quot;http://www.breederscup.com/sites/default/files/2018%20BCBC%20Final.pdf&quot;&gt;a 900+ page PDF file&lt;/a&gt; was posted to the Breeders’ Cup website containing a breakdown of all of the wagers placed by each player.&lt;/p&gt;

&lt;p&gt;Intrigued by this rare example of transparency into how professional and advanced horse racing tournament players approached this format, I decided to see if I could extricate the data within to conduct some analysis for educational and entertainment purposes.&lt;/p&gt;

&lt;h3 id=&quot;apache-pdfbox&quot;&gt;Apache PDFBox&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://pdfbox.apache.org/&quot;&gt;Apache PDFBox library&lt;/a&gt; is an open-source Java tool for interacting with PDF documents.&lt;/p&gt;

&lt;p&gt;It allows the “creation of new PDF documents, manipulation of existing documents and the ability to extract content from documents”.&lt;/p&gt;

&lt;p&gt;I’ve found that even for PDFs that turn off the ability to copy text from the document, PDFBox can still extract the content.&lt;/p&gt;

&lt;h4 id=&quot;1-of-3-basic-outputting-the-raw-text-line-by-line&quot;&gt;(1 of 3) Basic: outputting the raw text line-by-line&lt;/h4&gt;

&lt;p&gt;When attempting to parse a PDF generally you first want to just output the raw text to examine if there are any obvious patterns that can be used.&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File&lt;/code&gt; can be read by PDFBox as a PDF document by using &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/pdmodel/PDDocument.html#load-java.io.File-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PDDocument.load()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the file is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PDDocument&lt;/code&gt;, &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/PDFTextStripper.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PDFTextStripper&lt;/code&gt;&lt;/a&gt;’s &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/PDFTextStripper.html#writeText-org.apache.pdfbox.pdmodel.PDDocument-java.io.Writer-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeText()&lt;/code&gt;&lt;/a&gt; method can be used to strip just the text (without any of the formatting and such) and write it to a file:&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;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BCBCParser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDFTextStripper&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;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&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;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&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;c1&quot;&gt;// The order of the text tokens in a PDF file may not be in the same as they appear &lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// visually on the screen, so tell PDFBox to sort by text position &lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;setSortByPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BufferedWriter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newBufferedWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Paths&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;s&quot;&gt;&quot;bcbc.txt&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UTF_8&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;c1&quot;&gt;// This will take a PDDocument and write the text of that document to the writer.&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&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;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;InvalidPasswordException&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;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;printStackTrace&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;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&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;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;printStackTrace&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;kc&quot;&gt;null&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;This resulted in output like the following in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bcbc.txt&lt;/code&gt;:&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;Grubbs Charles 10603
Churchill Downs
Charles Grubbs
Date: 20181102
Race: 1
Pool Bets Refunds Winnings Runners
EXA $250.00 $0.00 $0.00 W1-6/6
WIN $250.00 $0.00 $0.00 6
WIN $100.00 $0.00 $0.00 6
$600.00 $0.00 $0.00 
Race: 3
Pool Bets Refunds Winnings Runners
WIN $400.00 $0.00 $0.00 B3,10
EXA $200.00 $0.00 $0.00 W1,8/3,10
EXA $250.00 $0.00 $0.00 W1,4-6,8/3,10
WIN $50.00 $0.00 $0.00 B3,10
$900.00 $0.00 $0.00 
Race: 5
Pool Bets Refunds Winnings Runners
WIN $150.00 $0.00 $0.00 B8,11,12
WIN $150.00 $0.00 $0.00 B8,11,12
WIN $150.00 $0.00 $0.00 B8,11,12
WIN $150.00 $0.00 $0.00 B8,11,12
WIN $100.00 $0.00 $0.00 8
$700.00 $0.00 $0.00 
Race: 6
Pool Bets Refunds Winnings Runners
WIN $200.00 $0.00 $0.00 14
TRI $240.00 $0.00 $11,154.00 W6/14/1-14
TRI $60.00 $0.00 $0.00 W6/1-14/14
TRI $60.00 $0.00 $0.00 W6/1-14/14
...
$934.00 $0.00 $4,136.00 
Race: 9
Pool Bets Refunds Winnings Runners
TRI $240.00 $0.00 $0.00 W6,9/2,4-6,9,10,12,13/2,
13
$240.00 $0.00 $0.00 
Race: 10
...
2-Day Totals $53,420.00 $0.00 $132,250.00 
Penalty Amount: $0.00 Final Score: 86,330.00
Engler Monte 900000778
Twinspires
Monte Engler
Date: 20181102
Race: 4
Pool Bets Refunds Winnings Runners
DOUBLE $50.00 $0.00 $0.00 2/4
DOUBLE $40.00 $0.00 $0.00 W3/1,4
$90.00 $0.00 $0.00 
Race: 6
Pool Bets Refunds Winnings Runners
TRIFECTA $360.00 $0.00 $0.00 W6/2,11,12/1-14
TRIFECTA $120.00 $0.00 $0.00 W1,6/1-14/1,6
DOUBLE $150.00 $0.00 $0.00 W1,11,12/1-10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Immediately, it could be seen that there are aspects of the output that could prove fruitful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;there are repeated text values (e.g. “Final Score: “) that could be used to mark where sections began and ended e.g. the player’s name, ID and home track always follow that line.&lt;/li&gt;
  &lt;li&gt;as it was a two-day event, the exact race that the wagers were for could be figured out by combining for the “Date: YYYYMMDD” and “Race: N” patterns.&lt;/li&gt;
  &lt;li&gt;some of the data is derived e.g. the bets/refunds/winnings totals-per-day and the Final Score; I generally prefer to parse just the minimum data and calculate those independently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there were some things of concern that were noted:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For wagers with many combinations, the textual representation of the bet often wrapped to the next line - great care would have to be taken to detect and handle that.&lt;/li&gt;
  &lt;li&gt;Oddly, a mix of bet type keys was being used. For example for Trifecta bets, some players had “TRI”, others had “TRIFECTA”. Some people seems to have made bets that were actually against the rules (yet possibly not detected by the tournament software). Clearly some data sanitation would be required. It also means that you can’t always rely on the consistency of specific “special” values but rather try to be rules- and/or pattern-based instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;2-of-3-basic-parsing-the-raw-text-word-by-word&quot;&gt;(2 of 3) Basic: parsing the raw text word-by-word&lt;/h4&gt;

&lt;p&gt;The BCBC table data is simple enough however to figure most of this out with basic rules and some regexes.&lt;/p&gt;

&lt;p&gt;The following is the main parser for the BCBC PDF files (the entire project &lt;a href=&quot;https://github.com/robinhowlett/breeders-cup-betting-challenge-parser&quot;&gt;is available on GitHub&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It builds a list of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BCBCEntry&lt;/code&gt; objects (corresponding to tournament players), each of which contain a list of the parsed bets.&lt;/p&gt;

&lt;p&gt;The overridden &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeText()&lt;/code&gt; method triggers a variety of calls to other methods that can also be overridden to further control the parsing of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PDDocument&lt;/code&gt;, including &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/PDFTextStripper.html#writePage--&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writePage()&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/PDFTextStripper.html#writeCharacters-org.apache.pdfbox.text.TextPosition-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeCharacters(TextPosition text)&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/PDFTextStripper.html#writeString-java.lang.String-java.util.List-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeString(String text, List&amp;lt;TextPosition&amp;gt; textPositions)&lt;/code&gt;&lt;/a&gt; among others.&lt;/p&gt;

&lt;p&gt;The latter is leveraged below to capture specific words that are expected, along with setting various marker booleans that instruct subsequent iterations to extract the desired information. Some pre-processing and sanitation is done by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BCBCEntry&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bet&lt;/code&gt; classes (see GitHub for the details):&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;BCBCParser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDFTextStripper&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;File&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;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LOGGER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCParser&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;span class=&quot;c1&quot;&gt;// regex to match bets e.g. EX, TRI, DD, WIN&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pattern&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BET_TYPE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pattern&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^([A-Z-])+$&quot;&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;BCBCConfig&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntryRelatedText&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Instantiate a new PDFTextStripper object.
     *
     * @throws IOException If there is an error loading the properties.
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BCBCParser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCConfig&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&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;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bcbcEntryRelatedText&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bcbcEntries&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// see https://pdfbox.apache.org/2.0/getting-started.html&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sun.java2d.cmm&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sun.java2d.cmm.kcms.KcmsServiceProvider&quot;&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcResults&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;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcResultsPdf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcResults&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;setSortByPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Writer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;devNullWriter&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;OutputStreamWriter&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;OutputStream&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;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&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;c1&quot;&gt;// discard everything&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;no&quot;&gt;UTF_8&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;c1&quot;&gt;// this will end up calling #writeString() below with the text of each line&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// each line of text can then be examined in the context of the text already&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// parsed, so can figure out if the text is related to the player or the bet etc&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// the line of text itself will be pulled apart to extract the actual bets&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcResultsPdf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;devNullWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// at this point, all the players&apos; start-end line indexes have been saved&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;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;InvalidPasswordException&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;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;no&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PDF Password incorrect&quot;&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;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&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;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;no&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error parsing PDF&quot;&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;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;bcbcEntries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;createBetsForEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntry&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;bcbcEntries&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;c1&quot;&gt;// triggered during stripper.writeText() execution above (by the private stripper.writeLine()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// method) and called for each &quot;word&quot; of a line processed when parsing the PDF (sometimes a word&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// may actually be a single space-separated piece of text)&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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;writeString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TextPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textPositions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// collect all the words relevant for this entry&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bcbcEntryRelatedText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// if &quot;Final Score&quot; was found, then we at the end of content about this player&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;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Final Score&quot;&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;nc&quot;&gt;BCBCEntry&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&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;BCBCEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcEntryRelatedText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bcbcEntries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// reset&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bcbcEntryRelatedText&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;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;c1&quot;&gt;// word-by-word parsing and building the data structure&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createBetsForEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCConfig&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BCBCEntry&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bets&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;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day2Found&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&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;n&quot;&gt;betTypeFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&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;n&quot;&gt;activeBetComplete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&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;n&quot;&gt;penaltyAmountFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;race&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;nc&quot;&gt;Bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;betBuilder&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;Bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPlayerText&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;c1&quot;&gt;// if it&apos;s Saturday&apos;s date, then Friday bets must be done&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;word&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;s&quot;&gt;&quot;Date: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDay2Date&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;day2Found&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// go to the next word (it should be the race number)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// track the race these bets were for&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;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Race: &quot;&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;race&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos; &apos;&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// go to the next word (it should be the start of the betBuilder table)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// update the entry with the total amount of penalties incurred&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;penaltyAmountFound&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;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPenalty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;penaltyAmountFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// reset&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// only betBuilder types (Exacta, Trifecta etc.) are in all-caps around the&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// betBuilder data&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// set this market because next word/line (or two, if wrapped) will be related to&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// a betBuilder&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;betTypeDetected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BET_TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;matcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// race totals are derived summary data that aggregate the lines above it&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// we don&apos;t need to parse it but it is a marker that all bets for this race have&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// been parsed&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raceTotalsSummaryLineDetected&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;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// first check for any bets that have not yet been fully parsed&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;betTypeFound&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;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completionOfActiveBetDetected&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;betTypeDetected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raceTotalsSummaryLineDetected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// check if a betBuilder description has wrapped to the next line and update it&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// if so&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;completionOfActiveBetDetected&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;c1&quot;&gt;// because of trailing spaces between the &quot;Bets&quot;, &quot;Refunds&quot;, &quot;Winnings&quot;, and&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// &quot;Runners&quot; column values, this &quot;word&quot; contains values intended for&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// multiple columns&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// the setter handles splitting this into its respective components as well as&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// handling line-wraps and inconsistent delimiters&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;betBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compositeBetInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&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;activeBetComplete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&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;c1&quot;&gt;// build and save the bet if it is ready&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;activeBetComplete&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;c1&quot;&gt;// set the date and race number as they can apply to multiple bets&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;betBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;day2Found&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDay2Date&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;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDay1Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;betBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;race&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

                &lt;span class=&quot;n&quot;&gt;bets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;betBuilder&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;span class=&quot;c1&quot;&gt;// start clean for the next betBuilder&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;betBuilder&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;Bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;betTypeFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;activeBetComplete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&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;c1&quot;&gt;// set the marker that a betBuilder is active (ready to be parsed)&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;betTypeDetected&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;betTypeFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;betBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// go to the next word (it should be the composite betBuilder information)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// set a flag that the next word parsed contains the penalty amount for the entry&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;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Penalty Amount:&quot;&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;penaltyAmountFound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bets&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;I was able to validate the parsing logic by picking a player (I chose 7th-placed Allen Harberg as he was the highest placed finisher that incurred a penalty) and calculated their final score just using the individual bets they placed:&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;nd&quot;&gt;@Test&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;parse_With2018Results_FinalScoreCalculatedCorrectly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcResults&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;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bcbc_2018.pdf&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BCBCEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntries&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;BCBCParser&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;Config2018&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// the Final Score is the starting bankroll of $7,500 plus all winnings minus all bets&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// minus all penalties&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finalScore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&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;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&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;s&quot;&gt;&quot;900000129&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Allen Harberg&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMapToDouble&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DoubleStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&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;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;summarizingDouble&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bet&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;bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getWinnings&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;bet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBets&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;getSum&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;bcbcEntry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPenalty&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;mi&quot;&gt;7500&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;findAny&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAsDouble&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finalScore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;43025&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&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;What’s more, I was able to reuse this solution again in 2019, and even created a bar chart race visualization that simulated how the leaderboard changed race-by-race using the data extracted with this solution:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;A live leaderboard visualization of the &lt;a href=&quot;https://twitter.com/BreedersCup?ref_src=twsrc%5Etfw&quot;&gt;@BreedersCup&lt;/a&gt; Betting Challenge 2019 (&lt;a href=&quot;https://twitter.com/hashtag/BCBC?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#BCBC&lt;/a&gt;) feat. &lt;a href=&quot;https://twitter.com/PatCummingsTIF?ref_src=twsrc%5Etfw&quot;&gt;@PatCummingsTIF&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/truxtonstables?ref_src=twsrc%5Etfw&quot;&gt;@truxtonstables&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/stoolpresidente?ref_src=twsrc%5Etfw&quot;&gt;@stoolpresidente&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/DickJerardi?ref_src=twsrc%5Etfw&quot;&gt;@DickJerardi&lt;/a&gt; among others.&lt;br /&gt;&lt;br /&gt;Based on a recent bar chart race demo by &lt;a href=&quot;https://twitter.com/mbostock?ref_src=twsrc%5Etfw&quot;&gt;@mbostock&lt;/a&gt; using &lt;a href=&quot;https://twitter.com/observablehq?ref_src=twsrc%5Etfw&quot;&gt;@observablehq&lt;/a&gt; : &lt;a href=&quot;https://t.co/PVVPjCpNAo&quot;&gt;https://t.co/PVVPjCpNAo&lt;/a&gt; &lt;a href=&quot;https://t.co/OhlVROozLG&quot;&gt;pic.twitter.com/OhlVROozLG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Robin Howlett (@robinhowlett) &lt;a href=&quot;https://twitter.com/robinhowlett/status/1195117366612332544?ref_src=twsrc%5Etfw&quot;&gt;November 14, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h4 id=&quot;3-of-3-advanced-outputting-each-characters-metadata&quot;&gt;(3 of 3) Advanced: outputting each character’s metadata&lt;/h4&gt;

&lt;p&gt;While I was able to certainly use the basic output of the PDF document’s text for parsing, PDFBox enables exposing the metadata of each individual character present within the document, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the X-Y coordinates of the character within the page&lt;/li&gt;
  &lt;li&gt;the font size (even font name)&lt;/li&gt;
  &lt;li&gt;the height and width of the printed character&lt;/li&gt;
  &lt;li&gt;the unicode character value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By leveraging the character metadata, much more fine-grained control of the parsing can be conducted. By using the actual measurements of the positioning of the text within the PDF (for instance, inferring columns in a table and grouping the values), we can avoid regex-driven development of trying to parse the purely textual output returned above, which may be very error prone for less predicatably-structured documents.&lt;/p&gt;

&lt;p&gt;We saw earlier that &lt;a href=&quot;https://pdfbox.apache.org/docs/2.0.13/javadocs/org/apache/pdfbox/text/TextPosition.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextPosition&lt;/code&gt;&lt;/a&gt; is part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeString()&lt;/code&gt; method signature, but we did not use it above. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextPosition&lt;/code&gt; represents “a string and a position on the screen of those characters”.&lt;/p&gt;

&lt;p&gt;The following variables within &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextPosition&lt;/code&gt; can be useful for understanding the positioning, size, spacing, and unicode value:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;xDirAdj&lt;/td&gt;
      &lt;td&gt;The horizontal positioning of the character within a particular page&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;yDirAdj&lt;/td&gt;
      &lt;td&gt;The vertical positioning of the character within a particular page&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fontSize&lt;/td&gt;
      &lt;td&gt;The size of the font for this character&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;height&lt;/td&gt;
      &lt;td&gt;The visible height of the character&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;widthDirAdj&lt;/td&gt;
      &lt;td&gt;The visible width of the character&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;widthOfSpace&lt;/td&gt;
      &lt;td&gt;The measurement of the space character that applies to the character&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;unicode&lt;/td&gt;
      &lt;td&gt;The unicode value of the character&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are others too like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xScale&lt;/code&gt; that may be appropriate for your use cases. View the PDFBox documentation for more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When comparing the various metadata values of each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextPosition&lt;/code&gt; instance with another, certain inferences can be made, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If the difference between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yDirAdj&lt;/code&gt; values of two characters is greater than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; of the characters (or some other logical value), then the characters are on different lines.&lt;/li&gt;
  &lt;li&gt;If the difference between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xDirAdj&lt;/code&gt; values of two characters is greater than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;widthDirAdj&lt;/code&gt; value, then some form of whitespace exists between characters within the string/text (some PDFs store space characters, some do not and must be calculated).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fontSize&lt;/code&gt; and similar can also be used to identify superscript values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An effective way to rapidly identify patterns within the text positions of the characters in the PDF is to use a spreadsheet. To do that, we need to create a CSV where each row is character within the PDF and its relevant &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextPosition&lt;/code&gt; metadata.&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;BCBCParser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDFTextStripper&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;nf&quot;&gt;BCBCParser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&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;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TextPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcbcResults&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;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PDDocument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bcbcResults&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;setSortByPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BufferedWriter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newBufferedWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Paths&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;s&quot;&gt;&quot;text-positions.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UTF_8&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;c1&quot;&gt;// the first row is the header column names&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&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;s&quot;&gt;&quot;xDirAdj&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;yDirAdj&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fontSize&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;xScale&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;s&quot;&gt;&quot;widthOfSpace&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;widthDirAdj&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;unicode&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// use pipe as a delimiter (just as a personal preference)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;|&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lineSeparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// this will call #writeString() below with the line text and positions of each char&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&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;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;InvalidPasswordException&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;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;printStackTrace&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;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&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;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;printStackTrace&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;kc&quot;&gt;null&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;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;writeWordSeparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// do nothing as we don&apos;t need spaces&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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;writeLineSeparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// do nothing as writeString(String, List&amp;lt;TextPosition&amp;gt;) below handles the new lines&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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;writeString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TextPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textPositions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&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;text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&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;textPositions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&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;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                	&lt;span class=&quot;c1&quot;&gt;// call the parent&apos;s writeString(String) to write to the output writer&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;writeString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;|&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getXDirAdj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getYDirAdj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFontSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getXScale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getHeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getWidthOfSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getWidthDirAdj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;textPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUnicode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// write a line/row after each character&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;writeString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lineSeparator&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;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&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;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;printStackTrace&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;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 outputs to a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-positions.csv&lt;/code&gt; the following:&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;xDirAdj|yDirAdj|fontSize|xScale|height|widthOfSpace|widthDirAdj|unicode
18.0|28.950012|1.0|10.65|7.364475|2.6625|8.2857|G
26.2857|28.950012|1.0|10.65|7.364475|2.6625|4.7285995|r
31.0143|28.950012|1.0|10.65|7.364475|2.6625|5.9214|u
36.9357|28.950012|1.0|10.65|7.364475|2.6625|5.921398|b
42.857098|28.950012|1.0|10.65|7.364475|2.6625|5.921398|b
48.778496|28.950012|1.0|10.65|7.364475|2.6625|4.142849|s
162.0|28.950012|1.0|10.65|7.364475|2.6625|7.6893005|C
169.6893|28.950012|1.0|10.65|7.364475|2.6625|5.921402|h
175.6107|28.950012|1.0|10.65|7.364475|2.6625|5.324997|a
180.9357|28.950012|1.0|10.65|7.364475|2.6625|4.728607|r
185.6643|28.950012|1.0|10.65|7.364475|2.6625|2.9606934|l
188.625|28.950012|1.0|10.65|7.364475|2.6625|4.728607|e
193.3536|28.950012|1.0|10.65|7.364475|2.6625|4.142853|s
558.3|27.0|1.0|8.95|6.02335|2.2375|4.4749756|1
562.77496|27.0|1.0|8.95|6.02335|2.2375|4.4749756|0
567.24994|27.0|1.0|8.95|6.02335|2.2375|4.4749756|6
571.7249|27.0|1.0|8.95|6.02335|2.2375|4.4749756|0
576.1999|27.0|1.0|8.95|6.02335|2.2375|4.4749756|3
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When opening the same file in Excel (and using Data &amp;gt; Text to Columns to instruct that the pipe character is a delimiter, plus adding some basic decimal formatting), we can now look at the patterns of the character metadata, to make some inferences:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/excel-columns.png&quot; alt=&quot;Excel Columns&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s look at the table that corresponds to the bets made for a particular race (see page 2 of the 2018 BCBC results PDF, race 3 of the second day):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/bcbc_bet-table.png&quot; alt=&quot;Bet Table&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This entry is interesting because some of the bet details (under the “Runners” column) have been wrapped to the next line. Also, the “Pool” column is left-aligned, but all other columns are right-aligned.&lt;/p&gt;

&lt;p&gt;A little scroll-and-search within Excel finds the character metadata that corresponds:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/bcbc-related.png&quot; alt=&quot;Related Cells&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This may be hard to see but I’ve split the Excel sheet and on top highlighted the “Pool”, “Bets”, and “Refunds” column header characters, and, below, the first row character values that correspond (“WIN”, “$200,00 “, and “$0.00 “ respectively).&lt;/p&gt;

&lt;p&gt;This allows us create rules for detecting when text has been wrapped. For example, see how the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yDirAdj&lt;/code&gt; value of the “0” character (that was wrapped above in the PDF’s “Runners” columns) of the highlighted row is unlike the rows above and below but that it has a high &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xDirAdj&lt;/code&gt; value, indicating it is positioned to the right of the page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/bcbc-related-newline.png&quot; alt=&quot;Related Cells New Line&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Again, the BCBC PDF was simple enough in its structure to not need this level of control, but I do have experience with far more complex PDF layouts.&lt;/p&gt;

&lt;p&gt;Consider the following PDF from &lt;a href=&quot;http://www.equibase.com/&quot;&gt;Equibase&lt;/a&gt; for a horse racing result chart:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/equibase-chart.png&quot; alt=&quot;Equibase Chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this case, there are a variety of layouts within the document - multi-line text, multiple key-value pairs on the same line, multiple tables whose text and even number of rows and columns are fully dynamic and based on the nature of the content, superscript text, fractions, even embedded images.&lt;/p&gt;

&lt;p&gt;To outline just one technique, for grouping related columns of data within the table (which had semi-predicable headers), I built a &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeSet&lt;/code&gt;&lt;/a&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xDirAdj&lt;/code&gt; and column header indices, so that for any character found within the table, I could find the nearest starting header column using the &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html#floor-E-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;floor()&lt;/code&gt;&lt;/a&gt; method:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/handycapper-floor.png&quot; alt=&quot;Using TreeSet.floor to indexing column ranges&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was able to fully parse this document (and over a million others like it) into a highly-specialized domain-specific data structure that powered a variety of data-centric tools, APIs and SDKs:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/handycapper-json.png&quot; alt=&quot;Handycapper JSON&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, &lt;a href=&quot;https://www.thoroughbreddailynews.com/getting-from-cease-and-desist-to-come-work-with-us/&quot;&gt;I ran into some &lt;em&gt;issues&lt;/em&gt; when distributing this open-source software&lt;/a&gt;, but the general procedure I used was the same as what I have been describing in this post.&lt;/p&gt;

&lt;p&gt;Between eyeballing the PDF, noting where obvious patterns exist, and potentionally building a data model using the character position metadata, you can start to construct a collection of data structures that capture the particular domain values you wish to extract from the PDF.&lt;/p&gt;
</description>
        <pubDate>Fri, 29 Nov 2019 00:00:00 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2019/11/29/parsing-structured-data-complex-pdf-layouts/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2019/11/29/parsing-structured-data-complex-pdf-layouts/</guid>
        
        <category>code</category>
        
        <category>java</category>
        
        <category>horseracing</category>
        
        
      </item>
    
      <item>
        <title>Solved: When the Maven Deploy Plugin silently fails to deploy</title>
        <description>&lt;p&gt;At SnapLogic, we recently noticed that a particular build job that was responsible for deploying build artifacts to a Nexus repository via Maven had suddenly stopped, well, deploying. What was odd was that no error of any kind was being communicated, even in DEBUG mode.&lt;/p&gt;

&lt;p&gt;Examining the history of the repository, what had changed was the addition of a new module (“slbugs”) to the existing multi-module Maven build. This module’s reposibility was to run some code health checks using Google’s &lt;a href=&quot;https://github.com/google/error-prone&quot;&gt;error-prone&lt;/a&gt; static analysis tool to catch some programming mistakes that our developers occassionally made that had a negative effect at runtime.&lt;/p&gt;

&lt;p&gt;What was different about this module versus the others was that it did not use the root POM as a parent (as it was sufficiently different from the other more product-focused modules). The other modules were also configured to use the &lt;a href=&quot;https://maven.apache.org/plugins/maven-deploy-plugin/deploy-mojo.html#deployAtEnd&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deployAtEnd&lt;/code&gt;&lt;/a&gt; parameter of the parent’s &lt;a href=&quot;https://maven.apache.org/plugins/maven-deploy-plugin&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt;&lt;/a&gt; plugin configuration.&lt;/p&gt;

&lt;p&gt;The problem was that each of the product modules would log that they would be deployed at the end of the build, but after the last module ran its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy&lt;/code&gt; phase, nothing would happen - no uploading of artifacts would be attempted, no warnings or debug messages logged to explain the inaction, and the build would just end with a SUCCESS status.&lt;/p&gt;

&lt;p&gt;The solution turned out to be related to the wonderful world of Maven classloaders.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;When the problem was reported and some obvious things had been tried (changing plugin versions etc.), I started to look into it. The first thing I did was create a standalone test that mirrored the basic structure of the multi-module project and chose what I deemed to be the pertinent configurations from the production POMs.&lt;/p&gt;

&lt;p&gt;It looked like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/2019/multi-module-layout.png&quot; alt=&quot;multi-module project layout&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Quite standard as you can see. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; defines the modules (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-one&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-two&lt;/code&gt;), where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-one&lt;/code&gt; does not use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; as its parent but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-two&lt;/code&gt; does. All dependency and plugin versions are identical, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; (and therefore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-two&lt;/code&gt; by inheritence) uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deployAtEnd&lt;/code&gt; configuration for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt;. For running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy&lt;/code&gt; phase, I setup a local empty temporary repository via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distributionManagement&lt;/code&gt; definition.&lt;/p&gt;

&lt;p&gt;So, I ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn clean deploy&lt;/code&gt; and expected to see the problem reproduced:&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;&amp;gt; mvn clean deploy
[MVNVM] Using maven: 3.5.2
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] module-one
[INFO] pom-root
[INFO] module-two
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building module-one 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ module-one ---
[INFO] Deleting /Users/rhowlett/dev/tmp/pom-root/module-one/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module-one ---
[INFO] Using &apos;UTF-8&apos; encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/rhowlett/dev/tmp/pom-root/module-one/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module-one ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/rhowlett/dev/tmp/pom-root/module-one/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ module-one ---
[INFO] Using &apos;UTF-8&apos; encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/rhowlett/dev/tmp/pom-root/module-one/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ module-one ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/rhowlett/dev/tmp/pom-root/module-one/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ module-one ---
[INFO] Surefire report directory: /Users/rhowlett/dev/tmp/pom-root/module-one/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.snaplogic.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.048 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ module-one ---
[INFO] Building jar: /Users/rhowlett/dev/tmp/pom-root/module-one/target/module-one-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-install-plugin:2.4:install (default-install) @ module-one ---
[INFO] Installing /Users/rhowlett/dev/tmp/pom-root/module-one/target/module-one-1.0-SNAPSHOT.jar to /Users/rhowlett/.m2/repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-SNAPSHOT.jar
[INFO] Installing /Users/rhowlett/dev/tmp/pom-root/module-one/pom.xml to /Users/rhowlett/.m2/repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-SNAPSHOT.pom
[INFO] 
[INFO] --- maven-deploy-plugin:2.8.2:deploy (default-deploy) @ module-one ---
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-20190515.171609-1.jar
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-20190515.171609-1.jar (2.4 kB at 237 kB/s)
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-20190515.171609-1.pom
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/module-one-1.0-20190515.171609-1.pom (1.4 kB at 702 kB/s)
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/1.0-SNAPSHOT/maven-metadata.xml (767 B at 192 kB/s)
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-one/maven-metadata.xml (281 B at 94 kB/s)
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building pom-root 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ pom-root ---
[INFO] 
[INFO] --- maven-install-plugin:2.4:install (default-install) @ pom-root ---
[INFO] Installing /Users/rhowlett/dev/tmp/pom-root/pom.xml to /Users/rhowlett/.m2/repository/com/snaplogic/pom-root/1.0-SNAPSHOT/pom-root-1.0-SNAPSHOT.pom
[INFO] 
[INFO] --- maven-deploy-plugin:2.8.2:deploy (default-deploy) @ pom-root ---
[INFO] Deploying com.snaplogic:pom-root:1.0-SNAPSHOT at end
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building module-two 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ module-two ---
[INFO] Deleting /Users/rhowlett/dev/tmp/pom-root/module-two/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module-two ---
[INFO] Using &apos;UTF-8&apos; encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/rhowlett/dev/tmp/pom-root/module-two/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module-two ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/rhowlett/dev/tmp/pom-root/module-two/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ module-two ---
[INFO] Using &apos;UTF-8&apos; encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/rhowlett/dev/tmp/pom-root/module-two/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ module-two ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/rhowlett/dev/tmp/pom-root/module-two/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ module-two ---
[INFO] Surefire report directory: /Users/rhowlett/dev/tmp/pom-root/module-two/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.snaplogic.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.043 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ module-two ---
[INFO] Building jar: /Users/rhowlett/dev/tmp/pom-root/module-two/target/module-two-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-install-plugin:2.4:install (default-install) @ module-two ---
[INFO] Installing /Users/rhowlett/dev/tmp/pom-root/module-two/target/module-two-1.0-SNAPSHOT.jar to /Users/rhowlett/.m2/repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-SNAPSHOT.jar
[INFO] Installing /Users/rhowlett/dev/tmp/pom-root/module-two/pom.xml to /Users/rhowlett/.m2/repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-SNAPSHOT.pom
[INFO] 
[INFO] --- maven-deploy-plugin:2.8.2:deploy (default-deploy) @ module-two ---
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/1.0-SNAPSHOT/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/1.0-SNAPSHOT/pom-root-1.0-20190515.171609-1.pom
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/1.0-SNAPSHOT/pom-root-1.0-20190515.171609-1.pom (1.8 kB at 602 kB/s)
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/1.0-SNAPSHOT/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/1.0-SNAPSHOT/maven-metadata.xml (594 B at 119 kB/s)
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/pom-root/maven-metadata.xml (279 B at 70 kB/s)
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-20190515.171610-1.jar
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-20190515.171610-1.jar (2.1 kB at 710 kB/s)
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-20190515.171610-1.pom
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/module-two-1.0-20190515.171610-1.pom (533 B at 178 kB/s)
Downloading from dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/maven-metadata.xml
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/1.0-SNAPSHOT/maven-metadata.xml (767 B at 256 kB/s)
Uploading to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/maven-metadata.xml
Uploaded to dev: file:///Users/rhowlett/.m2/temp-repository/com/snaplogic/module-two/maven-metadata.xml (281 B at 94 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] module-one ......................................... SUCCESS [  2.970 s]
[INFO] pom-root ........................................... SUCCESS [  0.131 s]
[INFO] module-two ......................................... SUCCESS [  0.652 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.905 s
[INFO] Finished at: 2019-05-15T11:16:10-06:00
[INFO] Final Memory: 19M/309M
[INFO] ------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Odd. The deployment happened perfectly. Then why wasn’t it happening with the production build?&lt;/p&gt;

&lt;p&gt;I decided to debug. I added &lt;a href=&quot;https://github.com/apache/maven-deploy-plugin/releases/tag/maven-deploy-plugin-2.8.2&quot;&gt;the source for version 2.8.2&lt;/a&gt; of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt; to my IDE and ran the same command except this time with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvnDebug&lt;/code&gt; and &lt;a href=&quot;https://stackoverflow.com/a/14853683/277133&quot;&gt;a Remote Debug configuration setup&lt;/a&gt;. I put a breakpoint &lt;a href=&quot;https://github.com/apache/maven-deploy-plugin/blob/maven-deploy-plugin-2.8.2/src/main/java/org/apache/maven/plugin/deploy/DeployMojo.java#L152&quot;&gt;in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute()&lt;/code&gt; method of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DeployMojo&lt;/code&gt; class&lt;/a&gt; and hoped to see what was causing the deployment not to be executed:&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;cm&quot;&gt;/**
 * When building with multiple threads, reaching the last project doesn&apos;t have to mean that all projects are ready
 * to be deployed
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AtomicInteger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readyProjectsCounter&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;AtomicInteger&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;execute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MojoExecutionException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MojoFailureException&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;n&quot;&gt;addedDeployRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&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;skip&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;getLog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Skipping artifact deployment&quot;&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;failIfOffline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;DeployRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentExecutionDeployRequest&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;nf&quot;&gt;DeployRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setUpdateReleaseInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isUpdateReleaseInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setRetryFailedDeploymentCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getRetryFailedDeploymentCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAltReleaseDeploymentRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;altReleaseDeploymentRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAltSnapshotDeploymentRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;altSnapshotDeploymentRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAltDeploymentRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;altDeploymentRepository&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;deployAtEnd&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;deployProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentExecutionDeployRequest&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;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;deployRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentExecutionDeployRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;addedDeployRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&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;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projectsReady&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readyProjectsCounter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;incrementAndGet&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;reactorProjects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&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;projectsReady&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;synchronized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployRequests&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;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&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;deployProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;remove&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;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&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;addedDeployRequest&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;getLog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Deploying &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getGroupId&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;s&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getArtifactId&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;s&quot;&gt;&quot;:&quot;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getVersion&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;s&quot;&gt;&quot; at end&quot;&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;It looked clear that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projectsReady&lt;/code&gt; boolean would be key. Its value was determined by the equality of a static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtomicInteger&lt;/code&gt; called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readyProjectsCounter&lt;/code&gt; and the size of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reactorProjects&lt;/code&gt; collection. The only way for the build to log that it was going to deploy at the end and then not deploy the final module, would be if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readyProjectsCounter&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reactorProjects&lt;/code&gt; size had some kind of off-by-X error. Indeed, the debugger showed that, for the production build, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readyProjectsCounter&lt;/code&gt; was one less than the collection size. But what would cause that?&lt;/p&gt;

&lt;p&gt;I dived into the debug logs of the production build to try to see if I could identify anything of note. After trying quite a few different things, something caught my eye.&lt;/p&gt;

&lt;p&gt;When “slbugs” was running its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy&lt;/code&gt; phase with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt;, it logged:&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;[DEBUG] Created new class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2
[DEBUG] Importing foreign packages into class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2
[DEBUG]   Imported:  &amp;lt; maven.api
[DEBUG] Populating class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2
[DEBUG]   Included: org.apache.maven.plugins:maven-deploy-plugin:jar:2.8.2
[DEBUG]   Included: backport-util-concurrent:backport-util-concurrent:jar:3.1
[DEBUG]   Included: org.codehaus.plexus:plexus-interpolation:jar:1.11
[DEBUG]   Included: junit:junit:jar:3.8.1
[DEBUG]   Included: org.codehaus.plexus:plexus-utils:jar:3.0.15
[DEBUG] Configuring mojo org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy from plugin realm ClassRealm[plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2, parent: sun.misc.Launcher$AppClassLoader@3d4eac69]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the parent POM was running, this was logged:&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;[DEBUG] Created new class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2-932771130
[DEBUG] Importing foreign packages into class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2-932771130
[DEBUG]   Imported:  &amp;lt; maven.api
[DEBUG] Populating class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2-932771130
[DEBUG]   Included: org.apache.maven.plugins:maven-deploy-plugin:jar:2.8.2
[DEBUG]   Included: backport-util-concurrent:backport-util-concurrent:jar:3.1
[DEBUG]   Included: org.codehaus.plexus:plexus-interpolation:jar:1.11
[DEBUG]   Included: junit:junit:jar:3.8.1
[DEBUG]   Included: org.codehaus.plexus:plexus-utils:jar:3.0.15
[DEBUG] Configuring mojo org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy from plugin realm ClassRealm[plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2-932771130, parent: sun.misc.Launcher$AppClassLoader@3d4eac69]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-932771130&lt;/code&gt; suffix was strange. When I turned on debug logging for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn -X clean deploy&lt;/code&gt;, I saw no such recreation of a “class realm”. In fact, every plugin was “initialized” once.&lt;/p&gt;

&lt;p&gt;But in the production build, many plugins (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-install-plugin&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-resources-plugin&lt;/code&gt; etc.), all seem to have been “recreated” when the build moved on to the root pom (after the initial “slbugs” module had been deployed). What was triggering that? I started to consume some articles from the web, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://maven.apache.org/guides/mini/guide-maven-classloading.html&quot;&gt;The official “Guide to Maven Classloading”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.semsur-it.com/2011/11/java-class-loader-and-maven-plugin.html&quot;&gt;Semsur IT’s “Java class loader and Maven plugin”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://takari.io/book/91-maven-classloading.html&quot;&gt;takari’s “Maven classloading”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.chalda.cz/2018/02/17/Maven-plugin-and-fight-with-classloading.html#_class_loading_troubles&quot;&gt;chalda’s “Maven plugin and fight with classloading”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll admit that I was a bit puzzled. Neither build had any custom extensions defined, but I started focusing on whether there was anything non-standard defined in the production build POM, looking at things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;properties&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;build&amp;gt;&lt;/code&gt;, or anything around &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;plugins&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sure enough, I noticed the following:&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;&amp;lt;pluginRepositories&amp;gt;
    &amp;lt;pluginRepository&amp;gt;
        &amp;lt;id&amp;gt;apache.snapshots&amp;lt;/id&amp;gt;
        &amp;lt;name&amp;gt;Apache Snapshots&amp;lt;/name&amp;gt;
        &amp;lt;url&amp;gt;http://repository.apache.org/content/groups/snapshots-group/&amp;lt;/url&amp;gt;
        &amp;lt;releases&amp;gt;
            &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;
        &amp;lt;/releases&amp;gt;
        &amp;lt;snapshots&amp;gt;
            &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;
        &amp;lt;/snapshots&amp;gt;
    &amp;lt;/pluginRepository&amp;gt;
&amp;lt;/pluginRepositories&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This was added many years ago to the build, probably to avail of some SNAPSHOT-versioned plugin. So I decided to replicate this in the standalone test’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt;’s pom.xml by adding the following:&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;pluginRepositories&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;pluginRepository&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;temp.repo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Temp Repo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;file://${user.home}/.m2/temp-repository&lt;span class=&quot;nt&quot;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/pluginRepository&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/pluginRepositories&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I re-ran the build and I finally I had reproduced the problem (the non-deployment of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-two&lt;/code&gt;):&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;...
[INFO] --- maven-deploy-plugin:2.8.2:deploy (default-deploy) @ module-two ---
[INFO] Deploying com.snaplogic:module-two:1.0-SNAPSHOT at end
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] module-one ......................................... SUCCESS [  3.477 s]
[INFO] pom-root ........................................... SUCCESS [  0.228 s]
[INFO] module-two ......................................... SUCCESS [  1.446 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.308 s
[INFO] Finished at: 2019-05-15T12:10:55-06:00
[INFO] Final Memory: 24M/437M
[INFO] ------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Similarly, the debug log shows the two initializations of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt;:&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;[DEBUG] Created new class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2
...
[DEBUG] Created new class realm plugin&amp;gt;org.apache.maven.plugins:maven-deploy-plugin:2.8.2-1119294797
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, the addition of the new module (“slbugs”) in our production build (that didn’t define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;pluginRespositories&amp;gt;&lt;/code&gt; and didn’t use the root POM as its parent), created a new context that resulted in new classloaders being used when the root POM (and its child modules) executed. This caused the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maven-deploy-plugin&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readyProjectsCounter&lt;/code&gt; static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtomicInteger&lt;/code&gt; to start once again from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; (instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; as it should have been incremented when the first module was deployed), resulting in the off-by-one error when compared to the count of the reactor module (which wasn’t static), meaning the parent and child modules never meeting the condition that would trigger the deployment.&lt;/p&gt;

&lt;p&gt;As for resolving this, I found that if both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom-root&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module-one&lt;/code&gt; “matched” when it came to defining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;pluginRepositories&amp;gt;&lt;/code&gt;, then the deployment would work; meaning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;if neither defined it, the deployment worked&lt;/li&gt;
  &lt;li&gt;if both defined it, the deployment worked (however the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluginRepository&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt;s must be the same)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standalone project I created to demonstrate this is available on GitHub here: https://github.com/robinhowlett/maven-deploy-plugin-gotcha&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/robinhowlett/maven-deploy-plugin-gotcha/tree/master&quot;&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch&lt;/a&gt; represents the non-working deployment, and &lt;a href=&quot;https://github.com/robinhowlett/maven-deploy-plugin-gotcha/tree/fix&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix&lt;/code&gt; branch&lt;/a&gt; represents the working example (with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;pluginRepositories&amp;gt;&lt;/code&gt; &lt;a href=&quot;https://github.com/robinhowlett/maven-deploy-plugin-gotcha/compare/fix?expand=1#diff-600376dffeb79835ede4a0b285078036&quot;&gt;removed from the root POM&lt;/a&gt;).&lt;/p&gt;
</description>
        <pubDate>Wed, 15 May 2019 00:00:00 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2019/05/15/solved-when-the-maven-deploy-plugin-silently-fails-to-deploy/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2019/05/15/solved-when-the-maven-deploy-plugin-silently-fails-to-deploy/</guid>
        
        <category>code</category>
        
        <category>maven</category>
        
        <category>snaplogic</category>
        
        
      </item>
    
      <item>
        <title>Visualizing inefficient multi-ticket horizontal wagering tickets</title>
        <description>&lt;p&gt;Between PPs, result charts, and wagering ticket summaries, the way that horse racing information has been presented to regular fans hasn’t changed very much over the years. In this post, I use data visualizations to try to increase comprehension of advanced wagering advice.&lt;/p&gt;

&lt;p&gt;It was May of 2017 when, deep into the development of &lt;a href=&quot;https://www.thoroughbreddailynews.com/getting-from-cease-and-desist-to-come-work-with-us/&quot;&gt;Handycapper&lt;/a&gt;, I took a break and browsed Twitter.&lt;/p&gt;

&lt;p&gt;I saw a tweet from &lt;a href=&quot;https://twitter.com/atTheTrack7&quot;&gt;Darin Zoccali (@atTheTrack7)&lt;/a&gt; referencing a column he published at DRF.com titled, &lt;a href=&quot;https://wcms.weboapps.com/news/zoccali-trust-must-or-your-handicapping-game-bust&quot;&gt;“Trust is a must or your handicapping game is a bust”&lt;/a&gt; (archive link).&lt;/p&gt;

&lt;p&gt;In this column, Darin explained how a well-known wagering professional, &lt;a href=&quot;https://twitter.com/InsideThePylons&quot;&gt;@InsideThePylons&lt;/a&gt; (ITP), had called out inefficiencies and mistakes in the construction of Pick 4 tickets Darin had posted in late 2016. Darin reported that this advice had helped him wager smarter, increase churn, and win far more when his opinion was correct.&lt;/p&gt;

&lt;p&gt;Intrigued, I wanted to find this conversation, and, with &lt;a href=&quot;https://twitter.com/search?f=tweets&amp;amp;q=from%3AatTheTrack7%20to%3AInsideThePylons%20since%3A2016-12-09%20until%3A2016-12-11&amp;amp;src=typd&quot;&gt;a little Twitter Search-fu&lt;/a&gt;, I was able to locate the Pick 4 in question.&lt;/p&gt;

&lt;p&gt;As I tried to understand the finer points of ticket construction &lt;a href=&quot;https://twitter.com/InsideThePylons&quot;&gt;@InsideThePylons&lt;/a&gt; was making, I remember making a mental note that this kind of feedback would be a great candidate to potentially benefit from data visualizations to aid understanding, for the average racing fan, of the interaction between Darin and ITP.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/r9n7-highlight.png&quot; alt=&quot;dataviz&quot; /&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;&lt;i&gt;TL;DR? &lt;a href=&quot;#daviz&quot;&gt;Skip directly to the interactive visualization&lt;/a&gt; or &lt;a href=&quot;https://www.youtube.com/watch?v=tYzROraMYDE&quot; target=&quot;_blank&quot;&gt;view a demonstration video&lt;/a&gt;.&lt;i&gt;&lt;/i&gt;&lt;/i&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-pick-4-tickets&quot;&gt;The Pick 4 Tickets&lt;/h3&gt;

&lt;p&gt;The exchange started with Darin posting his $0.50 Pick 4 tickets for &lt;a href=&quot;https://twitter.com/themeadowlands&quot;&gt;@TheMeadowlands&lt;/a&gt; Pick 4 pool for races 7 through 10:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-theme=&quot;light&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Put in my pick 4 tickets for &lt;a href=&quot;https://twitter.com/TheMeadowlands?ref_src=twsrc%5Etfw&quot;&gt;@TheMeadowlands&lt;/a&gt;. Pretty high on Alexie Mattosie tonight. Using Fool Me Once in a back-up ticket. &lt;a href=&quot;https://t.co/B6P4AfxVaM&quot;&gt;pic.twitter.com/B6P4AfxVaM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Darin Zoccali (@atTheTrack7) &lt;a href=&quot;https://twitter.com/atTheTrack7/status/807708303270838272?ref_src=twsrc%5Etfw&quot;&gt;December 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;In short, Darin wagered $99 on four Pick 4 tickets.&lt;/p&gt;

&lt;p&gt;He called out a strong preference for &lt;strong&gt;#8 Alexie Mattosie&lt;/strong&gt; in Race 9, but used &lt;strong&gt;#7 Fool Me Once&lt;/strong&gt; in the same race in “back-up” tickets.&lt;/p&gt;

&lt;p&gt;Judgement from ITP was swift:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I mean how could you possibly ever make a bet again in your life if it came 1/2 to 4 to 7 to 6?&lt;/p&gt;&amp;mdash; Inside The Pylons (@InsideThePylons) &lt;a href=&quot;https://twitter.com/InsideThePylons/status/807714842073124864?ref_src=twsrc%5Etfw&quot;&gt;December 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Eyeballing the individual tickets Darin had posted, I could see ITP was correct.&lt;/p&gt;

&lt;p&gt;Indeed, using &lt;a href=&quot;https://en.wikipedia.org/wiki/Sankey_diagram&quot;&gt;a Sankey diagram&lt;/a&gt; (you may have seen one previously from Hello Race Fans’s &lt;a href=&quot;http://helloracefans.com/races/kentucky-derby/paths-to-the-kentucky-derby-since-1990/&quot;&gt;“Paths to the Kentucky Derby”&lt;/a&gt;) to visualize the four individual Pick 4 tickets emphasized the lack of coverage:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/4-sankeys.png&quot; alt=&quot;four sankey diagrams&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But I had a more fundamental question - &lt;i&gt;would an inefficient multi-ticket structure be immediately identifiable with a more advanced, interactive perspective?&lt;/i&gt;&lt;/p&gt;

&lt;h3 id=&quot;implicit-choices&quot;&gt;Implicit Choices&lt;/h3&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;You played $36 keying Fool out of the $54 keying Alexie and removed the 2 bombs to save the $18 + keyed the bombs on press?&lt;/p&gt;&amp;mdash; Inside The Pylons (@InsideThePylons) &lt;a href=&quot;https://twitter.com/InsideThePylons/status/807715949969182720?ref_src=twsrc%5Etfw&quot;&gt;December 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;It’s worth explaining @InsideThePylons’s tweet:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“You played $36 keying Fool out of the $54 keying Alexie”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The $36 refers to the $12 ticket (3,4,5,8 / 1,2,4,5,8,9 / 7 / 6) plus the $24 ticket (3,4,5,8 / 1,2,4,5,8,9 / 7 / 1,3).&lt;/p&gt;

&lt;p&gt;The $54 ticket (1,2,3,4,5,8 / 1,2,4,5,8,9 / 8 / 1,3,6) differs only to the above tickets by using #1 and #2 in the first leg (Race 7) and replacing #7 Fool Me Once with #8 Alexie Mattosie in the third leg (Race 9).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“removed the 2 bombs to save the $18”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The “2 bombs” refer to #1 (ML 12/1, race odds 23/1) and #2 (ML 12/1, race odds 30/1) in the first leg (Race 7) that were not present in the tickets keying #7 Fool Me Once in the third leg (Race 9).&lt;/p&gt;

&lt;p&gt;These odds are not identified in the tickets posted above, but ITP was aware of them.&lt;/p&gt;

&lt;p&gt;By not using these longshots, there was a difference of $18 between the $36 ($24 + $12) tickets and the $54 ticket.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“keyed the bombs on press”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, Darin also added a $9 ticket (1,2,4 / 8,9 / 8 / 1,3,6), which ITP is referring to as the “press”:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;How can you like 124 best in 1st leg but eliminate 12 on the ticket with Fool?&lt;/p&gt;&amp;mdash; Inside The Pylons (@InsideThePylons) &lt;a href=&quot;https://twitter.com/InsideThePylons/status/807716187559772160?ref_src=twsrc%5Etfw&quot;&gt;December 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;What this $9 ticket implies is that, on top of the $54 ticket that singles #8 Alexie Mattosie in the third leg of the Pick 4 (Race 9), there is further investment in both this opinion, and the opinion that #1, #2, and #4 (race odds, 2.20) are Darin’s “most liked” choices in the first leg (Race 7), and similarly #8 (1.10) and #9 (16.90) in the second leg (Race 8).&lt;/p&gt;

&lt;p&gt;The “bombs” (#1 and #2 referred to earlier) are present in this ticket, yet absent from the $36 investment in the two tickets that single #7 Fool Me Once in the third leg (Race 9).&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Why would you go out of your way to remove &amp;quot;joker&amp;quot; and save $18 but play another ticket pressing joker that you removed?&lt;/p&gt;&amp;mdash; Inside The Pylons (@InsideThePylons) &lt;a href=&quot;https://twitter.com/InsideThePylons/status/807713879325192192?ref_src=twsrc%5Etfw&quot;&gt;December 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Looking back, I think Darin himself would acknowledge that the above tickets were not constructed optimally, and his subsequent DRF article details how he changed his approach going forward.&lt;/p&gt;

&lt;h3 id=&quot;visualizing-inefficient-tickets&quot;&gt;Visualizing Inefficient Tickets&lt;/h3&gt;

&lt;p&gt;I had already used Sankey diagrams above to display the four Pick 4 tickets, but the traditional implementation of this chart is to highlight single links between coupled nodes (e.g. between #8 in Race 9 and #1 in Race 10) rather than the end-to-end nature of a Pick 4 ticket (where a selection in every leg must win for the ticket to payoff, excluding consolations).&lt;/p&gt;

&lt;p&gt;I wanted something that would aggregate all four Pick 4 tickets into a single parallel set visualization, but still respect the combinatorial mathematics in-play with four separate tickets. I also wanted to quickly answer questions like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How many ticket combinations use &lt;b&gt;#8 Alexie Mattosie&lt;/b&gt; versus &lt;b&gt;#7 Fool Me Once&lt;/b&gt; in the third leg (Race 9)?&lt;/li&gt;
  &lt;li&gt;How many ticket combinations use the #9 in the second leg (Race 8) &lt;b&gt;and&lt;/b&gt; the #8 in the third leg (Race 9)?&lt;/li&gt;
  &lt;li&gt;Who is used in more tickets: the #3 in the first leg (Race 7) &lt;b&gt;or&lt;/b&gt; the #9 in the second leg (Race 8)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, I discovered &lt;a href=&quot;https://github.com/QinMing/d3-sankey-with-highlighting&quot;&gt;a variation of Sankey with end-to-end highlighting&lt;/a&gt;, developed by &lt;a href=&quot;http://www.linkedin.com/in/qinming&quot;&gt;Ming Qin&lt;/a&gt;, which visualized the famous &lt;a href=&quot;https://ww2.amstat.org/publications/jse/v3n3/datasets.dawson.html&quot;&gt;“Titanic Survivors”&lt;/a&gt; data set, which is commonly used as an exercise in statistical thinking.&lt;/p&gt;

&lt;p&gt;This implementation used a flow-based API that enabled end-to-end highlighting of that flow, plus additional features like rich tooltips and draggable nodes, that I could further leverage.&lt;/p&gt;

&lt;p&gt;Using &lt;a href=&quot;https://docs.google.com/spreadsheets/d/19gZmZKuiSSPVoiza5o-z4H6Ry88Rn3bxnoJwJPYmcR4/edit?usp=sharing&quot;&gt;a Google Spreadsheet&lt;/a&gt;, I manually described the 198 combinations that Darin’s $99 50c Pick 4 tickets covered (though automating this would be straightforward) and then used a formula to construct the flow node objects used by the library:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/creating-sankey-flows.png&quot; alt=&quot;creating sankey flows&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then it was just a matter of copy/pasting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodes&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flows&lt;/code&gt; into &lt;a href=&quot;https://github.com/robinhowlett/visualizing-horizontal-wagers-d3-sankey/blob/gh-pages/asset/wager-data.json&quot;&gt;the appropriate asset file&lt;/a&gt; and the visualization was ready.&lt;/p&gt;

&lt;p&gt;&lt;b id=&quot;daviz&quot;&gt;So here is the actual interactive visualization!&lt;/b&gt;&lt;/p&gt;

&lt;script src=&quot;/assets/js/d3.v3.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/js/showdown.min.js&quot;&gt;&lt;/script&gt;

&lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/css/highlightjs.css&quot; /&gt;

&lt;script src=&quot;/assets/js/highlight.pack.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/js/sankey.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/js/sankey-driver.js&quot;&gt;&lt;/script&gt;

&lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/css/global.css&quot; /&gt;

&lt;p&gt;
&lt;div id=&quot;canvas&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;legend&quot;&gt;
&lt;div class=&quot;legend-item&quot; style=&quot;width:16.4%; text-align:start;&quot;&gt;Race 7&lt;/div&gt;
&lt;div class=&quot;legend-item&quot; style=&quot;width:32.4%; text-align:center;&quot;&gt;Race 8&lt;/div&gt;
&lt;div class=&quot;legend-item&quot; style=&quot;width:32.4%; text-align:center;&quot;&gt;Race 9&lt;/div&gt;
&lt;div class=&quot;legend-item&quot; style=&quot;width:16.4%; text-align:end;&quot;&gt;Race 10&lt;/div&gt;
&lt;/div&gt;
&lt;/p&gt;

&lt;script src=&quot;/assets/js/sankey-embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;a href=&quot;http://www.robinhowlett.com/visualizing-horizontal-wagers-d3-sankey/&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;Click here for a larger standalone version of the visualization&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/robinhowlett/visualizing-horizontal-wagers-d3-sankey&quot;&gt;standalone visualization&lt;/a&gt; is open-source and available on GitHub.&lt;/p&gt;

&lt;h3 id=&quot;explanation-of-the-visualization&quot;&gt;Explanation of the Visualization&lt;/h3&gt;

&lt;p&gt;The above diagram has a few special features that are worth understanding:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using your cursor, hover over a rectangular node (representing a horse in a race) to see all the combinations that flow through it across all tickets. In the following example, we can see that #9 in the second leg has 39 combinations including it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/node-hover.png&quot; alt=&quot;node-hover&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;By hovering over #7 Fool Me Once, you can clearly see that no combinations that use him in the third leg (Race 9), also use #1 or #2 in the first leg (Race 7) - exactly to ITP’s point!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Hover over the dark-blue links between the nodes - these represent a connection between a selection in each of two legs, as well as visually showing the paths (and their frequency) of any incoming or outgoing combinations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/link-hover.png&quot; alt=&quot;link-hover&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In the above example, you can see that 12 of the 198 combinations use #1 in the second leg (Race 8) and #7 Fool Me Once in the third leg (Race 9). This can be also useful to find live tickets quickly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;By double-clicking on a highlighted link, you can get a rich tooltip that outlines all of the actual combinations (e.g. viable paths in a ticket) involving that link:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/link-double-click.png&quot; alt=&quot;link-double-click&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I can envision interesting information being added here; for example, expected payoffs or even advice that the combination is too “chalky”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Each rectangular node can be moved by dragging them, but I haven’t really found a compelling reason to do that for this particular visualization.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;So, does this visualization satisfy the goal of aiding understanding of the structuring issues behind the multiple Pick 4 tickets?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;There are several improvements I can think of, primarily about more clearly delineating each ticket’s combinations and perhaps being able to filter certain tickets and/or combinations.&lt;/p&gt;

&lt;p&gt;I do think that ADW’s could start thinking about presenting their wagering tickets in a more dynamic and interactive way, or at least allow them to be shared that way.&lt;/p&gt;

&lt;p&gt;There may even be a product opportunity here - a “wagering assistant” that reviews placed wagers and highlights inefficient ticket structures.&lt;/p&gt;

&lt;p&gt;However, I go back to what I said when I charted &lt;a href=&quot;http://www.horseplayersassociation.org/&quot;&gt;HANA&lt;/a&gt;’s &lt;a href=&quot;http://www.horseplayersassociation.org/2017Sortable.html&quot;&gt;track ratings&lt;/a&gt; against &lt;a href=&quot;https://twitter.com/robinhowlett/status/1026214986727608321&quot;&gt;the 2017 racing calendar in a spreadsheet&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Is there any major insight gained by this data visualization? Probably not. But that&amp;#39;s not the point. Today, there are a myriad of distribution options to extend both the reach &amp;amp; the form of content. Much of that can come from regular fans. That engagement should be encouraged.&lt;/p&gt;&amp;mdash; Robin Howlett (@robinhowlett) &lt;a href=&quot;https://twitter.com/robinhowlett/status/1026917526838501376?ref_src=twsrc%5Etfw&quot;&gt;August 7, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

</description>
        <pubDate>Sun, 12 Aug 2018 19:44:27 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2018/08/12/visualizing-inefficient-multi-ticket-horizontal-wagering-tickets/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2018/08/12/visualizing-inefficient-multi-ticket-horizontal-wagering-tickets/</guid>
        
        <category>dataviz</category>
        
        <category>horseracing</category>
        
        
      </item>
    
      <item>
        <title>Building a Google Chrome Extension (Keyboard Shortcuts, Copying to the Clipboard, and Notifications)</title>
        <description>&lt;p&gt;I recently had the quite enjoyable and productive experience of writing &lt;a href=&quot;https://chrome.google.com/webstore/detail/snaplogic-pipeline-linker/cmngkccmjnhnjhjcnmajoiacacnjefgb&quot;&gt;Pipeline Linker&lt;/a&gt;, my first &lt;a href=&quot;https://www.google.com/chrome/webstore/extensions.html&quot;&gt;Google Chrome Extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As part of my work with &lt;a href=&quot;http://www.snaplogic.com/&quot;&gt;SnapLogic&lt;/a&gt;, an enterprise integration platform-as-a-service (iPaaS) provider, I often have to navigate to Pipelines (hosted graphical representations of integrations) across multiple environments (both internal and customer-facing), client accounts, and project folders.&lt;/p&gt;

&lt;p&gt;In turn, as the manager of my team, I regularly direct team members to Pipelines that require attention through email, Hangouts, JIRA, Zendesk, and Slack.&lt;/p&gt;

&lt;p&gt;For whatever reason, our product had not provided an easy way of linking directly to these pipelines (you had to switch to a different tab and perform a search, before right-clicking on a table entry and copying the link).&lt;/p&gt;

&lt;p&gt;Over a weekend, I was pleasantly surprised by the ease and speed I was able to learn and implement a Chrome Extension that would address this gap, as well as add some features that I, in particular, value.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/pipeline-linker-recording.gif&quot; alt=&quot;cropped&quot; /&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h4 id=&quot;how-it-works&quot;&gt;How it works&lt;/h4&gt;

&lt;p&gt;The name of the Chrome Extension is &lt;a href=&quot;https://chrome.google.com/webstore/detail/snaplogic-pipeline-linker/cmngkccmjnhnjhjcnmajoiacacnjefgb&quot;&gt;Pipeline Linker&lt;/a&gt;, and it makes it easier to share links to Pipelines by copying the direct link of the active Pipeline to your clipboard:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I have open-sourced the extension’s code on GitHub: &lt;a href=&quot;https://github.com/SnapLogic/pipeline-linker&quot;&gt;https://github.com/SnapLogic/pipeline-linker&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When installed, this Chrome Extension will initially be grayed out and disabled:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/NNhY.png&quot; alt=&quot;installed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It will only be enabled if it detects the active tab is the &lt;a href=&quot;https://www.snaplogic.com/features/snaplogic-designer&quot;&gt;SnapLogic Designer&lt;/a&gt; (regardless of whether it was an internal or external environment):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/X4su.png&quot; alt=&quot;enabled&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When one wishes to share the direct link to the currently active pipeline in Designer, the extension’s icon beside the Chrome address bar (see above) is clicked or the keyboard shortcut is used (default is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+i&lt;/code&gt;. Mac users, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;control+i&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command(⌘)+i&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;When triggered, a Chrome Notification indicates that the Pipeline Link was copied to your clipboard:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vtlv.png&quot; alt=&quot;notification&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The notification message contains the name of the Pipeline, and the smaller contextual message below is the location of the pipeline and the Org name in parentheses.&lt;/p&gt;

&lt;p&gt;In your clipboard, a link like the following will be present and ready to be shared:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/MMps.png&quot; alt=&quot;slack&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;writing-the-extension&quot;&gt;Writing the extension&lt;/h4&gt;

&lt;p&gt;I had a short list of requirements for this extension:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It needed to retrieve the rendered HTML of the Designer page so it could be parsed for the information required to build the links.&lt;/li&gt;
  &lt;li&gt;The generated link should be copied to user’s clipboard.&lt;/li&gt;
  &lt;li&gt;A notficiation should be given to the user when the link has been copied.&lt;/li&gt;
  &lt;li&gt;The extension could be triggered with a keyboard shortcut.&lt;/li&gt;
  &lt;li&gt;The extension should only be enabled for the SnapLogic Designer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.chrome.com/extensions/getstarted&quot;&gt;Chrome Extension Developer Documentation&lt;/a&gt; is required reading, so I’m assuming the reader has done that.&lt;/p&gt;

&lt;p&gt;For the above requirements, 3 files would comprise the entirety of the functionality needed to cover the above:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the &lt;strong&gt;&lt;a href=&quot;https://github.com/SnapLogic/pipeline-linker/blob/master/js/background.js&quot;&gt;background.js&lt;/a&gt;&lt;/strong&gt; script, containing the majority of the JavaScript logic for the extension.&lt;/li&gt;
  &lt;li&gt;the &lt;strong&gt;&lt;a href=&quot;https://github.com/SnapLogic/pipeline-linker/blob/master/js/content.js****&quot;&gt;content.js&lt;/a&gt;&lt;/strong&gt; script, which is injected into HTML document of the user’s current browser tab, and executes the callback function, passing back the page’s rendered HTML. This is how the &lt;em&gt;background.js&lt;/em&gt; file receives the HTML to generate the Pipeline link.&lt;/li&gt;
  &lt;li&gt;the &lt;strong&gt;&lt;a href=&quot;https://github.com/SnapLogic/pipeline-linker/blob/master/manifest.json&quot;&gt;manifest.json&lt;/a&gt;&lt;/strong&gt; file, which specifies the browser permissions required, the background scripts, that it is a &lt;em&gt;page_action&lt;/em&gt; (it applies to a specific page rather than some generic behavior), the image files used as icons, and the keyboard shortcut commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Manifest&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;SnapLogic Pipeline Linker&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Copy the Active Pipeline&apos;s Link to the Clipboard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;manifest_version&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;activeTab&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;notifications&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;declarativeContent&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;scripts&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;js/background.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;persistent&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;page_action&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default_icon&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-19.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;38&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-38.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default_title&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Copy the link to the active pipeline&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;icons&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-128.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-48.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-16.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_execute_page_action&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Copy the link to the active pipeline&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;suggested_key&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Ctrl+I&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;MacCtrl+I&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above manifest is quite simple - most of the properties are self-explanatory, but I’ll call out the following that are worth further discussion:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permissions&lt;/code&gt; includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;activeTab&lt;/code&gt; (the extension only cares about the tab the user is currently using), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notifications&lt;/code&gt; (for pop-ups), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;declarativeContent&lt;/code&gt; (to use the metadata for the current tab - especially the URL - to control whether the extension is enabled or not).&lt;/p&gt;

    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notifications&lt;/code&gt; permission results in the user being prompted with the following reasonable request when installing the extension:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/images/5myi.png&quot; alt=&quot;installation&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;Both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;activeTab&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;declarativeContent&lt;/code&gt; have the advantage of not generating any additional permission warnings to the user.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background&lt;/code&gt; lists the scripts that run when the extension is active. A single script (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt;) is listed.&lt;/p&gt;

    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;persistent&quot;: false&lt;/code&gt; identifies this as an &lt;a href=&quot;https://developer.chrome.com/extensions/event_pages&quot;&gt;Event Page&lt;/a&gt; - a more performant and efficient solution that allows Chrome to load and unload the extension’s scripts automatically, based on the activity of the current page.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commands&lt;/code&gt; enables keyboard shortcuts to be defined and configured. When the user performs the shorcut (either using the default, suggested keys or after customising it themselves in their Chrome settings), the reserved &lt;a href=&quot;https://developer.chrome.com/extensions/commands&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_execute_page_action&lt;/code&gt;&lt;/a&gt; event is triggered.&lt;/p&gt;

    &lt;p&gt;This event is special in that it doesn’t need to be handled - the extension plugin framework takes care of the execution itself.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Enabling the Extension only for particular pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Page actions “represent actions that can be taken on the current page, but that aren’t applicable to all pages.” Therefore the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pageAction&lt;/code&gt; API was used to add listeners etc.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;declarativeContent&lt;/code&gt; permission mentioned earlier allows use of the &lt;a href=&quot;https://developer.chrome.com/extensions/declarativeContent&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.declarativeContent&lt;/code&gt; API&lt;/a&gt;. This API (available since Chrome 33) allows the extension “to take actions depending on the content of a page, without requiring permission to read the page’s content.”&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;c1&quot;&gt;// only enable the extension on SnapLogic Designer&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onInstalled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;declarativeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onPageChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeRules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;declarativeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onPageChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;conditions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;declarativeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PageStateMatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;pageUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hostSuffix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;elastic.snaplogic.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pathEquals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/sl/designer.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;declarativeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ShowPageAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.chrome.com/extensions/declarativeContent#type-PageStateMatcher&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.declarativeContent.PageStateMatcher&lt;/code&gt;&lt;/a&gt; allows definiting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hostSuffix&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt; that, when matched, enables the extension. When the condition does not match, the extension icon shows us as grayed out and nothing happens when it is clicked:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/CKyG.png&quot; alt=&quot;enabled-disabled&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parsing the HTML&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; script isn’t able to access the HTML of the active tab. Instead, another script (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content.js&lt;/code&gt;) is injected into the current page which then communicates with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; by passing messages:&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;c1&quot;&gt;// when the PipelineLinker extension is triggered&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pageAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onClicked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browserTab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// execute the script that gets injected into page of the current tag&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tabs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;executeScript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;js/content.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// send a message to content script&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tabs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browserTab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Background page started.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// receive the HTML from the tab&apos;s page and convert it to a DOM Document&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;htmlToDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;htmlToDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// HTML5 &amp;lt;template&amp;gt; allows any element underneath it&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerHTML&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To receive the HTML of the target page the extension wishes to interact with, the above code runs as part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; script. It adds a listener to the page action that, when triggered, sends a message (using Chrome’s &lt;a href=&quot;https://developer.chrome.com/extensions/messaging&quot;&gt;Message Passing API&lt;/a&gt;) to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content.js&lt;/code&gt; script.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content.js&lt;/code&gt; script is extremely simple:&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sendResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;sendResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outerHTML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On receiving the message sent by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt;, it executes the callback function, passing it &lt;a href=&quot;http://stackoverflow.com/questions/8853784/get-html-from-a-selected-tab&quot;&gt;the entire HTML (as a String)&lt;/a&gt; of the active tab.&lt;/p&gt;

&lt;p&gt;The anonymous callback function that was passed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; then converts that HTML to a Document by using &lt;a href=&quot;http://stackoverflow.com/a/35385518/277133&quot;&gt;HTML5’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element&lt;/a&gt;, which “allows any other element type as a child”.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/SnapLogic/pipeline-linker/blob/master/js/background.js#L20&quot;&gt;rest of the function&lt;/a&gt; is then just using selectors to navigate to the portion of the HTML of interest to the extension to build and copy the link, and the send the browser notification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copying to the Clipboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was surprisingly difficult to identify with Google searches. I tried a few different methods that did not work - it must have been something particular with how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; executes in the context of the Chrome Extension architecture vs the browser. Anyway, I finally &lt;a href=&quot;http://stackoverflow.com/a/18455088/277133&quot;&gt;found a function&lt;/a&gt; that worked well:&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;copyToClipboard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipelineLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;opacity&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;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipelineLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;execCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Copy&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Triggering a Notification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Again, very straightforward thanks to the &lt;a href=&quot;https://developer.chrome.com/apps/notifications&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.notifications API&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createNotification&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipelineName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipelineLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;opt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;basic&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Pipeline Link Copied&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipelineName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;contextMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipelineLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;iconUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img/icon-80.png&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;opt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notificationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;timer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notificationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Being able to control the duration of the popup was a nice extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard Shortcuts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defining a keyboard shortcut is done entirely within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.json&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-javascript 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;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_execute_page_action&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Copy the link to the active pipeline&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;suggested_key&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Ctrl+I&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;MacCtrl+I&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the user performs the shorcut, the reserved &lt;a href=&quot;https://developer.chrome.com/extensions/commands&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_execute_page_action&lt;/code&gt;&lt;/a&gt; event is triggered. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggested_key&lt;/code&gt; lists the default and/or OS-specific commands that are bound to the extension out-of-the-box. However, the user can change these to whatever they wish with a link at the bottom their `&lt;a href=&quot;chrome://extensions/&quot;&gt;chrome://extensions&lt;/a&gt; page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/KVH9.png&quot; alt=&quot;chrome-extensions-page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There were two “gotchas” worth calling out; on Mac, I couldn’t get a command with two or more combinations of Control/Shift/Command keys plus a letter (as per &lt;a href=&quot;http://stackoverflow.com/a/18541816/277133&quot;&gt;this SO answer&lt;/a&gt;) - hence the default of Ctrl+i. Other responses on that thread appear to have a different experience.&lt;/p&gt;

&lt;p&gt;The other thing to watch out for is when OS-global shortcuts override your selected shortcut combination. My Evernote app had shortcuts defined that were interfering, sometimes silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/webstore/publish&quot;&gt;Publishing the extension&lt;/a&gt; to the Chrome Web Store was straightforward. A fair number of logo sizes were recommended. My graphical skills are minimal but I was able to knock something out easily enough.&lt;/p&gt;

&lt;p&gt;Overall, creating a Chrome Extension is a concise but pleasant development experience and an extremely effective way of distributing “polyfill”-style features for existing web applications.&lt;/p&gt;
</description>
        <pubDate>Sat, 12 Nov 2016 07:27:43 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2016/11/12/building-a-google-chrome-extension-including-keyboard-shortcuts-and-copying-to-the-clipboard/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2016/11/12/building-a-google-chrome-extension-including-keyboard-shortcuts-and-copying-to-the-clipboard/</guid>
        
        <category>code</category>
        
        <category>chrome</category>
        
        <category>snaplogic</category>
        
        
      </item>
    
      <item>
        <title>Everything you ever wanted to know about SSL (but were afraid to ask)</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;Or perhaps more accurately, “practical things I’ve learned about SSL”. This post (and the &lt;a href=&quot;https://github.com/robinhowlett/everything-ssl&quot;&gt;companion Spring Boot application&lt;/a&gt;) will demonstrate using SSL certificates to validate and authenticate connections to secure endpoints over HTTPS for some common use cases (web servers, browser authentication, unit and integration testing). It shows how to configure Apache HTTP server for two-way SSL, unit testing SSL authentication with Apache’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpClient&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; (Java), and integration testing a REST API within a Spring Boot application running on an embedded Tomcat container.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are lots of ways for a client to authenticate itself against a server, including basic authentication, form-based authentication, and OAuth.&lt;/p&gt;

&lt;p&gt;To prevent exposing user credentials over the wire, the client communicates with the server over HTTPS, and the server’s identify is confirmed by validating its SSL certificate. The server doesn’t necessarily care who the client is, just as long as they have the correct credentials.&lt;/p&gt;

&lt;p&gt;An even higher level of security can be gained with using SSL certificates for both the client and the server.&lt;/p&gt;

&lt;p&gt;Two-way SSL authentication (also known as “mutual authentication”, and “TLS/SSL with client certificates”) refers to two parties authenticating each other through verifying provided digital certificates, so that both parties are assured of the other’s identity.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;#terminology&quot;&gt;Terminology&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;#authentication-with-ssl&quot;&gt;Authentication with SSL&lt;/a&gt;&lt;/li&gt;
	&lt;ul&gt;
		&lt;li&gt;&lt;a href=&quot;#one-way-ssl-authentication-server---client&quot;&gt;One-way SSL (server -&amp;gt; client)&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#two-way-ssl-authentication-server---client&quot;&gt;Two-way SSL (server &amp;lt;-&amp;gt; client)&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#file-formats-for-certs-and-keys&quot;&gt;File Formats for Certs and Keys&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#tools&quot;&gt;Tools&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#pki-and-the-ssl-certificate-chain-the-chain-of-trust&quot;&gt;PKI and the SSL Certificate Chain (&quot;the Chain of Trust&quot;)&lt;/a&gt;&lt;/li&gt;
	&lt;/ul&gt;
	&lt;li&gt;&lt;a href=&quot;#create-a-local-ssl-server-with-apache&quot;&gt;Create a local SSL server with Apache&lt;/a&gt;&lt;/li&gt;
	&lt;ul&gt;
		&lt;li&gt;&lt;a href=&quot;#configuring-apache-creating-a-site&quot;&gt;Configuring Apache: Creating a Site&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#configuring-ssl&quot;&gt;Configuring SSL&lt;/a&gt;&lt;/li&gt;
	&lt;/ul&gt;
	&lt;li&gt;&lt;a href=&quot;#unit-testing-ssl-authentication-with-apaches-httpclient-and-httpserver&quot;&gt;Unit Testing SSL Authentication with Apache’s HttpClient and HttpServer&lt;/a&gt;&lt;/li&gt;
	&lt;ul&gt;
		&lt;li&gt;&lt;a href=&quot;#java-keystores-jks&quot;&gt;Java KeyStores (JKS)&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#creating-keystores-and-truststores-with-keytool&quot;&gt;Creating KeyStores and TrustStores with Keytool&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#one-way-ssl&quot;&gt;One-Way SSL&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;#two-way-ssl-client-certificates&quot;&gt;Two-Way SSL (Client Certificates)&lt;/a&gt;&lt;/li&gt;
	&lt;/ul&gt;
	&lt;li&gt;&lt;a href=&quot;#two-way-ssl-authentication-with-spring-boot-embedded-tomcat-and-resttemplate&quot;&gt;Two-Way SSL Authentication with Spring Boot, embedded Tomcat and RestTemplate&lt;/a&gt;&lt;/li&gt;
	&lt;ul&gt;
		&lt;li&gt;&lt;a href=&quot;#integration-testing-ssl-authentication-with-springs-testresttemplate&quot;&gt;Integration Testing SSL Authentication with Spring’s TestRestTemplate&lt;/a&gt;&lt;/li&gt;
	&lt;/ul&gt;
	&lt;li&gt;&lt;a href=&quot;#two-way-ssl-with-snaplogics-rest-snap&quot;&gt;Two-Way SSL with SnapLogic&apos;s REST Snap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;terminology&quot;&gt;Terminology&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TLS vs SSL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TLS is the successor to SSL. It is a protocol that ensures privacy between communicating applications. Unless otherwise stated, in this document consider TLS and SSL as interchangable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificate (cert)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The public half of a public/private key pair with some additional metadata about who issued it etc. It may be freely given to anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A private key can verify that its corresponding certificate/public key was used to encrypt data. It is never given out publicly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificate Authority (CA)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A company that issues digital certificates. For SSL/TLS certificates, there are a small number of providers (e.g. Symantec/Versign/Thawte, Comodo, GoDaddy, LetsEncrypt) whose certificates are included by most browsers and Operating Systems. They serve the purpose of a “trusted third party”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificate Signing Request (CSR)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A file generated with a private key. A CSR can be sent to a CA to request to be signed. The CA uses its private key to digitally sign the CSR and create a signed cert. Browsers can then use the CA’s cert to validate the new cert has been approved by the CA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X.509&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A specification governing the format and usage of certificates.&lt;/p&gt;

&lt;h2 id=&quot;authentication-with-ssl&quot;&gt;Authentication with SSL&lt;/h2&gt;

&lt;p&gt;SSL is the standard security technology for establishing an encrypted link between a web server and a browser. Normally when a browser (the client) establishes an SSL connection to a secure web site, only the server certificate is checked. The browser either relies on itself or the operating system providing a list of certs that have been designated as root certificates and to be trusted as CAs.&lt;/p&gt;

&lt;h3 id=&quot;one-way-ssl-authentication-server---client&quot;&gt;One-way SSL authentication (server -&amp;gt; client)&lt;/h3&gt;

&lt;p&gt;Client and server use 9 handshake messages to establish the encrypted channel prior to message exchanging:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientHello&lt;/code&gt; message proposing SSL options.&lt;/li&gt;
  &lt;li&gt;Server responds with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerHello&lt;/code&gt; message selecting the SSL options.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Certificate&lt;/code&gt; message, which contains the server’s certificate.&lt;/li&gt;
  &lt;li&gt;Server concludes its part of the negotiation with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerHelloDone&lt;/code&gt; message.&lt;/li&gt;
  &lt;li&gt;Client sends session key information (encrypted with server’s public key) in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientKeyExchange&lt;/code&gt; message.&lt;/li&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChangeCipherSpec&lt;/code&gt; message to activate the negotiated options for all future messages it will send.&lt;/li&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Finished&lt;/code&gt; message to let the server check the newly activated options.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChangeCipherSpec&lt;/code&gt; message to activate the negotiated options for all future messages it will send.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Finished&lt;/code&gt; message to let the client check the newly activated options.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;two-way-ssl-authentication-server---client&quot;&gt;Two-way SSL authentication (server &amp;lt;-&amp;gt; client)&lt;/h3&gt;

&lt;p&gt;Client and server use 12 handshake messages to establish the encrypted channel prior to message exchanging:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientHello&lt;/code&gt; message proposing SSL options.&lt;/li&gt;
  &lt;li&gt;Server responds with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerHello&lt;/code&gt; message selecting the SSL options.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Certificate&lt;/code&gt; message, which contains the server’s certificate.&lt;/li&gt;
  &lt;li&gt;Server requests client’s certificate in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CertificateRequest&lt;/code&gt; message, so that the connection can be mutually authenticated.&lt;/li&gt;
  &lt;li&gt;Server concludes its part of the negotiation with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerHelloDone&lt;/code&gt; message.&lt;/li&gt;
  &lt;li&gt;Client responds with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Certificate&lt;/code&gt; message, which contains the client’s certificate.&lt;/li&gt;
  &lt;li&gt;Client sends session key information (encrypted with server’s public key) in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientKeyExchange&lt;/code&gt; message.&lt;/li&gt;
  &lt;li&gt;Client sends a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CertificateVerify&lt;/code&gt; message to let the server know it owns the sent certificate.&lt;/li&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChangeCipherSpec&lt;/code&gt; message to activate the negotiated options for all future messages it will send.&lt;/li&gt;
  &lt;li&gt;Client sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Finished&lt;/code&gt; message to let the server check the newly activated options.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChangeCipherSpec&lt;/code&gt; message to activate the negotiated options for all future messages it will send.&lt;/li&gt;
  &lt;li&gt;Server sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Finished&lt;/code&gt; message to let the client check the newly activated options.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;file-formats-for-certs-and-keys&quot;&gt;File Formats for Certs and Keys&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Privacy-Enhanced Mail (PEM)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PEM is just Distinguished Encoding Rules (DER) that has been Base64 encoded. Used for keys and certificates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PKCS12&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PKCS12 is a password-protected format that can contain multiple certificates and keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java KeyStore (JKS)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Java version of PKCS12 and also password protected. Entries in a JKS file must have an “alias” that is unique. If an alias is not specified, “mykey” is used by default. It’s like a database for certs and keys.&lt;/p&gt;

&lt;h3 id=&quot;tools&quot;&gt;Tools&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;OpenSSL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An open source toolkit implementing the SSL (v2/v3) and TLS (v1) protocols, as well as a full-strength general purpose cryptography library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keytool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Manages a Java KeyStore of cryptographic keys, X.509 certificate chains, and trusted certificates. Ships with the JDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XCA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A graphical tool to create and manage certificates.&lt;/p&gt;

&lt;h3 id=&quot;pki-and-the-ssl-certificate-chain-the-chain-of-trust&quot;&gt;PKI and the SSL Certificate Chain (“the Chain of Trust”)&lt;/h3&gt;

&lt;p&gt;All SSL/TLS connections rely on a chain of trust called the SSL Certificate Chain. Part of &lt;a href=&quot;https://en.wikipedia.org/wiki/Public_key_infrastructure&quot;&gt;PKI (Public Key Infrastructure)&lt;/a&gt;, this chain of trust is established by certificate authorities (CAs) who serve as trust anchors that verify the validity of the systems being communicated with. Each client (browser, OS, etc.) ships with a list of trusted CAs.&lt;/p&gt;

&lt;h4 id=&quot;ca-signed-certificates&quot;&gt;CA-signed Certificates&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/l4ycer6wclvp72var32be9ru0ykjzjzn.png&quot; alt=&quot;Chain of Trust&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the above example, the wildcard certificate for “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.elastic.snaplogic.com&lt;/code&gt;” has been issued by the “Go Daddy Secure Certificate Authority - G2” intermediate CA, which in turn was issued by the “Go Daddy Root Certificate Authority - G2” root CA.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Many organizations will create their own internal, self-signed root CA to be used to sign certificates for PKI use within that organization. Then, if each system trusts that CA, the certificates that are issued and signed by that CA will be trusted too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To trust a system that presents a the above certificate at a particular domain (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://elastic.snaplogic.com&lt;/code&gt;), the client system must trust both the intermediate CA and the root CA (the public certs of those CAs must exist in the client system’s trust/CA store), as well as verifying the chain is valid (signatures match, domain names match, and other requirements of the X.509 standard).&lt;/p&gt;

&lt;p&gt;Once a client trusts the intermediate and root CAs, all valid certificates signed by those CAs will be trusted by the client.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If only particular certificates signed by a trusted CA should be trusted, then either limiting the certificates in the CA store, or checking for certain certificate fingerprints, etc. should be considered instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;self-signed-certificates&quot;&gt;Self-signed Certificates&lt;/h4&gt;

&lt;p&gt;Self-signed certificates have a chain length of 1 - they are not signed by a CA but by the certificate creator itself. All root certificates are self-signed (a chain has to start somewhere).&lt;/p&gt;

&lt;p&gt;For example, to create a self-signed certificate (plus private key) for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;, the following OpenSSL command may be used:&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;| root@SL-MBP-RHOWLETT.local:/private/etc/apache2/ssl 
| =&amp;gt; openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost_self-signed.key -out localhost_self-signed.pem
Generating a 2048 bit RSA private key
................................................+++
..............................+++
writing new private key to &apos;localhost_self-signed.key&apos;
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &apos;.&apos;, the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:Colorado
Locality Name (eg, city) []:Boulder
Organization Name (eg, company) [Internet Widgits Pty Ltd]:SnapLogic
Organizational Unit Name (eg, section) []:SnapTeam
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Common Name&quot;&lt;/code&gt; must match the domain that will be presenting the certificate e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.p12&lt;/code&gt; file (that can be imported and trusted within OS X’s Keychain application for example):&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;| root@SL-MBP-RHOWLETT.local:/private/etc/apache2/ssl 
| =&amp;gt; openssl pkcs12 -export -in /etc/apache2/ssl/localhost_self-signed.pem -inkey /etc/apache2/ssl/localhost_self-signed.key -name &quot;SelfSignedServer&quot; -out localhost_self-signed.p12
Enter Export Password:
Verifying - Enter Export Password:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you want to specifically control a small number of certificates to use within an internal network you control, self-signed certificates can be very useful.&lt;/p&gt;

&lt;h2 id=&quot;create-a-local-ssl-server-with-apache&quot;&gt;Create a local SSL server with Apache&lt;/h2&gt;

&lt;p&gt;Modified from: &lt;a href=&quot;https://gist.github.com/jonathantneal/774e4b0b3d4d739cbc53&quot;&gt;https://gist.github.com/jonathantneal/774e4b0b3d4d739cbc53&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring Apache&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Switch to root:&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 su
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within Terminal, start Apache:&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;apachectl start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In a web browser, visit &lt;a href=&quot;http://localhost&quot;&gt;http://localhost&lt;/a&gt;. You should see a message stating that &lt;strong&gt;It works!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring Apache for HTTP: Setting up a port 80 Virtual Host&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Within Terminal, edit the Apache Configuration:&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;vi /etc/apache2/httpd.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enable SSL by uncommenting line 143:&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;LoadModule ssl_module libexec/apache2/mod_ssl.so
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within your editor, replace line 212 to suppress messages about the server’s fully qualified domain name:&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;ServerName localhost
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, uncomment line 160 and line 499 to enable Virtual Hosts.&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;LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so

Include /private/etc/apache2/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Uncomment line 518 to include httpd-ssl.conf (to listen on port 443):&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;Include /private/etc/apache2/extra/httpd-ssl.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Optionally, uncomment line 169 to enable PHP.&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;LoadModule php5_module libexec/apache2/libphp5.so
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within Terminal, edit the Virtual Hosts&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;vi /etc/apache2/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within your editor, replace the entire contents of this file with the following, replacing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rhowlett&lt;/code&gt; with your user name.&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;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName localhost
    DocumentRoot &quot;/Users/rhowlett/Sites/localhost&quot;

    &amp;lt;Directory &quot;/Users/rhowlett/Sites/localhost&quot;&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within Terminal, restart Apache:&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;apachectl restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;configuring-apache-creating-a-site&quot;&gt;Configuring Apache: Creating a Site&lt;/h3&gt;

&lt;p&gt;Within &lt;strong&gt;Terminal&lt;/strong&gt;, Create a &lt;strong&gt;Sites&lt;/strong&gt; directory, which will be the parent directory of many individual Site subdirectories:&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;mkdir ~/Sites
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, create a &lt;strong&gt;localhost&lt;/strong&gt; subdirectory within &lt;strong&gt;Sites&lt;/strong&gt;, which will be our first site:&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;mkdir ~/Sites/localhost
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, create an HTML document within &lt;strong&gt;localhost&lt;/strong&gt;:&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 &quot;&amp;lt;h1&amp;gt;localhost works&amp;lt;/h1&amp;gt;&quot; &amp;gt; ~/Sites/localhost/index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, in a web browser, visit &lt;a href=&quot;http://localhost&quot;&gt;http://localhost&lt;/a&gt;. You should see a message stating that localhost works.&lt;/p&gt;

&lt;h3 id=&quot;configuring-ssl&quot;&gt;Configuring SSL&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/em&gt;: I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snaplogic&lt;/code&gt; for all passwords below&lt;/p&gt;

&lt;p&gt;Modified from: &lt;a href=&quot;http://www.stefanocapitanio.com/configuring-two-way-authentication-ssl-with-apache/&quot;&gt;http://www.stefanocapitanio.com/configuring-two-way-authentication-ssl-with-apache/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within &lt;strong&gt;Terminal&lt;/strong&gt;, create a SSL directory:&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;mkdir /etc/apache2/ssl

cd /etc/apache2/ssl
mkdir certs private
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Create the CA cert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a database to keep track of each certificate signed:&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 &apos;100001&apos; &amp;gt; serial
touch certindex.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make a custom config file for openssl to use:&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;vi openssl.cnf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&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;#
# OpenSSL configuration file.
#

# Establish working directory.

dir = .

[ ca ]
default_ca = CA_default

[ CA_default ]
serial = $dir/serial
database = $dir/certindex.txt
new_certs_dir = $dir/certs
certificate = $dir/cacert.pem
private_key = $dir/private/cakey.pem
default_days = 365
default_md = sha512
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
policy = policy_match

[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ req ]
default_bits = 2048 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha512 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
req_extensions = v3_req

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = US
countryName_min = 2
countryName_max = 2

stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Colorado

localityName = Locality Name (eg, city)
localityName_default = Boulder

0.organizationName = Organization Name (eg, company)
0.organizationName_default = SnapLogic

# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd

organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = SnapTeam

commonName = Common Name (eg, YOUR name)
commonName_max = 64
commonName_default = localhost 

emailAddress = Email Address
emailAddress_max = 64

# SET-ex3 = SET extension number 3

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20

unstructuredName = An optional company name

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = US 
countryName_min = 2
countryName_max = 2

stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Colorado

localityName = Locality Name (eg, city)

0.organizationName = Organization Name (eg, company)
0.organizationName_default = SnapLogic

# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd

organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = SnapTeam

commonName = Common Name (eg, YOUR name)
commonName_max = 64
commonName_default = localhost

emailAddress = Email Address
emailAddress_max = 64

# SET-ex3 = SET extension number 3

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20

unstructuredName = An optional company name
[ v3_ca ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always

[ v3_req ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that I’ve set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default_md&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sha512&lt;/code&gt; so that modern browsers won’t complain about &lt;a href=&quot;http://michaelwyres.com/2012/05/chrome-weak-signature-algorithm-solved/&quot;&gt;Weak Signature Algorithms&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a CA by creating a root certificate. This will create the private key (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private/cakey.pem&lt;/code&gt;) and the public key (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacert.pem&lt;/code&gt;, a.k.a. the certificate) of the root CA. Use the default &lt;strong&gt;localhost&lt;/strong&gt; for the common name:&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;| root@SL-MBP-RHOWLETT.local:/private/etc/apache2/ssl 
| =&amp;gt; openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf
Generating a 2048 bit RSA private key
................................+++
.............................+++
writing new private key to &apos;private/cakey.pem&apos;
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
phrase is too short, needs to be at least 4 chars
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &apos;.&apos;, the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [Colorado]:
Locality Name (eg, city) [Milan]:Boulder
Organization Name (eg, company) [Organization default]:SnapLogic
Organizational Unit Name (eg, section) [SnapLogic]:SnapTeam 
Common Name (eg, YOUR name) [localhost]:
Email Address []:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Create the Server cert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a key and signing request for the server. This will create the CSR for the server (server-req.pem) and the server’s private key (private/server-key.pem). Use the default localhost for the common name:&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;| root@SL-MBP-RHOWLETT.local:/etc/apache2/ssl 
| =&amp;gt; openssl req -new -nodes -out server-req.pem -keyout private/server-key.pem -days 365 -config openssl.cnf 
Generating a 2048 bit RSA private key
......+++
..................................+++
writing new private key to &apos;private/server-key.pem&apos;
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &apos;.&apos;, the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [Colorado]:
Locality Name (eg, city) [Boulder]:
Organization Name (eg, company) [SnapLogic]:
Organizational Unit Name (eg, section) [SnapTeam]:
Common Name (eg, YOUR name) [localhost]:
Email Address []:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Have the CA sign the server’s CSR. This will create the server’s public certificate (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server-cert.pem&lt;/code&gt;):&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;| root@SL-MBP-RHOWLETT.local:/etc/apache2/ssl 
| =&amp;gt; openssl ca -out server-cert.pem -days 365 -config openssl.cnf -infiles server-req.pem 
Using configuration from openssl.cnf
Enter pass phrase for ./private/cakey.pem:
Check that the request matches the signature
Signature ok
The Subject&apos;s Distinguished Name is as follows
countryName           :PRINTABLE:&apos;US&apos;
stateOrProvinceName   :PRINTABLE:&apos;Colorado&apos;
localityName          :PRINTABLE:&apos;Boulder&apos;
organizationName      :PRINTABLE:&apos;SnapLogic&apos;
organizationalUnitName:PRINTABLE:&apos;SnapTeam&apos;
commonName            :PRINTABLE:&apos;localhost&apos;
Certificate is to be certified until Oct  4 16:30:23 2016 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Create the Client cert&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each client will create a key and signing request. We will just create one for now. You must use a different common name than the server/CA - here I’m using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client&lt;/code&gt;. This will create the client’s CSR (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client-req.pem&lt;/code&gt;) and the client’s private key (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private/client-key.pem&lt;/code&gt;):&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;| root@SL-MBP-RHOWLETT.local:/etc/apache2/ssl 
| =&amp;gt; openssl req -new -nodes -out client-req.pem -keyout private/client-key.pem -days 365 -config openssl.cnf 
Generating a 2048 bit RSA private key
....................................................+++
...........+++
writing new private key to &apos;private/client-key.pem&apos;
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &apos;.&apos;, the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [Colorado]:
Locality Name (eg, city) [Boulder]:
Organization Name (eg, company) [SnapLogic]:
Organizational Unit Name (eg, section) [SnapTeam]:
Common Name (eg, YOUR name) [localhost]:client
Email Address []:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Have the CA sign the client’s CSR. This will create the client’s public certificate (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client-cert.pem&lt;/code&gt;):&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;| root@SL-MBP-RHOWLETT.local:/etc/apache2/ssl 
| =&amp;gt; openssl ca -out client-cert.pem -days 365 -config openssl.cnf -infiles client-req.pem 
Using configuration from openssl.cnf
Enter pass phrase for ./private/cakey.pem:
Check that the request matches the signature
Signature ok
The Subject&apos;s Distinguished Name is as follows
countryName           :PRINTABLE:&apos;US&apos;
stateOrProvinceName   :PRINTABLE:&apos;Colorado&apos;
localityName          :PRINTABLE:&apos;Boulder&apos;
organizationName      :PRINTABLE:&apos;SnapLogic&apos;
organizationalUnitName:PRINTABLE:&apos;SnapTeam&apos;
commonName            :PRINTABLE:&apos;client&apos;
Certificate is to be certified until Oct  4 16:40:01 2016 GMT (365 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, create the PKCS12 file using the client’s private key, the client’s public cert and the CA cert. This will create the (Mac-friendly) PKCS12 file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client-cert.p12&lt;/code&gt;):&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;| root@SL-MBP-RHOWLETT.local:/etc/apache2/ssl 
| =&amp;gt; openssl pkcs12 -export -in client-cert.pem -inkey private/client-key.pem -certfile cacert.pem -name &quot;Client&quot; -out client-cert.p12 
Enter Export Password:
Verifying - Enter Export Password:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Configuring Apache for HTTPS and one-way SSL auth&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As root:&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;vi /etc/apache2/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add a Virtual Host for port 443 and enable SSL:&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;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName localhost
    DocumentRoot &quot;/Users/rhowlett/Sites/localhost&quot;

    &amp;lt;Directory &quot;/Users/rhowlett/Sites/localhost&quot;&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;

&amp;lt;VirtualHost *:443&amp;gt;
    ServerName localhost
    DocumentRoot &quot;/Users/rhowlett/Sites/localhost&quot;

    SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/server-cert.pem
    SSLCertificateKeyFile /etc/apache2/ssl/private/server-key.pem

    &amp;lt;Directory &quot;/Users/rhowlett/Sites/localhost&quot;&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Restart Apache:&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;apachectl restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Mac) Install the CA cert into Keychain Access&lt;/p&gt;

&lt;p&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/ssl&lt;/code&gt; in Finder:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/v2qvj2qa867hkudz5x4jtxmlbovqnw8o.png&quot; alt=&quot;Finder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Open the CA cert (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacert.pem&lt;/code&gt;) by double-clicking it to install it to Keychain Access:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/zgczjbqqu1jxsx6uj7920fj73qw6qvcf.png&quot; alt=&quot;Install CA cert&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mark it as trusted:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/rwgi31nmwcfp8ld4bsryfpb884y0y95f.png&quot; alt=&quot;Trust CA cert Trusted&quot; /&gt;
&lt;img src=&quot;https://snaplogic.box.com/shared/static/yj41mm1fi2tpi4mr9n28hsmhgnlqm8c7.png&quot; alt=&quot;Trusted&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Open your browser to &lt;a href=&quot;https://localhost&quot;&gt;https://localhost&lt;/a&gt; and you should see a successful secure connection:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/v3em6y3nlpdeuki9n3aawpk9ek22lw73.png&quot; alt=&quot;Successful HTTPS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring Apache for two-way SSL auth&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As root:&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;vi /etc/apache2/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLVerifyClient&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLCertificateFile&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLCACertificateFile&lt;/code&gt; options:&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;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName localhost
    DocumentRoot &quot;/Users/rhowlett/Sites/localhost&quot;

    &amp;lt;Directory &quot;/Users/rhowlett/Sites/localhost&quot;&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;

&amp;lt;VirtualHost *:443&amp;gt;
    ServerName localhost
    DocumentRoot &quot;/Users/rhowlett/Sites/localhost&quot;

    SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/server-cert.pem
    SSLCertificateKeyFile /etc/apache2/ssl/private/server-key.pem

    SSLVerifyClient require
    SSLVerifyDepth 10
    SSLCACertificateFile /etc/apache2/ssl/cacert.pem

    &amp;lt;Directory &quot;/Users/rhowlett/Sites/localhost&quot;&amp;gt;
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Restart Apache:&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;apachectl restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;OpenSSL can now confirm that the two-way SSL handshake can be successfully completed:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/Downloads 
| =&amp;gt; openssl s_client -connect localhost:443 -tls1 -cert /etc/apache2/ssl/client-cert.pem -key /etc/apache2/ssl/private/client-key.pem
CONNECTED(00000003)
depth=1 /C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
verify error:num=19:self signed certificate in certificate chain
verify return:0
---
Certificate chain
 0 s:/C=US/ST=Colorado/O=SnapLogic/OU=SnapTeam/CN=localhost
   i:/C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
 1 s:/C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
   i:/C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDPjCCAiYCAxAAATANBgkqhkiG9w0BAQ0FADBtMQswCQYDVQQGEwJVUzERMA8G
A1UECBMIQ29sb3JhZG8xEDAOBgNVBAcTB0JvdWxkZXIxEjAQBgNVBAoTCVNuYXBM
b2dpYzERMA8GA1UECxMIU25hcFRlYW0xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0x
NTEwMDYxOTE1MTNaFw0xNjEwMDUxOTE1MTNaMFsxCzAJBgNVBAYTAlVTMREwDwYD
VQQIEwhDb2xvcmFkbzESMBAGA1UEChMJU25hcExvZ2ljMREwDwYDVQQLEwhTbmFw
VGVhbTESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAyvia0x0Nd4tYyvoXEYtI3s/eLIQ3wFsOJIibNy70PLhp35gScQ69
MiIrVDYqIydVbInzyY5kuhttrUIrHCIiDwa5OqEiExJ+ollY9icnrMLrEXJqvv5C
/fduS5byC6StNg7xHQkYlYLUYMw8QQyCZFQVGXlxZeG6i086ffMYduFimkBAkNj5
/LkIwrOELpGnNcrOJxQEnLi8vmRI3oiCrgVc0ugrFBnoj3Tf6y3lx23fYgLbqf9c
bRCS6V3eppa/x9sezv9KQ+pDYly0bwKIcvJ9xLp7qPiO+smGGvS97Ec4NAif8y6v
pU92cPH32cv1p0AIDF0+GMOgVyAYZgSKQwIDAQABMA0GCSqGSIb3DQEBDQUAA4IB
AQBedsAvkB1yNLE2GCJWWQ19qEKOIBYCRQc2z29PgF/LAz5GVOIw/ZiN37C2vTob
jk1NnqfOx5aipQ5Pe5D2yfbarDl0kaqRn9MhBySi+oi3AgUZ5yL0x/nGF9O8jszJ
OM1FUC6qXKic5pR0qTrdXigONlKb0Au+l3z5dFMiqnNmDrNlI8kW1OXrwy/jvyPv
H1bHWAKYFTvHi2v7A0B96V1VvFBLbuQztckPQ3VpFDOwWhWLr2D90vxFd1Ea0SCi
3bysz4ax9XP0bmXJY+968nV31qQJMkk5/3rE5PWVZibsniccfdujgSQYl+yNA3sB
F5h6mCR6pAONZFo6+U3zARSb
-----END CERTIFICATE-----
subject=/C=US/ST=Colorado/O=SnapLogic/OU=SnapTeam/CN=localhost
issuer=/C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
---
Acceptable client certificate CA names
/C=US/ST=Colorado/L=Boulder/O=SnapLogic/OU=SnapTeam/CN=localhost
---
SSL handshake has read 4004 bytes and written 1539 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: A1D9CE5273963BCF70503B499D7714ECE2B628CEE59CE554615743ACEEA8E281
    Session-ID-ctx: 
    Master-Key: 0920CEE1491E9A116B2DF959430890D449D49DA990A178C0AC980DD5AF359B7E1CDD4B2D8237C8F81BAE186BC06E7BB0
    Key-Arg   : None
    TLS session ticket:
    0000 - 5c 8f 01 d5 5d c1 62 d5-65 d7 8f 05 5f 47 d2 82   \...].b.e..._G..
    0010 - f0 fd 2c 88 be 58 25 6c-9e 9a 1e 78 6a b4 66 c4   ..,..X%l...xj.f.
    0020 - 0d 3d 31 04 97 a2 5f e7-6f 3c 9f c9 b1 44 6a ab   .=1..._.o&amp;lt;...Dj.
    0030 - 84 89 76 e4 63 9b 81 b7-c3 28 e0 95 c6 c3 f5 89   ..v.c....(......
    0040 - d5 f9 7f da df fb 12 f7-de 2a ec e8 c2 01 59 07   .........*....Y.
    0050 - 9e ad 91 56 91 34 88 73-66 d1 ea c1 72 dc 56 ee   ...V.4.sf...r.V.
    0060 - ee 61 fe 5e 38 f0 aa d6-3a 7d ad ef e6 be 2a 15   .a.^8...:}....*.
    0070 - dc cc 9f 04 5e e8 f9 2b-07 21 6b 0f da 9f 08 2e   ....^..+.!k.....
    0080 - 88 af 96 41 98 f3 ff 8a-01 66 1a 1d 61 47 1b e5   ...A.....f..aG..
    0090 - ec ab b7 af 79 aa 7d 25-ca e0 fa f4 2b 2e 9a dd   ....y.}%....+...
    00a0 - 95 0c 4b 35 d8 96 8b f0-1e 20 c1 c3 47 fc 65 ed   ..K5..... ..G.e.
    00b0 - 21 e4 50 59 1e 33 6a 5c-c6 27 f1 65 be 5b 0f 35   !.PY.3j\.&apos;.e.[.5
    00c0 - 1d ba ac bf f5 9c d9 b7-32 87 11 ae b7 87 9b 52   ........2......R
    00d0 - bb 00 6b 66 af e2 94 45-e3 8f fb e0 b4 c6 d7 5a   ..kf...E.......Z
    00e0 - f8 1d 7a af e3 ee bb 6b-93 ff 46 af ed 86 bc f8   ..z....k..F.....
    00f0 - 6d e2 c9 60 eb 61 8e b9-7e bd 4d bb 1e 01 95 d2   m..`.a..~.M.....
    0100 - f5 d5 ee 82 10 4a 1d 23-9e 94 d7 0b 46 e4 d4 32   .....J.#....F..2
    0110 - 11 92 76 4a 94 9e a3 61-21 9b 4c 49 6c df 7b 18   ..vJ...a!.LIl.{.
    0120 - b7 49 66 bd 48 0d eb 9a-ad e9 32 c7 b9 6d 70 1a   .If.H.....2..mp.
    0130 - c7 a1 25 21 b4 f1 03 5b-80 83 e9 da 8d 56 f1 d9   ..%!...[.....V..
    0140 - 8b c5 32 b7 3a 67 5b 9c-51 84 a0 09 04 4f 48 60   ..2.:g[.Q....OH`
    0150 - 27 c0 fe 1c 45 7a 3b b2-22 8d ed 65 72 23 8a bf   &apos;...Ez;.&quot;..er#..
    0160 - e3 09 eb 78 98 ec 08 06-9d 37 02 1a 4b ae cd 3a   ...x.....7..K..:
    0170 - 9c a4 bd 5d 47 5e d3 d7-7b 89 7b 97 78 a6 4c 10   ...]G^..{.{.x.L.
    0180 - bf 3e ed 1f f4 fe e5 97-90 ee 31 58 5f ff c6 c2   .&amp;gt;........1X_...
    0190 - 61 b7 df 0a f5 27 c6 a8-ac 61 a3 d0 1e 3a 6a 42   a....&apos;...a...:jB
    01a0 - a9 18 b4 fb 4b 25 87 62-97 26 48 35 d0 16 d1 06   ....K%.b.&amp;amp;H5....
    01b0 - 9d 82 b5 e2 7b f2 24 c5-83 a1 4b fe 8d 38 ae 30   ....{.$...K..8.0
    01c0 - 8e eb e1 ac 8b 48 fa 27-b0 e1 ce b3 17 62 69 f0   .....H.&apos;.....bi.
    01d0 - 30 17 ae 31 9d bf 77 64-66 5b 13 8e a2 63 2e 58   0..1..wdf[...c.X
    01e0 - 02 10 26 e1 3b 0d 55 fc-3d 0f d5 08 2d 1e 28 0a   ..&amp;amp;.;.U.=...-.(.
    01f0 - c2 fd a2 f3 2a 40 25 ed-2b 06 2c 92 c3 78 a3 b3   ....*@%.+.,..x..
    0200 - 35 bc d9 6c 57 97 ca 93-0f f3 b8 e4 60 d8 99 b4   5..lW.......`...
    0210 - b8 ba ae b7 47 4a 59 84-5b f9 5e b2 11 44 42 bd   ....GJY.[.^..DB.
    0220 - e8 46 3d 1d 09 70 72 f6-23 df 89 f8 f7 b7 84 d2   .F=..pr.#.......
    0230 - 7d 42 0e 5d d7 76 c2 da-0b 61 f9 48 3c c9 5f ba   }B.].v...a.H&amp;lt;._.
    0240 - ab be 5f 82 2b 03 07 f1-83 12 69 ee 56 b5 7e 06   .._.+.....i.V.~.
    0250 - 03 d7 8e b3 70 7c 93 75-3d cd e0 a1 1b 8a 14 ef   ....p|.u=.......
    0260 - 91 c6 74 14 1e 16 4c 46-07 c5 62 04 70 a7 fd 5d   ..t...LF..b.p..]
    0270 - e5 67 d8 bf 43 bb 5e f3-7c 37 db 1a 66 cb ad 7d   .g..C.^.|7..f..}
    0280 - cc 30 e4 9b 35 30 b5 6c-d0 4b ba b2 8b 01 71 0e   .0..50.l.K....q.
    0290 - 0a af ec 4e 6a 1a f8 6f-b7 5e 2b b9 e9 ec b6 b6   ...Nj..o.^+.....
    02a0 - 38 1c 70 5c 86 bf ae a4-e6 41 9d c9 9f 40 e4 a0   8.p\.....A...@..
    02b0 - 4b 0d 3d ab 01 90 da 55-cb b8 c8 e6 94 8d 76 35   K.=....U......v5
    02c0 - 94 b5 e2 1a 7c 69 5c b3-ee 08 8b bd 3f 97 c4 31   ....|i\.....?..1
    02d0 - 72 8a 30 a8 c6 3e 74 74-dc 47 c1 d0 ce bd 0b 19   r.0..&amp;gt;tt.G......
    02e0 - f4 93 55 8c 1f 02 b3 6e-f3 4d 44 f1 cc f0 ef 2d   ..U....n.MD....-
    02f0 - 4d 16 92 a3 15 fe 69 db-cc b1 b5 6b d0 4a 49 fc   M.....i....k.JI.
    0300 - 67 9e 0c 47 96 08 0e f2-b2 5c 06 24 45 f3 6a 7d   g..G.....\.$E.j}
    0310 - 6e 1b 2b 9a 68 23 11 3a-43 79 8c 77 9e 98 be 38   n.+.h#.:Cy.w...8
    0320 - 9a 0e e1 a5 17 bd 0f 7b-e0 ac ca 94 ac 48 68 5c   .......{.....Hh\
    0330 - f1 2b 98 b5 8d 36 b6 4f-aa 6f e7 d4 4d a3 f0 4c   .+...6.O.o..M..L
    0340 - cb 09 92 91 01 b9 c2 f1-49 24 64 d3 14 2f a3 5f   ........I$d../._
    0350 - 74 6f c0 54 16 73 c8 40-33 bc 7e e9 3b d8 d5 7c   to.T.s.@3.~.;..|
    0360 - 78 49 5c 80 83 88 4e 4b-46 f2 7a 6b 62 c4 ca 42   xI\...NKF.zkb..B
    0370 - 18 b6 22 40 77 fc 26 0e-28 50 89 7a 14 49 ba b0   ..&quot;@w.&amp;amp;.(P.z.I..
    0380 - 2c d7 26 7a 30 f9 9b 90-ba 9a 1f 3b 80 1b 0b 25   ,.&amp;amp;z0......;...%
    0390 - f0 e7 83 83 55 1f 1e f0-71 5b 64 a4 1e 76 91 bb   ....U...q[d..v..
    03a0 - d9 19 f5 2d 2e 54 d7 3a-93 95 29 ae 44 09 e6 cd   ...-.T.:..).D...
    03b0 - ec 79 8d b6 3c 09 d5 05-8d fc 2b 79 88 37 25 92   .y..&amp;lt;.....+y.7%.
    03c0 - 73 ae e6 8a d6 0c 1a eb-7b b9 08 44 4e 81 67 36   s.......{..DN.g6
    03d0 - a6 3a 57 43 d0 ed dc 3e-bb 0f 87 02 f5 fe 80 bb   .:WC...&amp;gt;........
    03e0 - 28 17 6e 7e ad c4 d9 4c-0a 53 fa 41 d2 d2 7c 76   (.n~...L.S.A..|v
    03f0 - a4 95 10 26 1d 5b 7d 19-23 dd 28 a0 48 c1 96 d9   ...&amp;amp;.[}.#.(.H...

    Start Time: 1444189099
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
closed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;(Mac) Install the client PKCS12 file into Keychain Access&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/ssl&lt;/code&gt; in Finder and double-click the client PKCS12 file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client-cert.p12&lt;/code&gt;) to install it to Keychain Access:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/dzwp18utb34mdeyp280ccdn7n5zkyur4.png&quot; alt=&quot;Enter client cert password&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/lbom67dygm4r2m07pf8merf6i0tm73we.png&quot; alt=&quot;Client cert added&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;a href=&quot;https://localhost&quot;&gt;https://localhost&lt;/a&gt; in your browser again and select the client cert when prompted:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/4s5hq9ksyrqex4ghkhgeb5rdle5bj6yc.png&quot; alt=&quot;Select client cert&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You should then once again see a successful secure connection:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/v3em6y3nlpdeuki9n3aawpk9ek22lw73.png&quot; alt=&quot;Successful two-way SSL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://curl.haxx.se/&quot;&gt;cURL&lt;/a&gt; will also work:&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;| =&amp;gt; curl -v --cert /etc/apache2/ssl/client-cert.p12:snaplogic https://localhost
* Rebuilt URL to: https://localhost/
*   Trying ::1...
* Connected to localhost (::1) port 443 (#0)
* WARNING: SSL: Certificate type not set, assuming PKCS#12 format.
* Client certificate: client
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/ngbfxftthix3iu78szeb37jf6sg1lrwf.png&quot; alt=&quot;curl prompt&quot; /&gt;&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;| =&amp;gt; curl -v --cert /etc/apache2/ssl/client-cert.p12:snaplogic https://localhost
* Rebuilt URL to: https://localhost/
*   Trying ::1...
* Connected to localhost (::1) port 443 (#0)
* WARNING: SSL: Certificate type not set, assuming PKCS#12 format.
* Client certificate: client
* TLS 1.0 connection using TLS_DHE_RSA_WITH_AES_256_CBC_SHA
* Server certificate: localhost
* Server certificate: localhost
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: localhost
&amp;gt; User-Agent: curl/7.43.0
&amp;gt; Accept: */*
&amp;gt; 
&amp;lt; HTTP/1.1 200 OK
&amp;lt; Date: Tue, 05 Jan 2016 23:16:29 GMT
&amp;lt; Server: Apache/2.4.16 (Unix) PHP/5.5.29 OpenSSL/0.9.8zg
&amp;lt; Last-Modified: Fri, 02 Oct 2015 18:34:41 GMT
&amp;lt; ETag: &quot;19-521236aaf5240&quot;
&amp;lt; Accept-Ranges: bytes
&amp;lt; Content-Length: 25
&amp;lt; Content-Type: text/html
&amp;lt; 
&amp;lt;h1&amp;gt;localhost works&amp;lt;/h1&amp;gt;
* Connection #0 to host localhost left intact
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;unit-testing-ssl-authentication-with-apaches-httpclient-and-httpserver&quot;&gt;Unit Testing SSL Authentication with Apache’s HttpClient and HttpServer&lt;/h2&gt;

&lt;p&gt;Apache’s &lt;a href=&quot;https://hc.apache.org/&quot;&gt;HttpComponents&lt;/a&gt; provides &lt;a href=&quot;https://hc.apache.org/httpcomponents-client-ga/index.html&quot;&gt;HttpClient&lt;/a&gt;, “an efficient, up-to-date, and feature-rich package implementing the client side of the most recent HTTP standards and recommendations.”&lt;/p&gt;

&lt;p&gt;It also provides &lt;a href=&quot;https://hc.apache.org/httpcomponents-core-ga/index.html&quot;&gt;HttpCore&lt;/a&gt;, which includes an embedded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt;, which can be used for unit testing.&lt;/p&gt;

&lt;p&gt;Generate a PKCS12 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.p12&lt;/code&gt;) file from the public &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server-cert.pem&lt;/code&gt;, the private &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server-key.pem&lt;/code&gt;, and the CA cert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacert.pem&lt;/code&gt; created above to be used by the local test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; instance:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; openssl pkcs12 -export -in /etc/apache2/ssl/server-cert.pem -inkey /etc/apache2/ssl/private/server-key.pem -certfile /etc/apache2/ssl/cacert.pem -name &quot;Server&quot; -out server-cert.p12
Enter Export Password:
Verifying - Enter Export Password:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unable to write &apos;random state&apos;&lt;/code&gt;, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo rm ~/.rnd&lt;/code&gt; and try again&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;java-keystores-jks&quot;&gt;Java KeyStores (JKS)&lt;/h3&gt;

&lt;p&gt;Java has its own version of PKCS12 called &lt;strong&gt;Java KeyStore (JKS)&lt;/strong&gt;. It is also password protected. Entries in a JKS file must have an “alias” that is unique. If an alias is not specified, “mykey” is used by default. It’s like a database for certs and keys.&lt;/p&gt;

&lt;p&gt;For both the “KeyStore” and “TrustStore” fields in the REST SSL Account settings, we are going to use JKS files. The difference between them is for terminology reasons: KeyStores provide credentials, TrustStores verify credentials.&lt;/p&gt;

&lt;p&gt;Clients will use certificates stored in their TrustStores to verify identities of servers. They will present certificates stored in their KeyStores to servers requiring them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIENsaWVudCBLZXlTdG9yZSwACQdUcnVzdAAMBgAcBzoAIwhsb2FkcwAWCyBjb250YWluaW5nIHRydXN0ZWQgc2VydmVyIGNlcnRzXG4ALA0AYggAKwxzaWduZWQgYwCBBgYAMQUKAIETBi0-K1MARwU6IFJlcXVlc3RzIHByb3RlY3RlZCByZXNvdXJjZQoAHgYtPi0AgSQIUHJlc2VudHMAfgwASQoAgVQROiBWZXJpZmllAB0UAIICCwBXC1ZhbGlkYXRlZABGEQCCRAgAgSgLAIFPCwBNCACCaggAgSoLUmV0dXJuABoUAIF8CwCBUQkASAwAgXkHAIF2C0Fja25vd2xlZGdlcyB2AIEmBmlvbgCCTgkAgk4IQWNjZXNzZQCCQhQ&amp;amp;s=default&quot; alt=&quot;Client Certificate Flow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The JDK ships with a tool called &lt;strong&gt;Keytool&lt;/strong&gt;. It manages a JKS of cryptographic keys, X.509 certificate chains, and trusted certificates.&lt;/p&gt;

&lt;h3 id=&quot;creating-keystores-and-truststores-with-keytool&quot;&gt;Creating KeyStores and TrustStores with Keytool&lt;/h3&gt;

&lt;p&gt;Create the Server’s KeyStore from the PKCS12 file:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -importkeystore -deststorepass snaplogic -destkeypass snaplogic -destkeystore server_keystore.jks -srckeystore server-cert.p12 -srcstoretype PKCS12 -srcstorepass snaplogic -alias server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;View the server keystore to confirm it now contains the server’s cert:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -list -v -keystore server_keystore.jks 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: server
Creation date: Jan 4, 2016
Entry type: PrivateKeyEntry
Certificate chain length: 2
Certificate[1]:
Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: 100001
Valid from: Tue Oct 06 13:15:13 MDT 2015 until: Wed Oct 05 13:15:13 MDT 2016
Certificate fingerprints:
	 MD5:  62:83:6B:84:1B:CB:DE:26:CA:E0:9D:E8:04:84:B6:C1
	 SHA1: AD:D4:27:FF:9A:68:77:25:95:C3:A2:BE:F6:22:AD:82:5C:2B:AF:EB
	 SHA256: 8D:8D:EA:E5:7C:7A:E9:42:C9:9E:71:2A:76:C7:BE:BE:34:CC:4A:CC:83:ED:FE:C8:8E:C6:06:D2:D8:89:59:4A
	 Signature algorithm name: SHA512withRSA
	 Version: 1
Certificate[2]:
Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: e4e00ed07233a969
Valid from: Tue Oct 06 13:14:51 MDT 2015 until: Wed Oct 05 13:14:51 MDT 2016
Certificate fingerprints:
	 MD5:  F3:5E:28:E4:28:47:F2:EC:82:E2:BD:16:31:DC:90:02
	 SHA1: 6F:0F:49:BA:A9:30:01:E9:4C:60:B3:A1:85:7D:BB:C6:79:1F:41:7B
	 SHA256: A7:9D:25:E4:A6:34:8A:A3:5B:9A:CD:F3:62:D0:D8:2F:6A:A0:71:6A:6D:19:F3:04:A1:FD:BC:FB:21:40:DE:A1
	 Signature algorithm name: SHA512withRSA
	 Version: 3

Extensions: 

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
[CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US]
SerialNumber: [    e4e00ed0 7233a969]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
]



*******************************************
*******************************************
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create the client’s truststore and import the server’s public certificate:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -import -v -trustcacerts -keystore client_truststore.jks -storepass snaplogic -alias server -file /etc/apache2/ssl/server-cert.pem
Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: 100001
Valid from: Tue Oct 06 13:15:13 MDT 2015 until: Wed Oct 05 13:15:13 MDT 2016
Certificate fingerprints:
	 MD5:  62:83:6B:84:1B:CB:DE:26:CA:E0:9D:E8:04:84:B6:C1
	 SHA1: AD:D4:27:FF:9A:68:77:25:95:C3:A2:BE:F6:22:AD:82:5C:2B:AF:EB
	 SHA256: 8D:8D:EA:E5:7C:7A:E9:42:C9:9E:71:2A:76:C7:BE:BE:34:CC:4A:CC:83:ED:FE:C8:8E:C6:06:D2:D8:89:59:4A
	 Signature algorithm name: SHA512withRSA
	 Version: 1
Trust this certificate? [no]:  yes
Certificate was added to keystore
[Storing client_truststore.jks]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;View the client’s truststore to confirm it contains the server’s cert:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -list -v -keystore client_truststore.jks 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: server
Creation date: Jan 4, 2016
Entry type: trustedCertEntry

Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: 100001
Valid from: Tue Oct 06 13:15:13 MDT 2015 until: Wed Oct 05 13:15:13 MDT 2016
Certificate fingerprints:
	 MD5:  62:83:6B:84:1B:CB:DE:26:CA:E0:9D:E8:04:84:B6:C1
	 SHA1: AD:D4:27:FF:9A:68:77:25:95:C3:A2:BE:F6:22:AD:82:5C:2B:AF:EB
	 SHA256: 8D:8D:EA:E5:7C:7A:E9:42:C9:9E:71:2A:76:C7:BE:BE:34:CC:4A:CC:83:ED:FE:C8:8E:C6:06:D2:D8:89:59:4A
	 Signature algorithm name: SHA512withRSA
	 Version: 1


*******************************************
*******************************************
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;one-way-ssl&quot;&gt;One-Way SSL&lt;/h4&gt;

&lt;p&gt;At this point we have enough to demonstrate one-way SSL with the local test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; instance. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createLocalTestServer&lt;/code&gt; method instantiates an embedded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; instance with an (optional) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sslContext&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; meaning HTTP-only) and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boolean&lt;/code&gt; “forceSSLAuth” indicating if client certificates are required or not:&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;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpServer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createLocalTestServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&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;n&quot;&gt;forceSSLAuth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnknownHostException&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;HttpServer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServerBootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bootstrap&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;setLocalAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Inet4Address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getByName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;localhost&quot;&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;setSslContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sslContext&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;setSslSetupHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setNeedClientAuth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forceSSLAuth&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;registerHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&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;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setStatusCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SC_OK&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;create&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;server&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;getStore&lt;/code&gt; method loads the JKS files from the classpath, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getKeyManagers&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTrustManagers&lt;/code&gt; methods turn that store into the respective &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Key-&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrustManager&lt;/code&gt; arrays that are used to initialize an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLContext&lt;/code&gt;:&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;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JAVA_KEYSTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;jks&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * KeyStores provide credentials, TrustStores verify credentials.
 *
 * Server KeyStores stores the server&apos;s private keys, and certificates for corresponding public
 * keys. Used here for HTTPS connections over localhost.
 *
 * Client TrustStores store servers&apos; certificates.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getStore&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;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storeFileName&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;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;KeyStoreException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CertificateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NoSuchAlgorithmException&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;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JAVA_KEYSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;storeFileName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;openStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&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;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&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;store&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;cm&quot;&gt;/**
 * KeyManagers decide which authentication credentials (e.g. certs) should be sent to the remote
 * host for authentication during the SSL handshake.
 *
 * Server KeyManagers use their private keys during the key exchange algorithm and send
 * certificates corresponding to their public keys to the clients. The certificate comes from
 * the KeyStore.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getKeyManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&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;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;NoSuchAlgorithmException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnrecoverableKeyException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyStoreException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KeyManagerFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keyManagerFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;KeyManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDefaultAlgorithm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;keyManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&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;keyManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKeyManagers&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;cm&quot;&gt;/**
 * TrustManagers determine if the remote connection should be trusted or not.
 *
 * Clients will use certificates stored in their TrustStores to verify identities of servers.
 * Servers will use certificates stored in their TrustStores to verify identities of clients.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TrustManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getTrustManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NoSuchAlgorithmException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;KeyStoreException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;TrustManagerFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trustManagerFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;TrustManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TrustManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDefaultAlgorithm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;trustManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&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;trustManagerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTrustManagers&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;SSLContext&lt;/code&gt; is created and initialized like so:&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;cm&quot;&gt;/*
Create an SSLContext for the server using the server&apos;s JKS. This instructs the server to
present its certificate when clients connect over HTTPS.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createServerSSLContext&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;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storeFileName&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;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CertificateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NoSuchAlgorithmException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyStoreException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnrecoverableKeyException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;KeyManagementException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverKeyStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;storeFileName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KeyManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverKeyManagers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getKeyManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverKeyStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;TrustManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverTrustManagers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getTrustManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverKeyStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SSLContexts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;useProtocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TLS&quot;&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;span class=&quot;n&quot;&gt;sslContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverKeyManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverTrustManagers&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;SecureRandom&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;sslContext&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 following unit test shows making a HTTPS request to the local test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; instance and validating the server’s public certificate with the client’s truststore:&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;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&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;no&quot;&gt;ONE_WAY_SSL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// no client certificates&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;snaplogic&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toCharArray&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;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SERVER_KEYSTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ssl/server_keystore.jks&quot;&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;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CLIENT_TRUSTSTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ssl/client_truststore.jks&quot;&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;nc&quot;&gt;CloseableHttpClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Before&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;setUp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpClients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createDefault&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;@Test&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;httpsRequest_With1WaySSLAndValidatingCertsAndClientTrustStore_Returns200OK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverSSLContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;createServerSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SERVER_KEYSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&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;HttpServer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createLocalTestServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ONE_WAY_SSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getBaseUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// The server certificate was imported into the client&apos;s TrustStore (using keytool -import)&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientTrustStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_TRUSTSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&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;nf&quot;&gt;SSLContextBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;loadTrustMaterial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;clientTrustStore&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;TrustSelfSignedStrategy&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;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpClients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sslContext&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;span class=&quot;cm&quot;&gt;/*
    The HTTP client will now validate the server&apos;s presented certificate using its TrustStore.
     Since the cert was imported to the client&apos;s TrustStore explicitly (see above), the
     certificate will validate and the request will succeed
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;HttpResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&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;nf&quot;&gt;HttpGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/echo/this&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatusLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatusCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&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;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&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;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getBaseUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpServer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&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;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInetAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getHostName&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;s&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLocalPort&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 above unit test is included in the &lt;a href=&quot;https://github.com/robinhowlett/everything-ssl&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;everything-ssl&lt;/code&gt; GitHub project&lt;/a&gt;, along with the following (which are useful to see the  behavior when the SSL handshake fails, when server certificate validation is bypassed, malformed contexts etc.)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute_WithNoScheme_ThrowsClientProtocolExceptionInvalidHostname&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpRequest_Returns200OK&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_WithNoSSLContext_ThrowsSSLExceptionPlaintextConnection&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_With1WaySSLAndValidatingCertsButNoClientTrustStore_ThrowsSSLException&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_With1WaySSLAndTrustingAllCertsButNoClientTrustStore_Returns200OK&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_With1WaySSLAndValidatingCertsAndClientTrustStore_Returns200OK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;two-way-ssl-client-certificates&quot;&gt;Two-Way SSL (Client Certificates)&lt;/h4&gt;

&lt;p&gt;To configure two-way SSL we have to create the server’s truststore and create the client’s keystore.&lt;/p&gt;

&lt;p&gt;Since the client’s certificate is signed by a CA we created ourselves, import the CA cert into the server truststore:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -import -v -trustcacerts -keystore server_truststore.jks -storepass snaplogic -file /etc/apache2/ssl/cacert.pem -alias cacert
Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: e4e00ed07233a969
Valid from: Tue Oct 06 15:14:51 EDT 2015 until: Wed Oct 05 15:14:51 EDT 2016
Certificate fingerprints:
	 MD5:  F3:5E:28:E4:28:47:F2:EC:82:E2:BD:16:31:DC:90:02
	 SHA1: 6F:0F:49:BA:A9:30:01:E9:4C:60:B3:A1:85:7D:BB:C6:79:1F:41:7B
	 SHA256: A7:9D:25:E4:A6:34:8A:A3:5B:9A:CD:F3:62:D0:D8:2F:6A:A0:71:6A:6D:19:F3:04:A1:FD:BC:FB:21:40:DE:A1
	 Signature algorithm name: SHA512withRSA
	 Version: 3

Extensions: 

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
[CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US]
SerialNumber: [    e4e00ed0 7233a969]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
]

Trust this certificate? [no]:  yes
Certificate was added to keystore
[Storing server_truststore.jks]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Viewing the server truststore will show the CA’s certificate:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -list -v -keystore server_truststore.jks 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: cacert
Creation date: Jan 5, 2016
Entry type: trustedCertEntry

Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: e4e00ed07233a969
Valid from: Tue Oct 06 15:14:51 EDT 2015 until: Wed Oct 05 15:14:51 EDT 2016
Certificate fingerprints:
	 MD5:  F3:5E:28:E4:28:47:F2:EC:82:E2:BD:16:31:DC:90:02
	 SHA1: 6F:0F:49:BA:A9:30:01:E9:4C:60:B3:A1:85:7D:BB:C6:79:1F:41:7B
	 SHA256: A7:9D:25:E4:A6:34:8A:A3:5B:9A:CD:F3:62:D0:D8:2F:6A:A0:71:6A:6D:19:F3:04:A1:FD:BC:FB:21:40:DE:A1
	 Signature algorithm name: SHA512withRSA
	 Version: 3

Extensions: 

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
[CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US]
SerialNumber: [    e4e00ed0 7233a969]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
]



*******************************************
*******************************************
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, the client keystore stores the client certificate that will presented to the server for SSL authentication. Import the cert from client’s PKCS12 file (created above):&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -importkeystore -srckeystore /etc/apache2/ssl/client-cert.p12 -srcstoretype pkcs12 -destkeystore client_keystore.jks -deststoretype jks -deststorepass snaplogic
Enter source keystore password:  
Entry for alias client successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Viewing the created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_keystore.jks&lt;/code&gt; file will show the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client&lt;/code&gt; entry in the keystore:&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;| rhowlett@SL-MBP-RHOWLETT.local:~/dev/robinhowlett/github/everything-ssl/src/main/resources/ssl 
| =&amp;gt; keytool -list -v -keystore client_keystore.jks 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: client
Creation date: Jan 4, 2016
Entry type: PrivateKeyEntry
Certificate chain length: 2
Certificate[1]:
Owner: CN=client, OU=SnapTeam, O=SnapLogic, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: 100002
Valid from: Tue Oct 06 13:15:41 MDT 2015 until: Wed Oct 05 13:15:41 MDT 2016
Certificate fingerprints:
	 MD5:  F1:EF:60:64:48:DC:9B:C1:92:37:61:90:ED:48:01:1C
	 SHA1: C5:4B:1C:EF:85:C1:8C:5A:AA:74:54:49:F0:B5:97:F1:EC:34:49:6F
	 SHA256: B0:00:E4:C1:AE:03:92:95:9C:A2:BB:DB:13:3A:B6:38:BE:B4:BF:04:D0:72:41:6D:62:A6:93:D0:46:7E:3C:97
	 Signature algorithm name: SHA512withRSA
	 Version: 1
Certificate[2]:
Owner: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Issuer: CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US
Serial number: e4e00ed07233a969
Valid from: Tue Oct 06 13:14:51 MDT 2015 until: Wed Oct 05 13:14:51 MDT 2016
Certificate fingerprints:
	 MD5:  F3:5E:28:E4:28:47:F2:EC:82:E2:BD:16:31:DC:90:02
	 SHA1: 6F:0F:49:BA:A9:30:01:E9:4C:60:B3:A1:85:7D:BB:C6:79:1F:41:7B
	 SHA256: A7:9D:25:E4:A6:34:8A:A3:5B:9A:CD:F3:62:D0:D8:2F:6A:A0:71:6A:6D:19:F3:04:A1:FD:BC:FB:21:40:DE:A1
	 Signature algorithm name: SHA512withRSA
	 Version: 3

Extensions: 

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
[CN=localhost, OU=SnapTeam, O=SnapLogic, L=Boulder, ST=Colorado, C=US]
SerialNumber: [    e4e00ed0 7233a969]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 03 09 12 6E 8B DD 7A 80   FB F5 21 AB 75 D9 B8 49  ...n..z...!.u..I
0010: 79 5B 61 1F                                        y[a.
]
]



*******************************************
*******************************************
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;So, in summary, the server will present the certificate in its keystore to the client. The client will use its truststore to validate the server’s certificate. The client will present its certificate in its keystore to the server, and the server will validate the client certificate’s chain using the CA certificate in the server’s truststore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServer&lt;/code&gt; instance can now be created with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forceSSLAuth&lt;/code&gt; parameter set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; (see the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TWO_WAY_SSL&lt;/code&gt; boolean) which will require client certificates. The client’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLContext&lt;/code&gt; now has both the client truststore and keystore loaded:&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;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&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;no&quot;&gt;TWO_WAY_SSL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// client certificates mandatory&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Test&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;httpsRequest_With2WaySSLAndHasValidKeyStoreAndTrustStore_Returns200OK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverSSLContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;createServerSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SERVER_KEYSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&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;HttpServer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createLocalTestServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TWO_WAY_SSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getBaseUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientTrustStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_TRUSTSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KeyStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientKeyStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_KEYSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&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;nf&quot;&gt;SSLContextBuilder&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;loadTrustMaterial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientTrustStore&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;TrustSelfSignedStrategy&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;loadKeyMaterial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientKeyStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYPASS_AND_STOREPASS_VALUE&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;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpClients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setSSLContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sslContext&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;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;CloseableHttpResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpclient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&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;nf&quot;&gt;HttpGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/echo/this&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatusLine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStatusCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&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;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stop&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;Once again, the above unit test is included in the &lt;a href=&quot;https://github.com/robinhowlett/everything-ssl&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;everything-ssl&lt;/code&gt; GitHub project&lt;/a&gt;, along with the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_With2WaySSLAndUnknownClientCert_ThrowsSSLExceptionBadCertificate&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpsRequest_With2WaySSLButNoClientKeyStore_ThrowsSSLExceptionBadCertificate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;two-way-ssl-authentication-with-spring-boot-embedded-tomcat-and-resttemplate&quot;&gt;Two-Way SSL Authentication with Spring Boot, embedded Tomcat and RestTemplate&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://projects.spring.io/spring-boot/&quot;&gt;Spring Boot&lt;/a&gt; “takes an opinionated view of building production-ready Spring (Java) applications”. Spring Boot’s &lt;a href=&quot;http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-starter-poms&quot;&gt;Starter POMs&lt;/a&gt; and &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html&quot;&gt;auto-configuration&lt;/a&gt; make it quite easy to get going:&lt;/p&gt;

&lt;p&gt;``` xml pom.xml
&amp;lt;?xml version=”1.0” encoding=”UTF-8”?&amp;gt;&lt;/p&gt;
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
	&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;1.3.1.RELEASE&lt;/version&gt;
        &lt;relativePath /&gt; &lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;

	&lt;groupId&gt;com.robinhowlett&lt;/groupId&gt;
	&lt;artifactId&gt;everything-ssl&lt;/artifactId&gt;
	&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
	&lt;packaging&gt;jar&lt;/packaging&gt;

	&lt;name&gt;everything-ssl&lt;/name&gt;
	&lt;description&gt;Spring Boot and SSL&lt;/description&gt;

	&lt;properties&gt;
		&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
		&lt;java.version&gt;1.8&lt;/java.version&gt;

		&lt;commons-io-version&gt;2.4&lt;/commons-io-version&gt;
	&lt;/properties&gt;

	&lt;dependencies&gt;
		&lt;dependency&gt;
			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
			&lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
		&lt;/dependency&gt;
		&lt;dependency&gt;
			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
			&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
		&lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
            &lt;artifactId&gt;httpclient&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;commons-io&lt;/groupId&gt;
            &lt;artifactId&gt;commons-io&lt;/artifactId&gt;
            &lt;version&gt;${commons-io-version}&lt;/version&gt;
        &lt;/dependency&gt;
		
		&lt;dependency&gt;
			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
			&lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
			&lt;scope&gt;test&lt;/scope&gt;
		&lt;/dependency&gt;
	&lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-failsafe-plugin&lt;/artifactId&gt;
                &lt;version&gt;2.19&lt;/version&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;integration-test&lt;/goal&gt;
                            &lt;goal&gt;verify&lt;/goal&gt;
                        &lt;/goals&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

&lt;/project&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;
The Spring Boot documentation [describes the properties required to configure SSL](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html#howto-configure-ssl). I wanted however to support both HTTP and HTTPS for testing purposes, so an explicit SSL Connector for the embedded Tomcat container needed to be created:

``` java Config.java

/**
 * Configure embedded Tomcat and SSL connectors
 */
@Configuration
public class Config {

    @Autowired
    private Environment env;

    // Embedded Tomcat with HTTP and HTTPS support
    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory tomcat = new
                TomcatEmbeddedServletContainerFactory();
        tomcat.addAdditionalTomcatConnectors(createSSLConnector());
        return tomcat;
    }

    // Creates an SSL connector, sets two-way SSL, key- and trust stores, passwords, ports etc.
    protected Connector createSSLConnector() {
        Connector connector = new Connector(Http11Protocol.class.getCanonicalName());
        Http11Protocol protocol = (Http11Protocol) connector.getProtocolHandler();

        File keyStore = null;
        File trustStore = null;

        try {
            keyStore = getKeyStoreFile();
        } catch (IOException e) {
            throw new IllegalStateException(&quot;Cannot access keyStore: [&quot; + keyStore + &quot;] or &quot; +
                    &quot;trustStore: [&quot; + trustStore + &quot;]&quot;, e);
        }

        trustStore = keyStore;

        connector.setPort(env.getRequiredProperty(&quot;ssl.port&quot;, Integer.class));
        connector.setScheme(env.getRequiredProperty(&quot;ssl.scheme&quot;));
        connector.setSecure(env.getRequiredProperty(&quot;ssl.secure&quot;, Boolean.class));

        protocol.setClientAuth(env.getRequiredProperty(&quot;ssl.client-auth&quot;));
        protocol.setSSLEnabled(env.getRequiredProperty(&quot;ssl.enabled&quot;, Boolean.class));

        protocol.setKeyPass(env.getRequiredProperty(&quot;ssl.key-password&quot;));
        protocol.setKeystoreFile(keyStore.getAbsolutePath());
        protocol.setKeystorePass(env.getRequiredProperty(&quot;ssl.store-password&quot;));
        protocol.setTruststoreFile(trustStore.getAbsolutePath());
        protocol.setTruststorePass(env.getRequiredProperty(&quot;ssl.store-password&quot;));
        protocol.setCiphers(env.getRequiredProperty(&quot;ssl.ciphers&quot;));

        return connector;
    }

    // support loading the JKS from the classpath (to get around Tomcat limitation)
    private File getKeyStoreFile() throws IOException {
        ClassPathResource resource = new ClassPathResource(env.getRequiredProperty(&quot;ssl.store&quot;));

        // Tomcat won&apos;t allow reading File from classpath so read as InputStream into temp File
        File jks = File.createTempFile(&quot;server_keystore&quot;, &quot;.jks&quot;);
        InputStream inputStream = resource.getInputStream();
        try {
            FileUtils.copyInputStreamToFile(inputStream, jks);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        return jks;
    }
}
&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;application.properties&lt;/code&gt; file then defines the required SSL properties:&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;ssl.port=8443
ssl.scheme=https
ssl.secure=true
ssl.client-auth=true
ssl.enabled=true
ssl.key-password=snaplogic
ssl.store=ssl/server_keystore.jks
ssl.store-password=snaplogic
ssl.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_SHA256,TLS_ECDHE_RSA_WITH_AES_128_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_SHA,TLS_ECDHE_RSA_WITH_AES_256_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_SHA384,TLS_ECDHE_RSA_WITH_AES_256_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_SHA,TLS_DHE_RSA_WITH_AES_128_SHA256,TLS_DHE_RSA_WITH_AES_128_SHA,TLS_DHE_DSS_WITH_AES_128_SHA256,TLS_DHE_RSA_WITH_AES_256_SHA256,TLS_DHE_DSS_WITH_AES_256_SHA,TLS_DHE_RSA_WITH_AES_256_SHA
&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;ssl.client-auth=true&lt;/code&gt; property enforces two-way SSL.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We are re-using the JKS files created earlier (above)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simple REST interface is defined to return JSON representations of Greeting instances:&lt;/p&gt;

&lt;p&gt;``` java GreetingController.java&lt;/p&gt;

&lt;p&gt;/**&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Just says hello
 */
@RestController
public class GreetingController {&lt;/p&gt;

    &lt;p&gt;private static final String template = “Hello, %s!”;&lt;/p&gt;

    &lt;p&gt;@RequestMapping(“/greeting”)
 public Greeting greet(
         @RequestParam(value = “name”, required = false, defaultValue = “World!”) String name) {
     return new Greeting(String.format(template, name));
 }&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;}&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;	
Spring Security auto-configuration will enable basic authentication on the REST endpoint by default, so let&apos;s switch it off:

``` java HttpSecurityConfig.java

/**
 * Disable default-enabled basic auth
 */
@EnableWebSecurity
public class HttpSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable();
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;integration-testing-ssl-authentication-with-springs-testresttemplate&quot;&gt;Integration Testing SSL Authentication with Spring’s TestRestTemplate&lt;/h3&gt;

&lt;p&gt;Spring Boot really is great - it was quite straightforward to write an integration test to demonstrate two-way SSL authentication with the application running on the embedded Tomcat container.&lt;/p&gt;

&lt;p&gt;First I created an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;integration-test.properties&lt;/code&gt; file to set the HTTPS port to be different than then main application (the HTTP port will be set to a random open port by an annotation on the test itself). The only contents of this file is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssl.port&lt;/code&gt; property. All the other properties will come from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application.properties&lt;/code&gt; file detailed above:&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;ssl.port=54321
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The integration test class has annotations that instruct it to run using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SpringJUnit4ClassRunner&lt;/code&gt;, the scan for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Configuration&lt;/code&gt; classes at the base package of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EverythingSSLApplication&lt;/code&gt; class, to choose a random available HTTP port with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@WebIntegrationTest&lt;/code&gt; annotation (which itself is a combination of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@IntegrationTest&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@WebAppConfiguration&lt;/code&gt; annotations), and finally the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;integration-test.properties&lt;/code&gt; file is denoted as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@TestPropertySource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;``` java ITEverythingSSL.java&lt;/p&gt;

&lt;p&gt;/**&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Integration test that uses the embedded Tomcat instance configured by this Spring Boot app
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = EverythingSSLApplication.class)
@WebIntegrationTest(randomPort = true)
@TestPropertySource(locations = “classpath:integration-test.properties”)
public class ITEverythingSSL {&lt;/p&gt;

    &lt;p&gt;public static final String CLIENT_TRUSTSTORE = “ssl/client_truststore.jks”;
 public static final String CLIENT_KEYSTORE = “ssl/client_keystore.jks”;&lt;/p&gt;

    &lt;p&gt;@Rule
 public ExpectedException thrown = ExpectedException.none();&lt;/p&gt;

    &lt;p&gt;@Value(“${local.server.port}”)
 private int port = 0;&lt;/p&gt;

    &lt;p&gt;@Value(“${ssl.port}”)
 private int sslPort = 0;&lt;/p&gt;

    &lt;p&gt;@Value(“${ssl.store-password}”)
 private String storePassword;&lt;/p&gt;

    &lt;p&gt;…
```&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first test confirms that plain HTTP is supported:&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;nd&quot;&gt;@Test&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;rest_OverPlainHttp_GetsExpectedResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Greeting&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&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;Greeting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, Robin!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&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;TestRestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Greeting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getForEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/greeting?name={name}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Greeting&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;span class=&quot;s&quot;&gt;&quot;Robin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;responseEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContent&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;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-rest-templates-test-utility&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestRestTemplate&lt;/code&gt;&lt;/a&gt; is “a convenience subclass of Spring’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RestTemplate&lt;/code&gt; that is useful in integration tests”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getRestTemplateForHTTPS&lt;/code&gt; method creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestRestTemplate&lt;/code&gt; instance with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSLContext&lt;/code&gt; set to support the client’s keystore and truststore:&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;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getRestTemplateForHTTPS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&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;nc&quot;&gt;SSLConnectionSocketFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connectionFactory&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;SSLConnectionSocketFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sslContext&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;nf&quot;&gt;DefaultHostnameVerifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;CloseableHttpClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;closeableHttpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;HttpClientBuilder&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;na&quot;&gt;setSSLSocketFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connectionFactory&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;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&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;TestRestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;HttpComponentsClientHttpRequestFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpRequestFactory&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;nc&quot;&gt;HttpComponentsClientHttpRequestFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRequestFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;httpRequestFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setHttpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;closeableHttpClient&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;template&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 test that then demonstrates two-way SSL is quite simple:&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;nd&quot;&gt;@Test&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;rest_WithTwoWaySSL_AuthenticatesAndGetsExpectedResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Greeting&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&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;Greeting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, Robin!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;SSLContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SSLContexts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;custom&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;loadKeyMaterial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_KEYSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storePassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toCharArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;storePassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toCharArray&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;loadTrustMaterial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLIENT_TRUSTSTORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storePassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toCharArray&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;nf&quot;&gt;TrustSelfSignedStrategy&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;useProtocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TLS&quot;&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;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getRestTemplateForHTTPS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sslContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Greeting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responseEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getForEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://localhost:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sslPort&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/greeting?name={name}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Greeting&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;span class=&quot;s&quot;&gt;&quot;Robin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;responseEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContent&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 following integration tests have also been included in the project:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rest_WithMissingClientCert_ThrowsSSLHandshakeExceptionBadCertificate&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rest_WithUntrustedServerCert_ThrowsSSLHandshakeExceptionUnableFindValidCertPath&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;The companion Spring Boot application &lt;a href=&quot;https://github.com/robinhowlett/everything-ssl&quot;&gt;is available on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;two-way-ssl-with-snaplogics-rest-snap&quot;&gt;Two-Way SSL with SnapLogic’s REST Snap&lt;/h2&gt;

&lt;p&gt;Naturally, SnapLogic’s REST Snap makes this all very easy:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://snaplogic.box.com/shared/static/xkv5tch8lk2k2jf1q0881ri6u0nio7a0.gif&quot; alt=&quot;SnapLogic REST Snap&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;thank-yous&quot;&gt;Thank Yous&lt;/h4&gt;

&lt;p&gt;Thank you to Ed Heneghan for correcting my initial mistakes.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 Jan 2016 23:17:53 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2016/01/05/everything-you-ever-wanted-to-know-about-ssl-but-were-afraid-to-ask/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2016/01/05/everything-you-ever-wanted-to-know-about-ssl-but-were-afraid-to-ask/</guid>
        
        <category>code,</category>
        
        <category>security</category>
        
        
      </item>
    
      <item>
        <title>Farewell SportsLabs, Hello SnapLogic</title>
        <description>&lt;p&gt;After close to 5 years with &lt;a href=&quot;http://sportslabs.com&quot;&gt;Silver Chalice/SportsLabs&lt;/a&gt;, today, Friday August 7th 2015, will be my last day with the company. On Monday, I join &lt;a href=&quot;http://www.snaplogic.com/&quot;&gt;SnapLogic&lt;/a&gt;, an enterprise integration platform-as-a-service (iPaaS) provider that I believe has an amazing future.&lt;/p&gt;

&lt;p&gt;It has been a tremendous, life-changing journey.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h4 id=&quot;silver-chalice&quot;&gt;Silver Chalice&lt;/h4&gt;

&lt;p&gt;I joined Silver Chalice shortly after marrying &lt;a href=&quot;http://sarahhowlett.com/&quot;&gt;my wife&lt;/a&gt; and moving to America from Ireland. I had completed a &lt;a href=&quot;http://files.ntra.com/content.aspx?type=news&amp;amp;id=46097&quot;&gt;small social app for the 2010 Breeders’ Cup&lt;/a&gt; and was finishing up my responsibilities as a server-side engineer for IBM, when I started looking for startups in the Denver/Boulder area that I could be a part of. I knew Boulder, with its &lt;a href=&quot;http://www.techstars.com/&quot;&gt;Techstars&lt;/a&gt; incubator, would be a great place to find fast-growing companies.&lt;/p&gt;

&lt;p&gt;Towards the end of 2010, I stumbled across a job posting for a web developer position at new media company that &lt;a href=&quot;http://www.sportsbusinessdaily.com/Journal/Issues/2009/10/20091026/This-Weeks-News/White-Sox-Form-Silver-Chalice-Ventures-For-Revenue-Lift.aspx&quot;&gt;was being spun off&lt;/a&gt; from the Chicago White Sox.&lt;/p&gt;

&lt;p&gt;Ignoring the fact my baseball knowledge at that point in time amounted to “it’s kind of like &lt;a href=&quot;https://en.wikipedia.org/wiki/Rounders&quot;&gt;Rounders&lt;/a&gt;”, and that I wasn’t a web developer, I was intrigued by a major sports franchise looking to invest in opportunities in the digital and new media space. So I applied. And heard nothing back.&lt;/p&gt;

&lt;p&gt;I followed up a while later and got their attention this time. Silver Chalice was really only a handful of people at that point, split between Chicago and Boulder. They had outsourced any previous client work and had no engineers on staff. I met the hiring manager and, over a burger, he described the need for a system that could power market-leading news, weather and sports apps. How it did that, he had no idea. We fleshed out, in detail, all the ways we didn’t know what to build, but we did realize that it definitely wasn’t a web developer they were looking for. So I took a leap of faith, joined and set to work on building a &lt;a href=&quot;http://sportslabs.com/platform/&quot;&gt;Platform&lt;/a&gt; and rapidly hiring a team - neither of which I’d ever done before but had always wanted to do.&lt;/p&gt;

&lt;p&gt;Within a couple of weeks I was on a plane to the headquarters of a &lt;a href=&quot;http://www.raycommedia.com/&quot;&gt;major media company&lt;/a&gt; with a slide deck and somewhat vague idea of how we (the engineering department of 1) were, in the next 4 months, going to build apps and mobile websites for 31 TV stations, and a platform to power them all, for the 1-million-plus active users they had with their legacy provider’s systems. I was grilled pretty hard by their CIO but I discovered I had a knack for spinning a good tale and making a sale - we won the deal.&lt;/p&gt;

&lt;p&gt;What followed in the next few months was a madcap scramble to hire great engineers, build a &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;cloud-hosted&lt;/a&gt;, scalable content delivery architecture, an integration engine to ingest the myriad of content needed for a modern news/weather/sports app, and to build the apps and websites themselves. As a fan of &lt;a href=&quot;http://www.startuplessonslearned.com/&quot;&gt;Eric Ries&lt;/a&gt;, I was a proponent of API-driven development and continuous deployment, and we quickly built a system that, driven by &lt;a href=&quot;https://aws.amazon.com/autoscaling/&quot;&gt;auto-scaling&lt;/a&gt;, workers scheduled and consumed jobs from &lt;a href=&quot;https://aws.amazon.com/sqs/&quot;&gt;AWS SQS&lt;/a&gt; queues, used the &lt;a href=&quot;http://camel.apache.org/&quot;&gt;Apache Camel&lt;/a&gt; integration framework to source and combine the information from dozens of data sources across many protocols, and wrote “API response” data to &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;AWS S3&lt;/a&gt; and &lt;a href=&quot;https://aws.amazon.com/cloudfront/&quot;&gt;Cloudfront&lt;/a&gt; for durable, reliable content delivery, even under bursty, high-traffic scenarios like major breaking news. We got the job done and the apps and sites launched on time and the client experienced a significant increase in active users, and being #1 in many of their markets.&lt;/p&gt;

&lt;p&gt;It wasn’t all plain sailing. My very first paycheck bounced - you can imagine how that made me feel about joining a startup! Luckily it turned out to be the bank’s error. I was working very long hours at this stage, trying to keep up with building the platform, interviewing for all new employees, identifying market opportunities and pitching new business, and setting out a strategic vision for our technology. And we were still working by the skin of our teeth - on launch weekend, a not-insignificant portion of the platform was running on my laptop.&lt;/p&gt;

&lt;h4 id=&quot;sportslabs&quot;&gt;SportsLabs&lt;/h4&gt;

&lt;p&gt;We took this platform into sports by powering CBS Interactive’s college sports apps. That’s when the company saw a major opportunity to do what Major League Baseball had done with &lt;a href=&quot;http://www.mlbam.com/&quot;&gt;MLB Advanced Media&lt;/a&gt; (BAM) but for college sports - create a “roll-up” platform that could serve the digital needs of &lt;a href=&quot;http://www.ukathletics.com/&quot;&gt;top-tier collegiate athletic institutions&lt;/a&gt; and &lt;a href=&quot;http://www.theacc.com/&quot;&gt;organizations&lt;/a&gt;, as well as &lt;a href=&quot;http://campusinsiders.com/&quot;&gt;independent sports media companies&lt;/a&gt;, sports media &lt;a href=&quot;http://www.auburntigers.com/genrel/082714aae.html&quot;&gt;rights holders&lt;/a&gt;, and &lt;a href=&quot;http://milk.samsung.com/&quot;&gt;global media powerhouses&lt;/a&gt;. The Boulder arm of Silver Chalice was rebranded as SportsLabs and we said goodbye to our news media friends and focused exclusively on the college sports vertical.&lt;/p&gt;

&lt;p&gt;Today, SportsLabs is a major player in the space, streaming live video of the &lt;a href=&quot;http://www.theacc.com/page/championship_m-baskbl&quot;&gt;ACC Basketball Championships&lt;/a&gt;, building &lt;a href=&quot;http://watchnd.tv/#!/&quot;&gt;Notre Dame’s WatchND&lt;/a&gt; media properties, powering the location-based services, social streams and live stats for &lt;a href=&quot;https://play.google.com/store/apps/developer?id=SportsLabs&quot;&gt;30+ IMG Gameday iOS and Android native apps&lt;/a&gt;, winning premium clients like the &lt;a href=&quot;http://www.ukathletics.com/&quot;&gt;University of Kentucky&lt;/a&gt; and &lt;a href=&quot;http://www.kstatesports.com/&quot;&gt;Kansas State University&lt;/a&gt;, and signing &lt;a href=&quot;http://milk.samsung.com/&quot;&gt;one of the largest sports audio streaming deals&lt;/a&gt; with Samsung.&lt;/p&gt;

&lt;p&gt;From the 5-person Boulder office I joined in 2011, SportsLabs has now 50+ employees in Boulder and Silver Chalice must have 150+ in Chicago. We moved into new, much larger offices right on beautiful Pearl St. and have built market-leading products.&lt;/p&gt;

&lt;p&gt;As with any fast-paced environment, it was difficult to just keep up. We naturally had to rewrite the platform for the API to be flexible enough to support all these products - so we brought in &lt;a href=&quot;https://spring.io/projects&quot;&gt;the Spring stack&lt;/a&gt; including Spring MVC, Spring Data etc., plus MongoDB, Postgres, and an RoR/AngularJS stack to power our CMS and websites. After working seemingly non-stop I definitely experienced burn-out a few times. Sports, like most live entertainment experiences, is skewed to take place at nights or weekends - especially the highest profile events. The stress of a busy November Saturday evening with hundreds of events being live scored and streamed to the general public on a national stage meant that I rarely got a break from even thinking about the platform and our products.&lt;/p&gt;

&lt;p&gt;We added more and more data providers and the integration challenge began to be significant. Trying to balance developing and coding the platform, plus managing the platform team, was tricky. Plus, as my future CEO &lt;a href=&quot;https://en.wikipedia.org/wiki/Gaurav_Dhillon&quot;&gt;Gaurav Dhillon&lt;/a&gt; so wisely put it, &lt;a href=&quot;https://www.youtube.com/watch?v=oQ-8NdP_dLE&quot;&gt;the importance of timing is “vital”&lt;/a&gt;. While we have definitely seen the market move to “cutting the cord” and consuming content online (OTT solutions like HBO Now and SlingTV are super promising), sports on TV is still a great experience and, in many ways, the reason why TV, especially cable, has been able to fight off these challenges from far more flexible online properties. ESPN especially shapes the college football broadcast world, both in terms of distribution and money. And the broadcast rights for the big games are everything. &lt;a href=&quot;http://sports.yahoo.com/blogs/nfl-shutdown-corner/yahoo-will-broadcast-first-live-stream-nfl-game-on-oct--25-151500015.html&quot;&gt;Yahoo live streaming an NFL game&lt;/a&gt; this year is a gamechanger - with the worst part of it being it’s a Bills-Jaguars game.&lt;/p&gt;

&lt;p&gt;It really does feel that sports broadcasting online is going to go through some major shifts in the next few years - it’s just a little behind where I’d thought it would be now back in 2011. Either way, SportsLabs has marvelous opportunities ahead of it, working with world-famous clients in one of the most interesting media/technology spaces out there. They’ll do great things.&lt;/p&gt;

&lt;h4 id=&quot;snaplogic&quot;&gt;SnapLogic&lt;/h4&gt;

&lt;p&gt;In my time with Silver Chalice/SportsLabs I experienced a few themes that I think are going to be incredibly commonplace for businesses in the near future.&lt;/p&gt;

&lt;p&gt;Firstly, Silver Chalice was a new company and to get up and running quickly, we used SaaS heavily - both to build our products and to simply operate ourselves. From the usual suspects like Google Apps, Dropbox, Paylocity, and Concur, to the plethora of services we use in Engineering including Splunk, New Relic, PagerDuty, GitHub, Google Analytics, DFP, Atlassian JIRA Studio, and of course Amazon Web Services. While some of these services talk to one or two of the others, there wasn’t a way to easily move information between all of them.&lt;/p&gt;

&lt;p&gt;So much communication was lost between wiki pages defining product requirements, ticket systems detailing bugs, email and chat messages clarifying queries, continuous integration systems outlining test failures, logs, monitoring systems highlighting anomalies etc.! In fact, in my opinion, this largely technical limitation of sharing basic information across systems was costing us millions in time and effort wasted. And we as platform engineers were exacerbating the problem by trying to make data sources talk to each other in non-consistent ways.&lt;/p&gt;

&lt;p&gt;Secondly, with Camel we started with an ESB-like solution for integrating data and, while it worked quite well, we got sucked into a fairly tightly coupled architecture, even though it supported using a variety of ways to decouple parts of the architecture. We eventually moved to a JSON-based REST API solution that offered increased flexibility, but we strongly typed far too many of the contracts and integration boundaries. As the number of data sources increased, the complexity of trying to integrate them quickly into the platform, and scale them appropriately, increased also. Not having a GUI like the &lt;a href=&quot;http://www.snaplogic.com/features/snaplogic-designer&quot;&gt;SnapLogic Designer&lt;/a&gt; that described the integrations, meant that a significant portion of developers’ time was spent just understanding how the data would flow through the system. The code-only solution also limited the number of people that were able to help understand and document the flows too. Maintaining the flow descriptions in wikis etc. was both too time consuming for the (expensive) developers and too static to be relied upon.&lt;/p&gt;

&lt;p&gt;Thirdly, we were only scratching the surface with the amount of data becoming available to us. In the past, live sports might have been described as having “a lot of data”, but it pales in comparison to the volume of information now accessible through app analytics of user actions, streaming metadata from live broadcasts, social engagement with players and teams, location-based services at stadiums, and so on. Designing our platform to incorporate social, mobile, analytical, cloud and Internet of Things (IoT) data to further customize, operate, and gain insights into our products would require a major engineering effort.&lt;/p&gt;

&lt;p&gt;So when I joined our neighborhood website recently, I was amazed that the first post I saw was from &lt;a href=&quot;https://www.linkedin.com/profile/view?id=160214504&quot;&gt;a neighbor, Kevin Hahn&lt;/a&gt;, who lived just a couple of doors down, describing opportunities with a company named SnapLogic, who had just opened an office in Boulder. A quick perusal of &lt;a href=&quot;snaplogic.com&quot;&gt;snaplogic.com&lt;/a&gt; piqued my interest further. Watching &lt;a href=&quot;https://www.youtube.com/results?search_query=gaurav+dhillon&quot;&gt;fascinating interviews with SnapLogic CEO and Informatica co-founder Gaurav Dhillon&lt;/a&gt; and reading &lt;a href=&quot;https://twitter.com/dcunni&quot;&gt;Darren Cunningham’s&lt;/a&gt; excellent &lt;a href=&quot;http://www.snaplogic.com/blog/&quot;&gt;blog posts&lt;/a&gt; whetted my appetite even more.&lt;/p&gt;

&lt;p&gt;After speaking with Kevin, I made it my business to become part of SnapLogic. I can’t wait to start. It’s wonderfully exciting being part of a something that I believe is going to be huge - both in its success and its impact.&lt;/p&gt;

&lt;h4 id=&quot;reflections&quot;&gt;Reflections&lt;/h4&gt;

&lt;p&gt;I read an article where Gaurav said &lt;a href=&quot;http://www.businessinsider.com/guarav-dhillon-snaplogic-and-informatica-cofounder-2015-4&quot;&gt;“Leaving [Informatica] was the hardest and best thing I ever did”&lt;/a&gt;. I have similar feelings leaving SportsLabs and joining SnapLogic. Naturally, the team I’m leaving behind weighs on me the most.&lt;/p&gt;

&lt;p&gt;I can’t thank &lt;a href=&quot;https://www.linkedin.com/in/johnburris&quot;&gt;John Burris&lt;/a&gt; enough for the opportunity he gave me, how he looked out for me and my family, and the introduction to this startup world that I don’t want to ever leave. I’m very proud to be part of your team that built this company.&lt;/p&gt;

&lt;p&gt;I have a deep gratitude in having worked beside &lt;a href=&quot;https://www.linkedin.com/profile/view?id=452472&quot;&gt;Nancy Beaton&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/profile/view?id=AAEAAABZTdEBbfeEW-rr0IM8dUi6_8JmsCd-rq8&quot;&gt;Chris Jackson&lt;/a&gt;. Chris has an amazing talent for making a connection with clients. Nancy is one of the most impressive people I have ever had the pleasure of meeting. I’ll never forget the time we travelled to what we thought was a small, getting-to-know-you meeting with Samsung and instead entered a conference room with about 25 people, and standing room only, to listen to our pitch. I was amazed with the technical acumen displayed as she deftly took the audience through the finer points of streaming live audio, while also being responsible for securing one of the most lucrative contracts our space has ever had - a superstar.&lt;/p&gt;

&lt;p&gt;As for other terrific Silver Chalice/SportsLabs employees, both former and present, there are simply too many to for me to remember them all, but I do want to thank Jason Gall, Trey Hicks, Eric Foote, Colin Holm, Tim Gray, Kevin Gorham, Jonathan Kreuger, Arlie Sisson, Peter Laird, Nick Cassidy, Mike Olson, Colin Pilloud, Rebecca Cameron, Ryan Pensy, Dave Wiedenheft, Brian Grayless, Sean Ortiz, and Ryan Karlin for the help and support they gave me over the years. I’m sure I’ve left plenty out that deserve individual recognition too.&lt;/p&gt;

&lt;p&gt;I joined Silver Chalice/SportsLabs fresh off the boat (so to speak). I was newly married, living a new country where I didn’t know anyone, and not really sure what the future held. My experience completely opened up a new world for me - professionally and personally. My home is Boulder now, where my children are growing up. I gained confidence to pursue ambitious opportunities and engineer things that were worth something and made an impact in the market. I gained friendships that I hope will last a lifetime.&lt;/p&gt;

&lt;p&gt;I’ve enjoyed every moment.&lt;/p&gt;
</description>
        <pubDate>Fri, 07 Aug 2015 04:13:17 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/08/07/farewell-sportslabs/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/08/07/farewell-sportslabs/</guid>
        
        <category>career</category>
        
        <category>snaplogic</category>
        
        
      </item>
    
      <item>
        <title>Spring Social Bootstrap: Create REST API SDKs and CLIs that can Record and Replay HTTP requests</title>
        <description>&lt;p&gt;I joined &lt;a href=&quot;http://sportslabs.com&quot;&gt;SportsLabs&lt;/a&gt; (then still under the &lt;a href=&quot;http://www.silverchalice.com/&quot;&gt;Silver Chalice&lt;/a&gt; brand) way back &lt;a href=&quot;https://www.linkedin.com/in/robinhowlett&quot;&gt;in 2011&lt;/a&gt; as one of its earliest employees and the first engineer.&lt;/p&gt;

&lt;p&gt;We started work on envisioning and building the &lt;a href=&quot;http://sportslabs.com/platform/&quot;&gt;Advanced Media Platform&lt;/a&gt; - a system to ingest, process, transform, distribute, and stream sports, news, social, and media content to create market leading mobile, web, and social products for clients such as &lt;a href=&quot;http://milk.samsung.com/&quot;&gt;Samsung&lt;/a&gt;, the &lt;a href=&quot;http://watchnd.tv/#!/&quot;&gt;University of Notre Dame&lt;/a&gt;, the &lt;a href=&quot;http://www.theacc.com/&quot;&gt;ACC&lt;/a&gt;, the &lt;a href=&quot;http://www.collegefootballplayoff.com/&quot;&gt;College Football Playoff&lt;/a&gt;, &lt;a href=&quot;https://play.google.com/store/apps/developer?id=SportsLabs&amp;amp;hl=en&quot;&gt;IMG College&lt;/a&gt;, the &lt;a href=&quot;http://www.themw.com/&quot;&gt;Mountain West&lt;/a&gt; and &lt;a href=&quot;http://campusinsiders.com/&quot;&gt;Campus Insiders&lt;/a&gt;, among others.&lt;/p&gt;

&lt;p&gt;Since then, SportsLabs has consumed data from dozens of sources including &lt;a href=&quot;http://www.stats.com/&quot;&gt;STATS LLC&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt;, and &lt;a href=&quot;http://www.ooyala.com/&quot;&gt;Ooyala&lt;/a&gt;, but also from proprietary systems that were never foreseen as integration points.&lt;/p&gt;

&lt;p&gt;Data providers’ APIs use combinations of JSON, XML and/or CSV. Some are spec-compliant, others are not. Some rely heavily on query parameters, while others favor HTTP headers. Some API providers use &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth 2.0&lt;/a&gt; plus API rate limits, while others have rolled their own security solutions. Some integrations were with partners willing to work with us on evolving their web services. Others were with competitors who were not motivated to make things easy.&lt;/p&gt;

&lt;p&gt;This plethora of ways to configure, consume, learn from, and integrate with APIs led us to create &lt;a href=&quot;https://github.com/robinhowlett/spring-social-bootstrap&quot;&gt;Spring Social Bootstrap&lt;/a&gt;, a family of projects intended to aid creating and managing API clients for many of the above scenarios.&lt;/p&gt;

&lt;p&gt;Spring Social Bootstrap is comprised of the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/robinhowlett/spring-social-bootstrap/tree/master/spring-social-bootstrap-sdk&quot;&gt;Spring Social Bootstrap SDK&lt;/a&gt;&lt;/strong&gt;: a &lt;a href=&quot;http://projects.spring.io/spring-social&quot;&gt;Spring Social&lt;/a&gt;-based framework for rapidly and consistently developing API clients&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/robinhowlett/spring-social-bootstrap/tree/master/bootstrap-shell&quot;&gt;Bootstrap Shell&lt;/a&gt;&lt;/strong&gt;: a &lt;a href=&quot;http://docs.spring.io/spring-shell/docs/current/reference/htmlsingle/&quot;&gt;Spring Shell&lt;/a&gt;-based framework to aid creating command-line interface (CLI) applications for API clients built on Spring Social Bootstrap SDK&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/robinhowlett/spring-social-bootstrap/tree/master/har-mar-interceptor&quot;&gt;HAR Mar Interceptor&lt;/a&gt;&lt;/strong&gt;: allows capturing HTTP request/response exchanges, persisting them in a &lt;a href=&quot;http://www.softwareishard.com/blog/har-12-spec/&quot;&gt;HAR&lt;/a&gt; or &lt;a href=&quot;https://github.com/Mashape/api-log-format&quot;&gt;ALF&lt;/a&gt;-compatible format, and re-executing those requests at a later time.&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more --&gt;

&lt;p align=&quot;center&quot;&gt;![Spring Social Bootstrap SDK](http://i.imgur.com/asX8yGM.jpg)

**Spring Social Bootstrap SDK** is a simple [Spring Social](http://projects.spring.io/spring-social)-based framework for rapidly and consistently developing API clients.

It combines the strengths of Spring Social, such as [native support for OAuth-based service providers](http://docs.spring.io/spring-social/docs/current/reference/htmlsingle/#connectFramework) (including support for OAuth 1 and OAuth 2), with consistent and well-defined extension points for new API clients.

Spring Social, despite its name, has no requirement on integrations being only with social networks. In truth, its primarily a set of interfaces for predictable implementations of API clients - which is our goal too.

Spring Social Bootstrap SDK provides CRUD and query capabilities out-of-the-box e.g.

```java
testApi.testOperations().create(testBaseApiResource);
testApi.testOperations().get(testBaseApiResource.getId());
testApi.testOperations().update(testBaseApiResource);
testApi.testOperations().delete(testBaseApiResource.getId());
testApi.testOperations().query();
```

as well as guidelines on how to create developer-friendly features like query builders e.g.

```java
testApi.testOperations().qb().withPaging(1, 25, &quot;name&quot;, Sort.Direction.ASC).query();
```

I&apos;d advise taking a look at the [Spring Social Bootstrap SDK README](https://github.com/robinhowlett/spring-social-bootstrap/blob/master/spring-social-bootstrap-sdk/README.md) to learn more on how to create API clients based on Spring Social Bootstrap SDK.

&lt;p&gt;
---

&lt;p align=&quot;center&quot;&gt;![Bootstrap Shell](http://i.imgur.com/PypWRjx.png)

**Bootstrap Shell** is a [Spring Shell](http://docs.spring.io/spring-shell/docs/current/reference/htmlsingle/)-based framework to aid creating command-line interface (CLI) applications for API clients built on [Spring Social Bootstrap SDK](https://github.com/robinhowlett/spring-social-bootstrap/tree/master/spring-social-bootstrap-sdk).

When developing API clients, SportsLabs learned that is worth leveraging the clients in data monitoring, QA, testing, and simulation scenarios. As such, we created a small CLI framework to allow highly interactive command-line applications as well as taking advantage of the power of command line tools, such as [pipes](http://man7.org/linux/man-pages/man2/pipe.2.html), [`xargs`](http://unixhelp.ed.ac.uk/CGI/man-cgi?xargs), [`jq`](http://stedolan.github.io/jq/), and even opening the browser to online side-by-side diff tools like [mergely](http://www.mergely.com/editor?lhs=http://mockbin.com/bin/800a818b-5fb6-40d4-a342-75a1fb8599db/view&amp;amp;rhs=http://mockbin.com/bin/3c149e20-bc9c-4c68-8614-048e6023a108/view).

We also liked Spring Shell&apos;s Spring-based interactive shell, but the XML-only configuration, the `toString()`-based command outputs and lack of menu-style multi-step support required a fork of the project to add the following:

* [SHL-106: Java Configuration support](https://github.com/spring-projects/spring-shell/pull/66): Permits configuring a Spring Shell application using Java `@Configuration` classes instead of XML ([JIRA](https://jira.spring.io/browse/SHL-106))
* [SHL-174: Multi-Step Commands](https://github.com/spring-projects/spring-shell/pull/67): Permits commands annotated with `@CliStepIndicator` to perform additional logic during its execution, including accepting user input e.g. pagination instructions ([JIRA](https://jira.spring.io/browse/SHL-174))
* [SHL-175: Multiple Output Formats for Commands](https://github.com/spring-projects/spring-shell/pull/68): Permits command results to be printed to the console with different formats by using Spring Type Converters, denoted by `@CliPrinter` annotations on Command parameters ([JIRA](https://jira.spring.io/browse/SHL-175))

The result was a handy framework for developers and QA engineers to quickly start using our API clients in a variety of situations.

_e.g. [Mockbin CLI](https://github.com/robinhowlett/mockbin-cli): an Bootstrap Shell-based CLI application for [Mockbin](http://mockbin.com/) powered by [Spring Social Mockbin](https://github.com/robinhowlett/spring-social-mockbin)_
![CLI Example](http://i.imgur.com/8Eca4p3.gif)

Again, check out the [Bootstrap Shell README](https://github.com/robinhowlett/spring-social-bootstrap/blob/master/bootstrap-shell/README.md) for more information.

&lt;p&gt;
---

&lt;p align=&quot;center&quot;&gt;![HAR Mar Interceptor](http://i.imgur.com/oaqfyPi.png)

**HAR Mar Interceptor** allows capturing HTTP request/response exchanges, persisting them in a [HTTP Archive (HAR)](http://www.softwareishard.com/blog/har-12-spec/) or [API Log Format (ALF)](https://github.com/Mashape/api-log-format)-compatible format, and re-executing those requests at a later time.

Often one of the challenging things about creating clients for proprietary APIs is the lack of documentation or samples available to learn from. Also, some API providers can be very strict with rate limits and test API endpoints are rarely provided.

In Sports, this issue is complicated further by the time sensitive nature of the domain - events rarely last longer than a couple of hours and every so often rare scenarios like weather-related postponements, data-entry errors and [22 inning baseball games](http://www.ncaa.com/news/baseball/article/2014-06-01/tcu-wins-22-innings-second-longest-game-ncaa-tourney-history) create challenges for how well our products work for users.

Early on in SportsLabs&apos; life, we created our own system for storing the request history of particular API endpoints, but I discovered the [HTTP Archive (HAR)](http://www.softwareishard.com/blog/har-12-spec/) spec, created by [@JanOdvarko](https://twitter.com/janodvarko), lead of the [Firebug](http://www.getfirebug.com/) project. 

If you are familar with Firebug you&apos;ll know you can see the requests the browser is making for a particular page and the time taken for each response to be received:

![Firebug](http://core0.staticworld.net/images/idge/imported/article/itw/2013/11/22/netpanel-100521990-orig.png)

[Mashape](https://www.mashape.com/) [API Log Format (ALF)](https://github.com/Mashape/api-log-format) is based on HAR and is particularly designed for API consumer use cases.

HAR Mar Interceptor ships with a customized Spring [`ClientHttpRequestInterceptor`](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestInterceptor.html) for capturing HTTP request/response exchanges and storing them in ALF to a JDBC database:

![HAR Table](http://i.imgur.com/siNRX66.png)

The persisted data can be loaded into an `AlfHar` POJO and represented in JSON using [Jackson](http://wiki.fasterxml.com/JacksonHome). This JSON can even be stored in a `.har` file and loaded into HAR-compatible tools like [Charles Web Debugging Proxy](http://www.charlesproxy.com/).

It also provides `ReplayAlfHarTemplate` to replay entries in the HAR log at either real-time, fixed or immediate intervals.

This allows us to capture what our systems are requesting and/or responding under certain conditions, replay system inputs and/or outputs for debugging, plus design and tweak scenarios based on real data.

It also aids us when integrating with new data providers lacking documentation, as we can learn from the archive log and, for those who wish to limit testing efforts against their infrastructure, use services like Mashape&apos;s [Mockbin](http://mockbin.com/) to mock their endpoints with observed data - using [Spring Social Mockbin](https://github.com/robinhowlett/spring-social-mockbin), naturally.
&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 26 Apr 2015 22:50:33 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/04/26/spring-social-bootstrap-create-rest-api-sdks-and-clis-that-can-record-and-replay-http-requests/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/04/26/spring-social-bootstrap-create-rest-api-sdks-and-clis-that-can-record-and-replay-http-requests/</guid>
        
        <category>code</category>
        
        <category>spring</category>
        
        
      </item>
    
      <item>
        <title>Supporting Multi-Step Commands with Spring Shell</title>
        <description>&lt;p&gt;Out of the box, &lt;a href=&quot;http://docs.spring.io/spring-shell/docs/current/reference/htmlsingle/&quot;&gt;Spring Shell&lt;/a&gt; supports printing command results to the terminal in a fairly basic way.&lt;/p&gt;

&lt;p&gt;Spring Shell also provides the &lt;a href=&quot;http://docs.spring.io/spring-shell/docs/current/api/org/springframework/shell/core/ExecutionProcessor.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExecutionProcessor&lt;/code&gt;&lt;/a&gt; interface, allowing a “command provider to be called in a generic fashion just before, and right after, executing a command”.&lt;/p&gt;

&lt;p&gt;The interface defines three lifecycle events that can be intercepted:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;before a command has been invoked&lt;/li&gt;
  &lt;li&gt;after an invocation has been returned&lt;/li&gt;
  &lt;li&gt;after an exception was thrown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was interested in hooking into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;afterReturningInvocation&lt;/code&gt; to provide “step logic” - potentially allowing user or system input to execute additional logic based on the result of the initial command result (and/or each step result) e.g. paging backwards or forwards on the command line through lists of data.&lt;/p&gt;

&lt;p&gt;I was able to achieve this and opened &lt;a href=&quot;https://jira.spring.io/browse/SHL-174&quot;&gt;a JIRA ticket&lt;/a&gt; and the following pull request on Spring Shell’s GitHub repo: &lt;a href=&quot;https://github.com/spring-projects/spring-shell/pull/67&quot;&gt;SHL-174: Multi-Step Commands #67&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;When writing command execution results to the terminal, Spring Shell’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbstractShell&lt;/code&gt; examines the instance being returned by the command and logs at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INFO&lt;/code&gt; level the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toString()&lt;/code&gt; output of the result object. If the result object is an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Iterable&lt;/code&gt;, it iterates over the collection and logs the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toString()&lt;/code&gt; of each entry:&lt;/p&gt;

&lt;p&gt;``` java AbstractShell.java
	protected void handleExecutionResult(Object result) {
		if (result instanceof Iterable&amp;lt;?&amp;gt;) {
			for (Object o : (Iterable&amp;lt;?&amp;gt;) result) {
				logger.info(o.toString());
			}
		} else {
			logger.info(result.toString());
		}
	}&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;
When planning out my solution, I came up with the following back-of-the-napkin outline:

* a new custom annotation would be created and would be put on the command method. The `ParseResult invocationContext` instance passed into the processor methods could then be checked to see if the method being invoked had that annotation present, denoting a multi-step command.

* an abstract implementation of `ExecutionProcessor` would override the `afterReturningInvocation` method and detect if a multi-step command had been invoked. Command classes requiring multi-step logic would extend this class.

* the step logic would be configurable in that it could:
	* determine if there were more steps to execute, 
	* configure each step in preparation of execution, 
	* execute the step, and 
	* handle each step execution&apos;s result (e.g. logging to the shell), if any.

* the solution should also ensure that the command&apos;s original/final result was handled correctly.

&amp;lt;p /&amp;gt;
---
**`@CliStepIndicator`**

I created a new annotation that denotes a `@CliCommand`-annotated method supports multi-step processing:

``` java CliStepIndicator.java
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CliStepIndicator {

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbstractStepExecutionProcessor.java&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created a new abstract class implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExecutionProcessor&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;afterReturningInvocation&lt;/code&gt; method is overridden with the following logic:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;checks if this invocation is on a multi-step command&lt;/li&gt;
  &lt;li&gt;the initial result from the command is handled and this is fact is stored on the shell&lt;/li&gt;
  &lt;li&gt;while there are more steps
    &lt;ul&gt;
      &lt;li&gt;configure the next step&lt;/li&gt;
      &lt;li&gt;execute the next step in the workflow&lt;/li&gt;
      &lt;li&gt;handle the step execution result&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/8e84da9f72600736d4f0.js?file=AbstractStepExecutionProcessor.java&quot;&gt; &lt;/script&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StepCommand.java&lt;/code&gt; is an example command class with a multi-step command (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;step-test&lt;/code&gt;) method annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@CliStepIndicator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each step increments a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; variable (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configureStep&lt;/code&gt;), up to 3 times (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hasMoreSteps&lt;/code&gt;). Each step execution logs that it is executing (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executeStep&lt;/code&gt;), and step results are handled by printing the current &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; variable value (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleStepExecutionResult&lt;/code&gt;).&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/8e84da9f72600736d4f0.js?file=StepCommand.java&quot;&gt; &lt;/script&gt;

&lt;p&gt;The following integration test executes both the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;step-test&lt;/code&gt; multi-step command (to increment the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; variable 3 times) and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;step-check&lt;/code&gt; comand (to confirm the incrementation took place).&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/8e84da9f72600736d4f0.js?file=StepCommandsTest.java&quot;&gt; &lt;/script&gt;

&lt;p&gt;The test passes and prints the following output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://dl.dropboxusercontent.com/s/zy1rx79g8ugpad7/Screenshot%202015-03-19%2022.22.00.png&quot; alt=&quot;Test Result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ll be posting shortly about how I used this feature in a recent CLI project to navigate API pagination, run simulations, diff audit log entries etc.&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Mar 2015 21:44:25 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/03/19/supporting-multi-step-commands-with-spring-shell/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/03/19/supporting-multi-step-commands-with-spring-shell/</guid>
        
        <category>code</category>
        
        <category>spring</category>
        
        
      </item>
    
      <item>
        <title>Adding Java Config support to Spring Shell</title>
        <description>&lt;p&gt;Be it &lt;a href=&quot;https://github.com/nvie/gitflow&quot;&gt;DVCS workflows&lt;/a&gt;, &lt;a href=&quot;http://stedolan.github.io/jq/&quot;&gt;JSON transformations&lt;/a&gt;, or &lt;a href=&quot;http://octopress.org/&quot;&gt;blogging frameworks&lt;/a&gt;, I always favor tools that allow me to use the terminal.&lt;/p&gt;

&lt;p&gt;I’ve recently started using &lt;a href=&quot;http://docs.spring.io/spring-shell/docs/current/reference/htmlsingle/&quot;&gt;Spring Shell&lt;/a&gt; for rapidly experimenting with consuming data from a series of APIs I had created.&lt;/p&gt;

&lt;p&gt;I have become allergic to XML configuration for Spring applications in recent times, so I was disappointed to see a lack of support for Java configuration within Spring Shell.&lt;/p&gt;

&lt;p&gt;However, I did find a &lt;a href=&quot;https://jira.spring.io/browse/SHL-106&quot;&gt;JIRA issue tracking the feature request&lt;/a&gt;. The ticket creator had even submitted a pull request with a potential solution. However, Spring Shell lead &lt;a href=&quot;https://twitter.com/markpollack&quot;&gt;Mark Pollack&lt;/a&gt; responded that the feature could be provided in a simpler manner and even provided guidance to the solution.&lt;/p&gt;

&lt;p&gt;Implementing this solution seemed quite straightforward, so I gave it a go and it turned out well.&lt;/p&gt;

&lt;p&gt;I’ve submitted the following pull request to the Spring Shell GitHub repository: &lt;a href=&quot;https://github.com/spring-projects/spring-shell/pull/66&quot;&gt;SHL-106: Java Configuration support #66&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;The pull request detailed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Updated &lt;a href=&quot;https://github.com/robinhowlett/spring-shell/commit/38562bebf3d7621d4ee9fff1e0f477664299f282#diff-aefe86f2ee6f28928f53e91587a79910&quot;&gt;Bootstrap.java&lt;/a&gt; to accept basePackages String varargs and to include them in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClassPathBeanDefinitionScanner&lt;/code&gt; scan&lt;/li&gt;
  &lt;li&gt;Updated &lt;a href=&quot;https://github.com/robinhowlett/spring-shell/commit/38562bebf3d7621d4ee9fff1e0f477664299f282#diff-c2b9b8f993e1d2ca3677a070dd126078&quot;&gt;sample HelloWorld project&lt;/a&gt; to demonstrate mixing XML and Java Configuration&lt;/li&gt;
  &lt;li&gt;Updated docbook-reference-plugin dependency group ID and version (needed to get my gradle build working)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was my first pull request submission for a Spring project (albeit still awaiting approval). I was very happy to “give a little back” finally!&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Mar 2015 19:58:12 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/03/19/adding-java-config-support-to-spring-shell/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/03/19/adding-java-config-support-to-spring-shell/</guid>
        
        <category>code</category>
        
        <category>java</category>
        
        <category>spring</category>
        
        
      </item>
    
      <item>
        <title>Custom Jackson Polymorphic Deserialization without Type Metadata</title>
        <description>&lt;p&gt;At &lt;a href=&quot;http://sportslabs.com&quot;&gt;SportsLabs&lt;/a&gt; we regularly encounter proprietary, non-standard APIs and formats. Our job is to integrate with these APIs, normalize them, and distribute the data in web- and mobile-friendly web services.&lt;/p&gt;

&lt;p&gt;One of the scenarios we often encounter is a provider supplying multiple resource JSON-based APIs that share a lot of the same data in their responses, but without any particular field dedicated to identying the type of the resource e.g.&lt;/p&gt;

&lt;div class=&quot;language-json 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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;common&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a common field within multiple resource responses&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;one&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;one is a field only within this response type&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;div class=&quot;language-json 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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;common&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a common field within multiple resource responses&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;two&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;two is a field only within this response type&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of mapping 1-to-1 with these APIs, we often try to follow &lt;a href=&quot;http://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;DRY&lt;/a&gt; principles and model them as implementations of a common polymorphic abstraction.&lt;/p&gt;

&lt;p&gt;When using &lt;a href=&quot;http://wiki.fasterxml.com/JacksonPolymorphicDeserialization&quot;&gt;Jackson for polmorphic deserialization&lt;/a&gt; and not being in control of the API response data, the lack of any kind of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; identifier requires a different solution.&lt;/p&gt;

&lt;p&gt;One of the ways we’ve addressed this problem is to identify fields and properties that are unique to a particular resource API’s response.&lt;/p&gt;

&lt;p&gt;We then add this field to a registry of known unique-property-to-type mappings and then, during deserialization, lookup the response’s field names to see if any of them are stored within the registry.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Planning out the solution, there were a couple of things I wanted to incorporate:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the deserializer should be initialized with the abstract class representing the shared response data&lt;/li&gt;
  &lt;li&gt;a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register(String uniqueProperty, Class&amp;lt;? extends T&amp;gt; clazz)&lt;/code&gt; method will add the field-name-to-concrete-class mapping to the registry&lt;/li&gt;
  &lt;li&gt;the custom deserializer would be added to a Jackson &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimpleModule&lt;/code&gt; which in turn would be registered with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectMapper&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/ce45e575197060b8392d.js?file=UniquePropertyPolymorphicDeserializer.java&quot;&gt; &lt;/script&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deserialize&lt;/code&gt; method reads each of the fields in the response and looks up the registry to see if it is present. If it finds a match, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapper.treeToValue&lt;/code&gt; method is invoked with the response object and the mapped class returned by the registry. If no match is found an exception is thrown.&lt;/p&gt;

&lt;p&gt;For the unit test, I created an inner static abstract class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbstractTestObject&lt;/code&gt; (containing shared data) with two concrete implementations (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestObjectOne&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestObjectTwo&lt;/code&gt;) that each contain a property unique to that type.&lt;/p&gt;

&lt;p&gt;The test also contains a inner class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestObjectDeserializer&lt;/code&gt; that extends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UniquePropertyPolymorphicDeserializer&lt;/code&gt;. The test’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUp&lt;/code&gt; method:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;initializes the custom deserializer,&lt;/li&gt;
  &lt;li&gt;registers the unique-field-name-to-type mappings, and&lt;/li&gt;
  &lt;li&gt;adds the custom deserializer to a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimpleModule&lt;/code&gt; which is registered with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectMapper&lt;/code&gt; in turn.&lt;/li&gt;
&lt;/ul&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/ce45e575197060b8392d.js?file=UniquePropertyPolymorphicDeserializerTest.java&quot;&gt; &lt;/script&gt;

&lt;p&gt;If others have solved this problem differently, I’d love to hear about it!&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Mar 2015 18:28:24 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/03/19/custom-jackson-polymorphic-deserialization-without-type-metadata/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/03/19/custom-jackson-polymorphic-deserialization-without-type-metadata/</guid>
        
        <category>code</category>
        
        <category>jackson</category>
        
        
      </item>
    
      <item>
        <title>Building a Custom Jackson Deserializer</title>
        <description>&lt;p&gt;A partner recently provided a useful HTTP-based API for me at short notice.&lt;/p&gt;

&lt;p&gt;The API returned simple JSON representations of the current state of various events they had periodically ingested from our REST APIs.&lt;/p&gt;

&lt;p&gt;A decision they made was to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;yes&quot;&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;no&quot;&lt;/code&gt; values in the response, rather than booleans e.g.&lt;/p&gt;

&lt;div class=&quot;language-json 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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exists&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;has_ended&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;no&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I had written a &lt;a href=&quot;http://projects.spring.io/spring-social/&quot;&gt;Spring Social&lt;/a&gt;-based API client to interact with their API but wanted to deserialize their API response representation into a POJO that followed Java convention.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://wiki.fasterxml.com/JacksonHome&quot;&gt;Jackson&lt;/a&gt; is a great Java library for processing JSON data format. It’s quite straightforward to use it to solve a problem like the above with a custom &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/JsonDeserializer.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JsonDeserializer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Planning out the solution, there were a couple of things I wanted to incorporate:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the deserializer should be case-insensitive&lt;/li&gt;
  &lt;li&gt;a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; value in the JSON document should be treated as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; (due to autoboxing considerations around Java’s primitive (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boolean&lt;/code&gt;) and Object-based &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boolean&lt;/code&gt; representations)&lt;/li&gt;
  &lt;li&gt;an exception should be thrown if something other than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;yes&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;no&quot;&lt;/code&gt; was provided&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sketching out the JUnit test class, I needed a static inner class to represent the POJO that Jackson would deserialize to, an &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/ObjectMapper.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectMapper&lt;/code&gt;&lt;/a&gt; to deserialize a sample JSON document, and test cases for the above conditions.&lt;/p&gt;

&lt;p&gt;For the field that will use the custom deserializer, the &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/annotation/JsonDeserialize.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@JsonDeserialize&lt;/code&gt;&lt;/a&gt; annotation is added, with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using&lt;/code&gt; parameter pointing at our implementation class.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/1ef8a82584e9ea36ed04.js?file=YesNoBooleanDeserializerTest.java&quot;&gt; &lt;/script&gt;

&lt;p&gt;For the implementation, we must override the &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/JsonDeserializer.html#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext\)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JsonDeserializer.deserialize(JsonParser jp, DeserializationContext ctxt)&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;

&lt;p&gt;If Jackson has identified the field value token currently being deserialized as a String, it is case-insensitively checked for either a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;yes&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;no&quot;&lt;/code&gt; value, throwing a &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/JsonMappingException.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JsonMappingException&lt;/code&gt;&lt;/a&gt; with an explaination through the &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/DeserializationContext.html#weirdStringException(java.lang.String, java.lang.Class, java.lang.String\)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DeserializationContext.weirdStringException(String value, Class&amp;lt;?&amp;gt; instClass, String msg)&lt;/code&gt;&lt;/a&gt; method if an unsupported String value is received instead.&lt;/p&gt;

&lt;p&gt;As Java supports both the primitive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boolean&lt;/code&gt; and the Object &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boolean&lt;/code&gt;, and can autobox between them, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; values will be interpreted as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;. To do this, &lt;a href=&quot;http://fasterxml.github.io/jackson-databind/javadoc/2.4/com/fasterxml/jackson/databind/JsonDeserializer.html#getNullValue(\)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JsonDeserializer.getNullValue()&lt;/code&gt;&lt;/a&gt; is overridden and returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boolean.FALSE&lt;/code&gt;. Then within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deserialize&lt;/code&gt; method, the current token is checked if it is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JsonToken.VALUE_NULL&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getNullValue()&lt;/code&gt; method is called if it is.&lt;/p&gt;

&lt;p&gt;Finally, an exception is thrown if the token is of any other type.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/robinhowlett/1ef8a82584e9ea36ed04.js?file=YesNoBooleanDeserializer.java&quot;&gt; &lt;/script&gt;

</description>
        <pubDate>Fri, 02 Jan 2015 04:35:31 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2015/01/02/building-a-custom-jackson-deserializer/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2015/01/02/building-a-custom-jackson-deserializer/</guid>
        
        <category>code</category>
        
        <category>java</category>
        
        <category>jackson</category>
        
        
      </item>
    
      <item>
        <title>Spring app migration: from XML to Java-based config</title>
        <description>&lt;p&gt;Our team recently built a Spring MVC 3.1 application for a web service API. We had used the traditional XML-based configuration but I wanted to see how easy would it be to migrate a Spring application from an XML-based to a Java annotation-based configuration.&lt;/p&gt;

&lt;p&gt;I referenced three great resources for this migration:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.twitter.com/baeldung&quot;&gt;@baeldung&lt;/a&gt;’s excellent &lt;a href=&quot;http://www.baeldung.com/2011/10/20/bootstraping-a-web-application-with-spring-3-1-and-java-based-configuration-part-1/&quot;&gt;Bootstrapping a web application with Spring 3.1 and Java based Configuration&lt;/a&gt; series&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.twitter.com/doughaber&quot;&gt;@doughaber&lt;/a&gt;’s post, &lt;a href=&quot;http://blog.fawnanddoug.com/2012/05/pagination-with-spring-mvc-spring-data.html&quot;&gt;Pagination with Spring MVC, Spring Data and Java Config&lt;/a&gt;, included some great “gotchas”, especially around web tier configuration&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.twitter.com/cbeam&quot;&gt;@cbeam&lt;/a&gt; and &lt;a href=&quot;http://www.twitter.com/rstoya05&quot;&gt;@rstoya05&lt;/a&gt;’s presentation, &lt;a href=&quot;http://cbeams.github.com/spring-3.1-config&quot;&gt;Configuration Enhancements in Spring 3.1&lt;/a&gt;, laid out in terrific detail how Spring configuration has evolved and how most XML configurations could be achieved through Java annotations&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more --&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.xml&lt;/code&gt; defined the context configuration and dispatch servlet locations:&lt;/p&gt;

&lt;p&gt;``` xml web.xml
&amp;lt;?xml version=”1.0” encoding=”ISO-8859-1”?&amp;gt;&lt;/p&gt;
&lt;web-app version=&quot;2.5&quot; xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd&quot;&gt;
	&lt;context-param&gt;
		&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
		&lt;param-value&gt;classpath:META-INF/spring/root-context.xml&lt;/param-value&gt;
	&lt;/context-param&gt;
	...
	&lt;servlet&gt;
		&lt;servlet-name&gt;spring-mvc-dispatcher&lt;/servlet-name&gt;
		&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
		&lt;init-param&gt;
			&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
			&lt;param-value&gt;classpath:META-INF/spring/servlet-context.xml&lt;/param-value&gt;
		&lt;/init-param&gt;
		&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
	&lt;/servlet&gt;
	&lt;servlet-mapping&gt;
		&lt;servlet-name&gt;spring-mvc-dispatcher&lt;/servlet-name&gt;
		&lt;url-pattern&gt;/&lt;/url-pattern&gt;
	&lt;/servlet-mapping&gt;
&lt;/web-app&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;
`root-context.xml` was intended to define shared resources visible to all other web components, servlets etc and for cross-cutting concerns like security - for the moment though it was just an empty `&amp;lt;beans&amp;gt;` XML file.

`servlet-context.xml` was the `DispatcherServlet` and a wrapper around three other contexts; `persistence-context.xml` for Spring Data/JPA configuration, `bizniz-context.xml` for business/service configuration and `web-context.xml` for web service configuration:

``` xml servlet-context.xml
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
	xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
	xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd&quot;&amp;gt;
	
	&amp;lt;import resource=&quot;classpath:META-INF/spring/persistence-context.xml&quot; /&amp;gt;
	&amp;lt;import resource=&quot;classpath:META-INF/spring/bizniz-context.xml&quot; /&amp;gt;
	&amp;lt;import resource=&quot;classpath:META-INF/spring/web-context.xml&quot; /&amp;gt;
&amp;lt;/beans&amp;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;web-context.xml&lt;/code&gt; contained an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;mvc:annotation-driven&amp;gt;&lt;/code&gt; declaration to enable Spring MVC’s annotation based model. This was the first place to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;mvc:annotation-driven&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@EnableWebMVC&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;mvc:annotation-driven&amp;gt;&lt;/code&gt; enables the Spring MVC &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Controller&lt;/code&gt; programming model and adds support for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@RequestMapping&lt;/code&gt; annotations etc.&lt;/p&gt;

&lt;p&gt;We had added to the web service a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageableArgumentResolver&lt;/code&gt; for Spring Data paging query parameter support and an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MappingJackson2HttpMessageConverter&lt;/code&gt; with a custom Jackson &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectMapper&lt;/code&gt;. The custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JacksonObjectMapper&lt;/code&gt; just extended &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectMapper&lt;/code&gt; and added a serialization inclusion configuration set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NON_NULL&lt;/code&gt;, so empty JSON properties would not be outputted.&lt;/p&gt;

&lt;p&gt;``` xml web-context.xml
&amp;lt;beans …&amp;gt;&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;…

&amp;lt;mvc:annotation-driven&amp;gt;
	&amp;lt;mvc:argument-resolvers&amp;gt;
		&amp;lt;bean class=&quot;org.springframework.data.web.PageableArgumentResolver&quot; /&amp;gt;
	&amp;lt;/mvc:argument-resolvers&amp;gt;
	&amp;lt;mvc:message-converters&amp;gt;
		&amp;lt;bean class=&quot;org.springframework.http.converter.json.MappingJackson2HttpMessageConverter&quot;&amp;gt;
			&amp;lt;property name=&quot;objectMapper&quot; ref=&quot;jacksonObjectMapper&quot; /&amp;gt;
		&amp;lt;/bean&amp;gt;
	&amp;lt;/mvc:message-converters&amp;gt;
&amp;lt;/mvc:annotation-driven&amp;gt;

&amp;lt;bean id=&quot;jacksonObjectMapper&quot; class=&quot;com.silverchalice.contracts.json.mappers.JacksonObjectMapper&quot; /&amp;gt;

…
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&amp;lt;/beans&amp;gt;&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;
The first thing that needed to be done was to create a new configuration class, `WebConfig`, annotated with `@Configuration` and `@EnableWebMVC`. The new class was added to a new package, `com.silverchalice.api.config`.

It is common in many Spring MVC application configurations to just use the default self-closing `&amp;lt;mvc:annotation-driven /&amp;gt;` tag; therefore the new configuration class could be just an empty class annotated with `@EnableWebMVC`.

However, since we had added an argument resolver and message conveter customizations, the config class would need to be able to configure these too. This was acheived by extending `WebMvcConfigurerAdapter`. The needed customizations could then be added by simply overriding the appropriate method:

``` java WebConfig.java
package com.silverchalice.api.config;

...

/**
 * Spring MVC Configuration.
 * 
 * Extends {@link WebMvcConfigurerAdapter}, which provides convenient callbacks that allow us to customize aspects of the Spring Web MVC framework.
 * These callbacks allow us to register custom interceptors, message converters, argument resovlers, a validator, resource handling, and other things.
 * 
 * @author robin
 * @see WebMvcConfigurerAdapter
 */
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

	@Override
	public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; argumentResolvers) {
		// equivalent to &amp;lt;mvc:argument-resolvers&amp;gt;
	}
	
	@Override
	public void configureMessageConverters(List&amp;lt;HttpMessageConverter&amp;lt;?&amp;gt;&amp;gt; converters) {
		// equivalent to &amp;lt;mvc:message-converters&amp;gt;
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To implement the above methods, we needed references to the beans that they used; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageableArgumentResolver&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MappingJackson2HttpMessageConverter&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JacksonObjectMapper&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was a simple as adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Bean&lt;/code&gt; annotated methods that returned instances of each type that Spring would then manage. The overridden configuation methods could then just add those instances to their respective collections:&lt;/p&gt;

&lt;p&gt;``` java WebConfig.java
package com.silverchalice.api.config;&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {&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;@Override
public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; argumentResolvers) {
    argumentResolvers.add(resolver());
}

@Override
public void configureMessageConverters(List&amp;lt;HttpMessageConverter&amp;lt;?&amp;gt;&amp;gt; converters) {
	converters.add(converter());
}

@Bean
public ServletWebArgumentResolverAdapter resolver() {
	return new ServletWebArgumentResolverAdapter(pageable());
}

@Bean 
public PageableArgumentResolver pageable() {
	return new PageableArgumentResolver();
}

@Bean
public MappingJackson2HttpMessageConverter converter() {
	MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
	converter.setObjectMapper(mapper());
	return converter;
}

/**
 * Provides the Jackson ObjectMapper with custom configuration for our JSON serialization.
 * @return The Jackson object mapper with non-null serialization configured
 */
@Bean
public JacksonObjectMapper mapper() {
	return new JacksonObjectMapper();
} } ```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You may have noticed that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageableArgumentResolver&lt;/code&gt; was wrapped in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServletWebArgumentResolverAdapter&lt;/code&gt; before being added to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;argumentResolvers&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;This is because Spring Data’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageableArgumentResolver&lt;/code&gt; interface uses the old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ArgumentResolver&lt;/code&gt; interface instead of the new (Spring 3.1) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HandlerMethodArgumentResolver&lt;/code&gt; interface. The XML config handles this behind the scenes but with a Java config it must be done manually. Thanks to &lt;a href=&quot;http://www.twitter.com/doughaber&quot;&gt;@doughaber&lt;/a&gt; for this &lt;a href=&quot;http://blog.fawnanddoug.com/2012/05/pagination-with-spring-mvc-spring-data.html&quot;&gt;gotcha&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As the Spring MVC Java annotation-based configuration was complete, the references to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;mvc:annotation-driven&amp;gt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jacksonObjectMapper&lt;/code&gt; beans in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; were removed.&lt;/p&gt;

&lt;p&gt;We now had an annotated configuration class that configured the container - but only the Spring MVC configuration. The dispatcher servlet configuration (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servlet-config.xml&lt;/code&gt;) was still being referenced as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contextConfigLocation&lt;/code&gt; under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;servlet&amp;gt;&lt;/code&gt; and we hadn’t actually replaced the application context (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root-context.xml&lt;/code&gt;) with an annotated equivalent.&lt;/p&gt;

&lt;p&gt;So, a basic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Configuration&lt;/code&gt; annotated class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppConfig&lt;/code&gt; was created to replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root-context.xml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;``` java AppConfig.java
package com.silverchalice.api.config;&lt;/p&gt;

&lt;p&gt;import org.springframework.context.annotation.Configuration;&lt;/p&gt;

&lt;p&gt;/**&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Configuation defining shared resources visible to all other web components,&lt;/li&gt;
  &lt;li&gt;servlets etc and for cross-cutting concerns like security&lt;/li&gt;
  &lt;li&gt;&lt;/li&gt;
  &lt;li&gt;@author robin
 */
@Configuration
public class AppConfig {&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;}&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;
`root-context.xml` was then deleted.

Similarly, `ServletConfig` was created to replace `servlet-context.xml`. However, we had only partially migrated the configuration. We needed to include the new `WebConfig` but also the existing `persistence-context.xml`, `bizniz-context.xml`, and `web-context.xml` XML-based configurations.

This was done using the `@Import` and `@ImportResource` annotations respectively:

``` java ServletConfig.java
package com.silverchalice.api.config;

…
	
/**
 * Configuation intended to aggregate the context for 
 * each architectural layer of the application
 * 
 * @author robin
 */
@Configuration
@Import(WebConfig.class)
@ImportResource({
	&quot;classpath:META-INF/spring/persistence-context.xml&quot;,
	&quot;classpath:META-INF/spring/bizniz-context.xml&quot;,
	&quot;classpath:META-INF/spring/web-context.xml&quot;
})
public class ServletConfig {

}
&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;WebConfig&lt;/code&gt; configuration class was explicity imported using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Import&lt;/code&gt;. Since we didn’t need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servlet-context.xml&lt;/code&gt; anymore, we used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ImportResource&lt;/code&gt; to import in the existing XML-based context configurations and deleted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servlet-context.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we needed to modify our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.xml&lt;/code&gt; so that the context knows about these Java annotation-based configurations.&lt;/p&gt;

&lt;p&gt;The only thing needed here was to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contextClass&lt;/code&gt; params for both the application and dispatch servlet contexts referencing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnnotationConfigWebApplicationContext&lt;/code&gt;, and modifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contextConfigLocation&lt;/code&gt; params to use the appropriate configuration class:&lt;/p&gt;

&lt;p&gt;``` xml web.xml&lt;/p&gt;
&lt;web-app version=&quot;2.5&quot; xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd&quot;&gt;

	…
	
	&lt;context-param&gt;
		&lt;param-name&gt;contextClass&lt;/param-name&gt;
		&lt;param-value&gt;
		   org.springframework.web.context.support.AnnotationConfigWebApplicationContext
		&lt;/param-value&gt;
	&lt;/context-param&gt;
	&lt;context-param&gt;
		&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
		&lt;param-value&gt;com.silverchalice.api.config.AppConfig&lt;/param-value&gt;
	&lt;/context-param&gt;
	
	…
	
	&lt;servlet&gt;
		&lt;servlet-name&gt;spring-mvc-dispatcher&lt;/servlet-name&gt;
		&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
		&lt;init-param&gt;
			&lt;param-name&gt;contextClass&lt;/param-name&gt;
			&lt;param-value&gt;
				org.springframework.web.context.support.AnnotationConfigWebApplicationContext
			&lt;/param-value&gt;
		&lt;/init-param&gt;
		&lt;init-param&gt;
			&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
			&lt;param-value&gt;com.silverchalice.api.config.ServletConfig&lt;/param-value&gt;
		&lt;/init-param&gt;
		&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
	&lt;/servlet&gt;
	&lt;servlet-mapping&gt;
		&lt;servlet-name&gt;spring-mvc-dispatcher&lt;/servlet-name&gt;
		&lt;url-pattern&gt;/&lt;/url-pattern&gt;
	&lt;/servlet-mapping&gt;
	
&lt;/web-app&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;	
The Spring MVC configuration was now completely Java annotation-based and the rest of the configuration, while still XML-based, was set up for incremental migration to our new way.

After deploying to Tomcat 7, the logs show that the `AnnotationConfigWebApplicationContext` has worked for both the application and dispatcher servlet configurations (which, in turn, loads the web configuration), while still picking up the renaming XML configurations:

``` logtalk
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Thu Dec 27 23:34:24 MST 2012]; root of context hierarchy
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Successfully resolved class for [com.silverchalice.api.config.AppConfig]

…

INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet &apos;spring-mvc-dispatcher&apos;: initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing WebApplicationContext for namespace &apos;spring-mvc-dispatcher-servlet&apos;: startup date [Thu Dec 27 23:34:24 MST 2012]; parent: Root WebApplicationContext
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Successfully resolved class for [com.silverchalice.api.config.ServletConfig]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/persistence-context.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/bizniz-context.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/web-context.xml]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p /&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;context:component-scan&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ComponentScan&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next was to convert how packages are scanned to register beans within the application context.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; contained the following line:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;context:component-scan base-package=&quot;com.silverchalice.api.controller&quot; /&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;com.silverchalice.api.controller&lt;/code&gt; package contains our Spring MVC components for the presentation layer (the web service interface we are exposing) e.g.&lt;/p&gt;

&lt;p&gt;``` java SportsController.java
@Controller
@RequestMapping(“/sports”)
public class SportsController {
	…
}&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;
To replace this component scanning directive with its Java equivalent, the `@ComponentScan` annotation is added to the web configuration class:

``` java WebConfig.java
@ComponentScan(basePackageClasses = { SportsController.class })
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It would have been possible to just add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ComponentScan(&quot;com.silverchalice.api.controller&quot;)&lt;/code&gt; but by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;basePackageClasses&lt;/code&gt; specifier with a class reference(s), the packages of each class noted will be scanned, which is both type-safe and adds IDE support for future refactoring.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Indeed, Spring documentation suggests to “consider creating a special no-op marker class or interface in each package that serves no purpose other than being referenced by this attribute”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;context:component-scan … /&amp;gt;&lt;/code&gt; was removed from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p /&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;beans profile=&quot;…&quot;&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Spring 3.1 also came the ability to conditionally include beans under certain environment bean definition profiles.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://swagger.wordnik.com/&quot;&gt;Swagger&lt;/a&gt; is a JSON-based specification for “describing, producing, consuming, and visualizing RESTful web services”. We forked &lt;a href=&quot;https://twitter.com/marty_pitt&quot;&gt;@marty_pitt&lt;/a&gt;’s &lt;a href=&quot;https://github.com/SilverChaliceNewMedia/swagger-springmvc&quot;&gt;Swagger Spring MVC&lt;/a&gt; implementation to provide deployment-tailored Swagger documentation for our web service.&lt;/p&gt;

&lt;p&gt;We used this functionality in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; to declare a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DocumentationController&lt;/code&gt; bean for each deployment environment:&lt;/p&gt;

&lt;p&gt;``` xml web-context.xml
…&lt;/p&gt;

&lt;!-- Swagger integration --&gt;
&lt;beans profile=&quot;dev&quot;&gt;
	&lt;bean id=&quot;documentationController&quot; class=&quot;com.mangofactory.swagger.springmvc.controller.DocumentationController&quot; p:apiVersion=&quot;1.0&quot; p:swaggerVersion=&quot;1.0&quot; p:basePath=&quot;http://localhost:8080/webservice&quot; /&gt;
&lt;/beans&gt;
&lt;beans profile=&quot;prod&quot;&gt;
        &lt;bean id=&quot;documentationController&quot; class=&quot;com.mangofactory.swagger.springmvc.controller.DocumentationController&quot; p:apiVersion=&quot;1.0&quot; p:swaggerVersion=&quot;1.0&quot; p:basePath=&quot;http://webservice.silverchalice.com&quot; /&gt;
&lt;/beans&gt;

&lt;p&gt;…&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;
Depending on which profile is active at application context start, a `documentationController` bean will be available for the specified environment.

[@cbeam](http://www.twitter.com/cbeam)&apos;s blog post, [Spring 3.1 M1: Introducting @Profile](http://blog.springsource.org/2011/02/14/spring-3-1-m1-introducing-profile/), recommended *object-oriented configuration* by declaring configuration interfaces and then using `@Autowired` to inject the profile-dependent configuration themselves. I liked that, so we first created an interface for the application environment configuration:

``` java AppEnvConfig.java
/**
 * Environment configuration interface for application
 * 
 * @author robin
 */
public interface AppEnvConfig {

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before creating an environment configuration interface for the web layer, I created an interface for the Swagger configuration that would support profiles:&lt;/p&gt;

&lt;p&gt;``` java SwaggerConfig.java
import com.mangofactory.swagger.springmvc.controller.DocumentationController;&lt;/p&gt;

&lt;p&gt;/**&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Configuration for Swagger Spring MVC API Documentation support&lt;/li&gt;
  &lt;li&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;@author robin
 */
public interface SwaggerConfig {&lt;/p&gt;

    &lt;p&gt;DocumentationController apiDocs();&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;}&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;
The web environment configuration interface then extended both `AppEnvConfig` and `SwaggerConfig`:

``` java WebEnvConfig.java
/**
 * Environment configuration interface for web layer
 * 
 * @author robin
 */
public interface WebEnvConfig extends AppEnvConfig, SwaggerConfig {

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each web environment configuration that implemented this interface was then annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile&lt;/code&gt; whose value specified they profile name:&lt;/p&gt;

&lt;p&gt;``` java WebEnvDevConfig.java
/**&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Environment configuration for the web layer (development profile)&lt;/li&gt;
  &lt;li&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;@author robin
 */
@Configuration
@Profile(“dev”)
public class WebEnvDevConfig implements WebEnvConfig {&lt;/p&gt;

    &lt;p&gt;@Override
 public DocumentationController apiDocs() {
     DocumentationController docs = new DocumentationController();&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; docs.setApiVersion(&quot;1.0&quot;);
 docs.setSwaggerVersion(&quot;1.0&quot;);
 docs.setBasePath(&quot;http://localhost:8080/webservice&quot;);
	
 return docs;  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;}&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;	
The `apiDocs` method is then overridden per profile and the `DocumentationController` instance is configured for that environment. Similarly, the production environment configuration (`WebEnvProdConfig`) class would instantiate an instance customized for production URLs.

&amp;lt;p /&amp;gt;
---
**From `&amp;lt;context:property-placeholder location=&quot;…&quot; /&amp;gt;` to `@PropertySource`**

However, the various implementations of `WebEnvConfig` with slightly modified `DocumentationController` instances felt like a [&quot;code smell&quot;](http://www.codinghorror.com/blog/2006/05/code-smells.html).

Instead I preferred to use `.properties` files to detail environment-specific settings, for example, `web-dev.properties`:

``` properties web-dev.properties
apiVersion=&quot;1.0&quot;
swaggerVersion=&quot;1.0&quot;
basePath=&quot;http://localhost:8080/webservice&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To use properties previously with XML-based configuration, a property placeholder definition would be used:&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;&amp;lt;context:property-placeholder location=&quot;web-dev.properties&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With Spring 3.1, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Environment&lt;/code&gt; abstraction allows &lt;a href=&quot;http://blog.springsource.com/2011/02/15/spring-3-1-m1-unified-property-management/&quot;&gt;searching for properties across a hierarchy of property sources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@PropertySource&lt;/code&gt; annotation will specifiy a location of properties that will be added to the environment. This makes it very easy to both add to and retrieve properties from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Environment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebEnvConfig&lt;/code&gt; abstract class (that now contains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apiDocs&lt;/code&gt; method) was altered to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Autowired Environment&lt;/code&gt; instance to retrieve the appropriate property value:&lt;/p&gt;

&lt;p&gt;``` java WebEnvConfig
public abstract class WebEnvConfig implements AppEnvConfig, SwaggerConfig {&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;@Autowired Environment env;

@Override
public DocumentationController apiDocs() {
	DocumentationController docs = new DocumentationController();
	
	docs.setApiVersion(env.getProperty(&quot;apiVersion&quot;));
	docs.setSwaggerVersion(env.getProperty(&quot;swaggerVersion&quot;));
	docs.setBasePath(env.getProperty(&quot;basePath&quot;));
	
	return docs;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;}&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;
The, for each `@Profile` annotated concrete web configuration class, the `@PropertySource` annotation&apos;s value denotes the location of the `.properties` file to be used:

``` java WebEnvDevConfig
@Configuration
@Profile(&quot;dev&quot;)
@PropertySource(&quot;classpath:web-dev.properties&quot;)
public class WebEnvDevConfig extends WebEnvConfig {

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Therefore, for whatever the active profile was (for instance, if the JVM parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Dspring.profiles.active=dev&lt;/code&gt; was used), the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.properties&lt;/code&gt; file for that profile was loaded.&lt;/p&gt;

&lt;p&gt;This meant that we could inject in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebEnvConfig&lt;/code&gt; instance into our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebConfig&lt;/code&gt; class and expose our Swagger Spring MVC functionality that would be customized for the environment profile it was launched under.&lt;/p&gt;

&lt;p&gt;To do this, we needed to import the profile annotated configuration classes, autowire a configuration instance and them expose a single bean with the desired functionality:&lt;/p&gt;

&lt;p&gt;``` java WebConfig.java
@ComponentScan(basePackageClasses = { SportsController.class })
@Configuration
@EnableWebMvc
@Import({WebEnvDevConfig.class, WebEnvProdConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {&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;// the web @Configuration class injected for this @Profile
@Autowired WebEnvConfig webEnvConfig;

…

/**
 * Swagger Spring MVC API Documentation support
 * 
 * @return swagger documentation controller
 */
@Bean
public DocumentationController documentationController() {
	return webEnvConfig.apiDocs();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;}&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;
Naturally, this approach could be extended across all application layers.

&amp;lt;p /&amp;gt;
---
**From `web.xml` to `WebApplicationInitializer`**

The last piece of the puzzle of converting our Spring MVC app&apos;s web layer to a fully Java annoatation-based configuration was the conversion of the deployment descriptor itself.

At this moment, our web service was set up as a Servlet 2.5 application, but, since we were using Apache Tomcat 7 as our servlet container, we could use [Servlet 3.0 and its annotation support](http://explodingjava.blogspot.in/2010/05/servlet-30-annotations.html) instead. This also opened up the opportunity to replace most, if not all, of `web.xml` with a programmatic equivalent written in Java.

In order to configure the `ServletContext` programmatically, we needed to create a class that implemented `WebApplicationInitializer`. Implementations of this interface would be detected automatically and bootstrapped by the Servlet 3.0 container.

``` java WebInit.java
package com.silverchalice.api.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;

public class WebInit implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext container) throws ServletException {
		// TODO Auto-generated method stub
	}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is simply a matter of replicating our changes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.xml&lt;/code&gt; within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebInit&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;As we are using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile&lt;/code&gt; now, we need to tell the container context what profile to use if no active profile has been configured (either as a JVM argument, an environment variable etc.). This was done in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.xml&lt;/code&gt; by setting a context init-param:&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;cp&quot;&gt;&amp;lt;!- If no active profile is set via -Dspring.profiles.active or 
some other mechanism then this will be used. --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;context-param&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;param-name&amp;gt;&lt;/span&gt;spring.profiles.default&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-name&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;param-value&amp;gt;&lt;/span&gt;dev&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-value&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/context-param&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In our new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebInit&lt;/code&gt; class, this is very easy:&lt;/p&gt;

&lt;p&gt;``` java WebInit.java
public class WebInit implements WebApplicationInitializer {&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;@Override
public void onStartup(ServletContext container) throws ServletException {
	/*
	 * If no active profile is set via -Dspring.profiles.active or 
	 * some other mechanism then this will be used.
	 */
	container.setInitParameter(&quot;spring.profiles.default&quot;, &quot;dev&quot;);
	
	…
	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;}&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;	
Next we created the root application context itself, set its display name, registered the root application configuration and added a context loader listener to bootstrap the container - all very standard stuff. We had set this all up earlier in `web.xml`:

``` xml
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;ISO-8859-1&quot;?&amp;gt;
&amp;lt;web-app version=&quot;2.5&quot; xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd&quot;&amp;gt;
...
	&amp;lt;display-name&amp;gt;Silver Chalice Web API&amp;lt;/display-name&amp;gt;
	&amp;lt;context-param&amp;gt;
	        &amp;lt;param-name&amp;gt;contextClass&amp;lt;/param-name&amp;gt;
	        &amp;lt;param-value&amp;gt;
				org.springframework.web.context.support.AnnotationConfigWebApplicationContext
	        &amp;lt;/param-value&amp;gt;
	&amp;lt;/context-param&amp;gt;
	&amp;lt;context-param&amp;gt;
	        &amp;lt;param-name&amp;gt;contextConfigLocation&amp;lt;/param-name&amp;gt;
	        &amp;lt;param-value&amp;gt;com.silverchalice.api.config.AppConfig&amp;lt;/param-value&amp;gt;
	&amp;lt;/context-param&amp;gt;
	&amp;lt;!-- Creates the Spring Container shared by all Servlets and Filters --&amp;gt;
	&amp;lt;listener&amp;gt;
	        &amp;lt;listener-class&amp;gt;org.springframework.web.context.ContextLoaderListener&amp;lt;/listener-class&amp;gt;
	&amp;lt;/listener&amp;gt;
...
&amp;lt;/web-app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;but it was very straightforward to do this through Java:&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;c1&quot;&gt;// Creates the root application context&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;AnnotationConfigWebApplicationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appContext&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;nf&quot;&gt;AnnotationConfigWebApplicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;appContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDisplayName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Silver Chalice Web API&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Registers the application configuration with the root context&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;appContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AppConfig&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;span class=&quot;c1&quot;&gt;// Creates the Spring Container shared by all Servlets and Filters&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addListener&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;ContextLoaderListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appContext&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;Filters were straightforward to convert also:&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;filter&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-name&amp;gt;&lt;/span&gt;PerfLoggingFilter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-name&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-class&amp;gt;&lt;/span&gt;com.silverchalice.api.filter.PerfLoggingFilter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-class&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-mapping&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-name&amp;gt;&lt;/span&gt;PerfLoggingFilter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-name&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/*&lt;span class=&quot;nt&quot;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-mapping&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;became:&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;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PerfLoggingFilter&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PerfLoggingFilter&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;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addMappingForUrlPatterns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/*&quot;&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;Finally the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DispatchServlet&lt;/code&gt; itself was migrated from:&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;servlet&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;servlet-name&amp;gt;&lt;/span&gt;spring-mvc-dispatcher&lt;span class=&quot;nt&quot;&gt;&amp;lt;/servlet-name&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;servlet-class&amp;gt;&lt;/span&gt;org.springframework.web.servlet.DispatcherServlet&lt;span class=&quot;nt&quot;&gt;&amp;lt;/servlet-class&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;init-param&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;param-name&amp;gt;&lt;/span&gt;contextClass&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-name&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;param-value&amp;gt;&lt;/span&gt;
			org.springframework.web.context.support.AnnotationConfigWebApplicationContext
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-value&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/init-param&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;init-param&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;param-name&amp;gt;&lt;/span&gt;contextConfigLocation&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-name&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;param-value&amp;gt;&lt;/span&gt;com.silverchalice.api.config.ServletConfig&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-value&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/init-param&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;load-on-startup&amp;gt;&lt;/span&gt;1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/load-on-startup&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/servlet&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;servlet-mapping&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;servlet-name&amp;gt;&lt;/span&gt;spring-mvc-dispatcher&lt;span class=&quot;nt&quot;&gt;&amp;lt;/servlet-name&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/&lt;span class=&quot;nt&quot;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/servlet-mapping&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to:&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;c1&quot;&gt;// Creates the dispatcher servlet context&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;AnnotationConfigWebApplicationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servletContext&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;nf&quot;&gt;AnnotationConfigWebApplicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Registers the servlet configuraton with the dispatcher servlet context&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;servletContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ServletConfig&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;span class=&quot;c1&quot;&gt;// Further configures the servlet context&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;ServletRegistration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Dynamic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatcher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
		&lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addServlet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spring-mvc-dispatcher&quot;&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;nf&quot;&gt;DispatcherServlet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;servletContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dispatcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setLoadOnStartup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dispatcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&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;web.xml&lt;/code&gt; can be deleted now and the application starts just fine:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-logtalk&quot;&gt;INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing Silver Chalice Web API: startup date [Tue Jan 01 19:21:33 MST 2013]; root of context hierarchy
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Registering annotated classes: [class com.silverchalice.api.config.AppConfig]
…
INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 323 ms
DEBUG: com.silverchalice.api.filter.PerfLoggingFilter - initializing perf logging filter
…
INFO: Initializing Spring FrameworkServlet &apos;spring-mvc-dispatcher&apos;
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet &apos;spring-mvc-dispatcher&apos;: initialization started
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Refreshing WebApplicationContext for namespace &apos;spring-mvc-dispatcher-servlet&apos;: startup date [Tue Jan 01 19:21:34 MST 2013]; parent: Silver Chalice Web API
INFO : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Registering annotated classes: [class com.silverchalice.api.config.ServletConfig]
…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it.&lt;/p&gt;

&lt;p /&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;aop:aspectj-autoproxy /&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@EnableAspectJAutoProxy&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The single remaining item left in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; was 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;&lt;span class=&quot;nt&quot;&gt;&amp;lt;beans&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;c&quot;&gt;&amp;lt;!-- Enable AspectJ auto-wiring --&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;aop:aspectj-autoproxy&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/beans&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We were using &lt;a href=&quot;http://www.eclipse.org/aspectj/&quot;&gt;AspectJ&lt;/a&gt; with &lt;a href=&quot;http://perf4j.codehaus.org/&quot;&gt;Perf4J&lt;/a&gt; to profile our web service. We had added the Perf4J &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profiled&lt;/code&gt; annotation (not to be confused with the Spring annotation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile&lt;/code&gt;) to our controllers and had created an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aop.xml&lt;/code&gt; file configuring our timing aspect.&lt;/p&gt;

&lt;p&gt;To remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; completely we just needed to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@EnableAspectJAutoProxy&lt;/code&gt; annotation to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebConfig&lt;/code&gt; configuration class:&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;nd&quot;&gt;@ComponentScan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;basePackageClasses&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;nc&quot;&gt;SportsController&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;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableAspectJAutoProxy&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebMvc&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;WebEnvDevConfig&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;span class=&quot;nc&quot;&gt;WebEnvProdConfig&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;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;WebConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebMvcConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;err&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;Then the reference to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-context.xml&lt;/code&gt; was removed from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServletConfig&lt;/code&gt; and the file itself was deleted.&lt;/p&gt;
</description>
        <pubDate>Wed, 13 Feb 2013 10:46:00 +0000</pubDate>
        <link>https://robinhowlett.com/blog/2013/02/13/spring-app-migration-from-xml-to-java-based-config/</link>
        <guid isPermaLink="true">https://robinhowlett.com/blog/2013/02/13/spring-app-migration-from-xml-to-java-based-config/</guid>
        
        <category>code</category>
        
        <category>java</category>
        
        <category>spring</category>
        
        
      </item>
    
  </channel>
</rss>
