vorba.ch
Paul’s personal blog about software and technology
2018-12-23T00:00:00Z
http://vorb.de/log/feed.xml
Paul Vorbach
https://paul.vorba.ch
© 2014–2018 Paul Vorbach
https://vorba.ch/favicon.ico
Building Native Images of Kotlin Programs Using GraalVM
http://vorba.ch/2018/native-kotlin-on-graalvm.html
2018-12-23T00:00:00Z
Paul Vorbach
<p>In April of this year, Oracle announced <a href="https://www.graalvm.org/">GraalVM</a>, a new virtual machine that claims to run applications written in a diverse set of languages either in a runtime environment like OpenJDK or Node.js, but also as a standalone program.</p>
<p>The last part attracted my attention. This could be used to improve the startup time of Java applications. The startup time of the JVM is often taken as a reason not to use it for writing command-line applications. Consequently Jan Stępień showed only a few days after the announcement that you can <a href="https://www.innoq.com/en/blog/native-clojure-and-graalvm/">use GraalVM to build native binaries of Clojure applications</a>.</p>
<p>Now I wanted to try how easy GraalVM is to use with Kotlin, which is slowly becoming my new go-to language for JVM-based projects. Therefore I installed the latest version of GraalVM (1.0.0-rc-10), which was quite easy using <a href="https://sdkman.io/">SDKman</a>:</p>
<pre><code>$ sdk install java 1.0.0-rc-10-grl
$ sdk use java 1.0.0-rc-10-grl</code></pre>
<p>If you haven’t already done so, also install Kotlin:</p>
<pre><code>$ sdk install kotlin 1.3.11
$ sdk use kotlin 1.3.11</code></pre>
<p>Now I wrote the typical “hello world” in Kotlin (<code>main.kt</code>):</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode kotlin"><code class="sourceCode kotlin"><a class="sourceLine" id="cb3-1" title="1"><span class="kw">fun</span> <span class="fu">main</span>(<span class="kw">vararg</span> <span class="va">args</span>: <span class="dt">String</span>) {</a>
<a class="sourceLine" id="cb3-2" title="2"> println(<span class="st">"Hello, world"</span>)</a>
<a class="sourceLine" id="cb3-3" title="3">}</a></code></pre></div>
<p>Then we need to compile it:</p>
<pre><code>$ kotlinc main.kt -include-runtime -d main.jar</code></pre>
<p>And finally we’re able to build a native binary from it:</p>
<pre><code>$ native-image --static -jar main.jar
Build on Server(pid: 5484, port: 35163)
[main:5484] classlist: 441.34 ms
[main:5484] (cap): 1,017.18 ms
[main:5484] setup: 1,581.26 ms
[main:5484] (typeflow): 2,216.25 ms
[main:5484] (objects): 516.04 ms
[main:5484] (features): 86.21 ms
[main:5484] analysis: 2,876.19 ms
[main:5484] universe: 131.56 ms
[main:5484] (parse): 610.96 ms
[main:5484] (inline): 518.10 ms
[main:5484] (compile): 2,818.48 ms
[main:5484] compile: 4,187.55 ms
[main:5484] image: 354.83 ms
[main:5484] write: 164.75 ms
[main:5484] [total]: 9,769.44 ms</code></pre>
<p>This new native binary is about 3.4 MB in size, which is acceptable when you consider the alternative of having to bundle an entire JVM with your application. Also, the files in the JAR alone contain 4.6 MB of data. The compressed JAR file is only 1.2 MB in size.</p>
<p>So let’s run it:</p>
<pre><code>$ ./main
Hello, world</code></pre>
<p>That was smooth, wasn’t it? No JVM involved.</p>
<p>And if we compare startup time using <code>time</code>, we see that it only takes around three milliseconds to run:</p>
<pre><code>$ time ./main
Hello, world
./main 0.00s user 0.00s system 84% cpu 0.003 total</code></pre>
<p>Compare that to running the JAR file using the JVM:</p>
<pre><code>$ time java -jar main.jar
Hello, world
java -jar main.jar 0.07s user 0.01s system 107% cpu 0.079 total</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>I’ve shown that it is quite easily possible to create a command-line application using Kotlin and building a native binary from it using GraalVM. My next steps are to compile a project that does a little more than simply printing “Hello, world” and also have a look into how to debug such a native library. Additionally, a comparison to Kotlin/Native would make sense.</p>
<p>If you’re interested in playing around on your own with Kotlin and GraalVM, you can have a look at <a href="https://github.com/pvorb/graalvm-kotlin-native-image-sample">this little project</a> I created on GitHub. It uses Maven to automate the various steps involved.</p>
Improving URL Security with Identicons
http://vorba.ch/2018/url-security-identicons.html
2018-09-09T00:00:00Z
Paul Vorbach
<p>If you’ve followed the recent discussion about Chrome’s changes to displaying URLs in the address bar, you might want to directly switch to the <a href="#a-new-approach">second part of this blog post</a>.</p>
<h2 id="existing-problems-with-urls">Existing Problems with URLs</h2>
<p>With version 69, Google introduced a new feature for Chrome that strips subdomains of URLs in their address bar if Chrome considers them to be “trivial”. For example, when you enter the URL https://www.google.com, only google.com will be visible in Chrome’s address bar. The scheme part of the URL will also be hidden, only indicating whether you’re on HTTPS or not by a “Secure” or “Insecure” badge. But “www” is not the only subdomain that is considered trivial. The subdomain “m”, which is often used to serve a mobile version of a website, is also hidden. This way, https://en.m.wikipedia.org becomes en.wikipedia.org.</p>
<p>This change <a href="https://news.ycombinator.com/item?id=17927972">enraged many people on Hacker News</a> and, while some defended the decision, many called it an attack to the Domain Name System and standards.</p>
<p>One obvious reason for Google to strip down URLs in the address bar is to reduce horizontal space on mobile devices. Another reason to change this is to make URL usage more secure for end users. In recent years, many browsers started to highlight the hostname of URLs in the address bar by displaying the rest of the URL in gray color. Safari even goes a step further and only displays the root-level domain of a URL in the address bar. All of this was to make it obvious on which website users are.</p>
<p>In a recent story on Wired titled <a href="https://www.wired.com/story/google-wants-to-kill-the-url/">“Google Wants to Kill the URL,”</a> an engineering manager on the Google Chrome team, Adrienne Porter Felt, is cited as follows:</p>
<blockquote>
<p>“People have a really hard time understanding URLs. They are hard to read, it’s hard to know which part of them is supposed to be trusted, and in general I don’t think URLs are working as a good way to convey site identity. So we want to move toward a place where web identity is understandable by everyone – they know who they’re talking to when they’re using a website and they can reason about whether they can trust them. But this will mean big changes in how and when Chrome displays URLs. We want to challenge how URLs should be displayed and question it as we’re figuring out the right way to convey identity.”</p>
</blockquote>
<p>Personally, I have problems with the mindset of the inexperienced user. Of course there are many that have no idea how the world wide web and underlying technologies work and they have a right to safely and easily navigate the web without thinking about URLs. But on the other hand, URLs are an essential pillar of the web and the internet, along with HTTP, HTML and many other open standards, that are controlled by the <a href="https://ietf.org/">IETF</a> or <a href="https://www.w3.org/">W3C</a>. Hiding the technical details that drive the web from its users is making it easier for some that struggle to use technology in general, but it also keeps people from learning how the web works, because they don’t get in touch with the web’s rough edges. While it might be in Google’s interest to blur the boundaries of a search on Google and concrete web page, it can’t be good for the openness of the web if Google expands its position as a gatekeeper. Other of its initiatives, like <a href="https://www.ampproject.org/">Accelerated Mobile Pages (AMP)</a> go in a similar direction.</p>
<p>So let’s come back to one of the good reasons for changing how users experience and interact with URLs: the security aspect. In order to know if you’re on the website you want to be, you need to know which part of the URL is relevant for security. And even if you know that the use of HTTPS and the hostname (domain) are relevant, hostnames can be difficult to tell from each other, since they can contain Unicode characters through the use of Punycode (<a href="https://tools.ietf.org/html/rfc3492.html">RFC 3492</a>). The problem here is that there are so many <a href="https://en.wikipedia.org/wiki/Homoglyph">homoglyphs</a> in Unicode that look essentially the same as other characters.</p>
<p>For instance, a malicious entity could register the domain аⅿаzоn.com, which, depending on the font used to render this article, can’t be distinguished from amazon.com. This way, users could be tricked into entering their credentials on the malicious website. This problem isn’t really tackled by highlighting the domain. In fact, browser vendors started to block requests to websites that use glyphs from different ranges of Unicode.</p>
<h2 id="a-new-approach">A new approach</h2>
<p>When I started to think about these problems, I immediately thought of identicons. Identicons are often used by forum or commenting software to distinguish multiple authors of posts when they didn’t upload an avatar to their account. The principle behind identicons is simple: calculate a hash of some string of text and render an image with the use of multiple, pre-defined geometric figures. Add some color and your identicon is ready. Identicons were <a href="http://web.archive.org/web/20070206213620/http://www.docuverse.com/blog/donpark/2007/01/18/visual-security-9-block-ip-identification">invented by Don Park</a> in 2007 “as an easy means of visually distinguishing multiple units of information, anything that can be reduced to bits. It’s not just IPs but also people, places and things.” These are two examples of identicons I generated using <a href="https://github.com/donpark/identicon">Don Park’s original implementation</a>.</p>
<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAFdklEQVR42p2az2tVVxDHz59UcOe2a8Gd60L/gaLrrtyoi24KLYJgbIpKkGoW5ZGCwRLrz9rGkJC0ic/GhmCJKIIF21o74ZTJZM7M98yZy108knvf/Xxm3r1n5pxbPrj4pbcfnb3w3dOt499cAccsTLfe+9vXa4/XX+yBA+h08OUffXv9xq/rR2bOg2MKoH/0fJeusffnG+AABIieDvjk5iQnQPRv/v6LjqEgAoeC6esGHDyBSl93kARPgOnrBhxKlx47mAKSHifBFFD02KFE6IFDK6DocRJaAZMeOJQgveegBEx6kAQlAOg9hxKnNx2kgEcPkiAFuvSmQxmibx1YANN7SWCBIH3rUEbplUMV6NJ7SagCQ/TKoSTopQMRBOnNJNDpCXrpUB7s7rzPbuTw+Y/3g/RmEpZ+387R143G6XJyceGff9/lzp9dfTx5sjm/uXHs2uVcEigD9CW5q5M5ZW//Hsg50IX5HqDT4xoyCfUeSDhU+oOn0KhDpVeP0aCGTAI/hYYcmP7QOBB3YHpzJI5ocBLkOBB0kPR6JI44SHpQzGENToIaibsOit6ohbCDou/2A/RVi9vTE/NzXhLaWgg4tPR2Neo5tPRdAd5ajZoEsxo1HUx6tx9oHUz6uICpQUnw+gHl4NGjjkw6ePSjAkqDkgA6MnYA9EiAHQB9TkBqXFz5GXw5XRrT7wsQAdjv7DwD//3ip4dzG2tfrS57+2RK4/Q6OGDulzUMMLOyjA8oufhRBfXxZJ52gsBdOZ4ToNPxVegYKhbBMSWHXi9Pnz0BWWMCByzAFwIOJYdOO32mP5oCbYXsOQCBGn7ePYeSQOfwmwJefW86AIH2iqZDSaBz+FsB3J20Dp6ACj9wKKPoMvxKINJbKQdPAFxaOZRRdBl+KRDvDKWDKeCF33QoQ+gq/Cww2teygykQYWCHMoSuwl8Fcl15dWgFuuFXDiWO3oafNhpr0105OdA4nQi/dCinFhfiJ6jw03br2dP0nACZk38u/LRTKUUFVaHLxx3aORj6DeTmBGqVpn5CwfBX9IN7IOjQhp9v4lEHrjGlQCT8Ev3QTRxxMKfA+DEad5AVshTA4W/R9WMUO5jhVwNZxEHV9ywAwu+hG+MAcPBmIFUpgR3a7oQFzPBjdHskNh288JvFnOdg9lZVoA1/BN0tJVoHMAFsltOtg9cZVgEZ/jg6KuakAwi/J6AcQF9Lp3P4R9E71Sg74Pl30FJWB9yV0+kUoBz6/wKgX5482Tz9w/e4p76xtQF6dhqnaazFTf1nD++C77/527TT1OOV/tdv356E48OZu0ufLi1uv36Vix9+1eDsvdt4TgDNC3GNST8D4EBZOnbt8pGZ8zkNIED03TkBV0BVyMCBCOY3N+rnhIYnwPRdhxJc6fcciID+JafRhzRMAUWPHUp8pd90qFOLnIRRjVbApAcOZWilv3XgNTJzLaOroQQAvedQRlf6lQNP7rZJiGhIgS696VASK/3SgQW8JGANFgjStw4lt9LPDnJ6HSTB06gCQ/TKoaRX+quDWmYNLhWzBp2eoJcOZXXvj/RKP43TNNTLv0SSIDVmVpbTcwL1dYny4dVL01cv0yv9aoUmngTaz92/Taen33V49Hz36OyF/Xsg4cA1ZrvEFEwC0fM9kHCo9AdPoSEHWSGbK/XdJFR6+RQacmD6Q+NA0EHV9+YiH04C06txIOgg6fVI3HVouxNTACRB0rcjcddB0Ru1EHAweytvmdVMgqI3ayHg0NLb1ajp4HWG4GUPlYSW3qtGTQeT3u0HlAPoa8FCt0yCSQ/6AeXg0aOOjB1wVw4EOAkePe7I2AHQd141IAcap/FKP37VgJIA6Ls9MTk82N0B9LT/B1X4qlCCm6utAAAAAElFTkSuQmCC" alt="1" /> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAC00lEQVR42sXawU/UQBQG8Pdn+094N549mHjnYDbGixIjJEpARAQF3SzGJYu7BQLdLQzZWGvb+Wam733tZC4ku/D92G7nzbzKHRwX37Y/P3/kmyejJ4sfe0Wxuus68uv58cZj8Ccufx3h3yCdAS79Kr+Z7o7Gb57xDCzAOr17gQO4H3kGCqBMXwJ4BntANX0VQDIYA2rpawCGwRLQTN8EmBvMAK3pWwG2BhuAL70PYGgwAID0AGBlCAPcK8DMxvsg/YPweOv01VPfnH15qzRkkwOcUHClgNPHDOo67aZQ07tBXae9AKv0awDVINT0JYBnEGr6KoBkEGr6GoBhEGr6JsDcINT0rQBbg1DT+wCGBqGmB4C/hkJpEE36YpnnV/NgNQWmC4Hfvvi5Nzvc9M356Y50Tu/k7tMLFlvK4QoqXCuIJn1MuUsClFe+aNIPBah+b0WTfhBA7a4jmvRNgPtS4vJ9dXutATTvmaJJ3wTgo8gOn1gV0HrHF036PgG+9Uo06XsDgNVWNOn7AeBaQTTpewD83nmJawXB6S+O3if9vVRAsP8QrHQE/+8n714MBYisMgVfOUMB4mtkwdf9IICkCl/wt7Z/QOr+RPA9JxVws5gmbQCC/YcEQOsdMxWQOoL9h1iA737fG6DzzlbwatUPQLMvF7zW9gBQnirIn+8fprsj33R76uDRAN4ABN6enRfLXLMvF+X5zPL2Cp/f8z699bUj7PN7avp/R4s8AzX9f4e7JAM1ff14nWGgpm9pcJgbqOnbW0y2Bmp6b5PP0EBNj9qsVgZqegSwMlDTPwBw+Z5NDpSG80+vXUHlm5dnX3H6bLyPE4Yf9qCu07PDTWX/IepxG57BB4jvP8Q+8EQytAKS+g8Jj5wxDE1Aav8h7aE/c0MNEOw/aAHmhiogpv9gALA1lIDI/oMNwNCwBsT3H8wAVgYHSOo/WAJMDPOTj0n9h+a4B7WYzkuOuH4AAAAAAElFTkSuQmCC" alt="2" /></p>
<p>One valid issue with identicons was identified by Colin Davis, who created the service <a href="https://robohash.org/">Robohash</a> four years later and <a href="https://news.ycombinator.com/item?id=2743444">commented on Hacker News</a>:</p>
<blockquote>
<p>“Identicons are a great idea, I really love them.. They’re a good solution to a gut-check”Something is wrong here.."</p>
<p>Sort of like a SSH-fingerprint.</p>
<p>The problem I’ve had with them is that they’re generate not all that memorable. Was that triangles pointing left, then up, or up then left?</p>
<p>[Robohash] is my attempt at addressing that problem for my own new project […]”</p>
</blockquote>
<p>So my idea was to use identicons or similar for displaying a small and identifying icon next to an URL in the address bar of browsers in order to easily identify phishing attacks on your most important web services. These would need to be rendered by the browser directly without any possibility for website owners to change the appearance.</p>
<p>For example, here are the robohash images for amazon.com and its scam version next to it:</p>
<p><a href="https://robohash.org/amazon.com"><img src="/2018/robohash-amazon.com.png" alt="amazon.com" /></a> <a href="https://robohash.org/аⅿаzоn.com"><img src="/2018/robohash-amazon.com-scam.png" alt="аⅿаzоn.com" /></a></p>
<p>Easily distinguishable, right?</p>
<p>Remembering identicons for a small number of websites would be enough to safely navigate the web. If an identicon looked suspicious, you’d simply not enter your personal information on that website.</p>
<p>To make this work securely across the vast number of domains on the web is a little difficult, though. Depending on the algorithm used, most types of identicons can only have up to hundreds of millions of combinations. This is certainly not enough to be applied globally and there also will certainly be images that look very much like others. If two similar-looking identicons also had similar-lookin domains, this could get really dangerous. So to improve this, we would need to use state of the art hash algorithms like <a href="https://www.nist.gov/publications/sha-3-standard-permutation-based-hash-and-extendable-output-functions?pub_id=919061">SHA-3</a> and also use all resulting bits to change a feature of the identicon. In order to make this possible, multiple identicon types could be combined or new variations of existing types could be created.</p>
<p>I probably should start to develop a browser extension that implements this idea and look how this idea works in practice. If you think this idea would be worth giving a try, feel free to go ahead and implement something yourself!</p>
<p>What do you think about this idea? Let me know in the comments or on <a href="https://news.ycombinator.com/item?id=17947467">Hacker News</a>!</p>
Configuring the Spring Security JSP Taglib with FreeMarker for Spring Boot 2
http://vorba.ch/2018/spring-boot-freemarker-security-jsp-taglib.html
2018-09-01T00:00:00Z
Paul Vorbach
<p><strong>TL;DR:</strong> <em>Have a look at my <a href="https://github.com/pvorb/spring-boot-freemarker-spring-security-jsp-taglib-sample">sample project</a> for setting up a Spring Boot project with FreeMarker and the JSP taglib from Spring Security.</em></p>
<p>Currently, I’m working on a project that involves classic Spring WebMVC development using Spring Boot 2, because I want it to work without any JavaScript. For that project, I chose to use FreeMarker as a template library, as I prefer its syntax over Thymeleaf. Also one of the things I quite like is that you can, in some way, <a href="https://nickfun.github.io/posts/2014/freemarker-template-inheritance.html">inherit templates</a>. In my opinion this is more useful in practice than including the same header and footer snippets for every page individually.</p>
<p>Today I needed my template to render a username, if there was a successful authentication. Since my project is using Spring Security via the <code>spring-boot-starter-security</code> artifact, I looked for a solution that would make using authentication context information in a FreeMarker template easy. Of course this would be something many people did before me with that exact combination of tools.</p>
<p>As it turned out, I was wrong and this problem was harder than expected. It took me a while to search through the docs for various projects and also many questions on Stack Overflow, so I thought I’d share the path to my solution here and I hope it’ll save you some time.</p>
<p>Spring Security doesn’t come with a macro library for FreeMarker, but only <a href="https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/taglibs.html">a taglib for JSP</a>. But one of the great things about FreeMarker is that it supports using JSP taglibs. <a href="https://stackoverflow.com/a/28348350/432354">This answer on Stack Overflow</a> led me to an easy-looking solution: To simply include the following line into your template</p>
<pre><code><#assign security=JspTaglibs["http://www.springframework.org/security/tags"]/></code></pre>
<p>and then using the</p>
<pre><code><@security.authorize access="isAuthenticated()"></code></pre>
<p>macro.</p>
<p>This left me with the following exception:</p>
<pre><code>freemarker.ext.jsp.TaglibFactory$TaglibGettingException: No TLD was found for the "http://www.springframework.org/security/tags" JSP taglib URI. (TLD-s are searched according the JSP 2.2 specification. In development- and embedded-servlet-container setups you may also need the "MetaInfTldSources" and "ClasspathTlds" freemarker.ext.servlet.FreemarkerServlet init-params or the similar system properites.)</code></pre>
<p>So I thought the taglib was simply missing from the classpath and I began looking for a dependency that contained the correct *.tld file. And I was right! The following dependency contained the missing file.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb4-1" title="1"><span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb4-2" title="2"> <span class="kw"><groupId></span>org.springframework.security<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb4-3" title="3"> <span class="kw"><artifactId></span>spring-security-taglibs<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb4-4" title="4"><span class="kw"></dependency></span></a></code></pre></div>
<p>After including the dependency I got the following exception:</p>
<pre><code>freemarker.template.TemplateModelException: Error while looking for TLD file for "http://www.springframework.org/security/tags"; see cause exception.
[…]
Caused by: freemarker.ext.jsp.TaglibFactory$TaglibGettingException: No TLD was found for the "http://www.springframework.org/security/tags" JSP taglib URI. (TLD-s are searched according the JSP 2.2 specification. In development- and embedded-servlet-container setups you may also need the "MetaInfTldSources" and "ClasspathTlds" freemarker.ext.servlet.FreemarkerServlet init-params or the similar system properites.)</code></pre>
<p>Meh. That “caused by” exception was the exact same one as before. So FreeMarker still couldn’t find the taglib? What next?</p>
<p>I had a look in the classpath, where the file was located. It was located in the <code>spring-security-taglibs-5.0.7.RELEASE.jar</code> file under <code>/META-INF/security.tld</code>. But unfortunately, simply changing the previous assignment to</p>
<pre><code><#assign security=JspTaglibs["/META-INF/security.tld"]/></code></pre>
<p>or</p>
<pre><code><#assign security=JspTaglibs["classpath:/META-INF/security.tld"]/></code></pre>
<p>also didn’t work out.</p>
<p>As this didn’t lead anywhere, I looked for alternatives solutions. There had been a <a href="https://github.com/spring-projects/spring-security/issues/3275">request for a native FreeMarker macro library</a>, several years ago. Unfortunately this didn’t get picked up by the Spring Security team and in the end there were only some suggestions on how to use the <code>SPRING_SECURITY_CONTEXT</code> in your own macros.</p>
<p>This was when I thought about giving up and writing my own macros for Spring Security, but eventually I came across <a href="https://stackoverflow.com/a/33758469/432354">an answer on Stack Overflow by Andy Wilkinson</a>:</p>
<blockquote>
<p>[There’s a] possible workaround where you configure FreemarkerConfigurer’s tag lib factory with some additional TLDs to be loaded from the classpath:</p>
<pre><code>freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(…);</code></pre>
</blockquote>
<p>So I only had to define a list of *.tld files on the classpath? That was worth giving a try. I added the following code to the constructor of my <code>@SpringBootApplication</code> class.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb9-1" title="1"><span class="at">@SpringBootApplication</span></a>
<a class="sourceLine" id="cb9-2" title="2"><span class="kw">public</span> <span class="kw">class</span> FreemarkerJspTaglibSampleApplication {</a>
<a class="sourceLine" id="cb9-3" title="3"></a>
<a class="sourceLine" id="cb9-4" title="4"> <span class="kw">public</span> <span class="fu">FreemarkerJspTaglibSampleApplication</span>(FreeMarkerConfigurer freeMarkerConfigurer) {</a>
<a class="sourceLine" id="cb9-5" title="5"> freeMarkerConfigurer.<span class="fu">getTaglibFactory</span>().<span class="fu">setClasspathTlds</span>(<span class="fu">singletonList</span>(<span class="st">"/META-INF/security.tld"</span>));</a>
<a class="sourceLine" id="cb9-6" title="6"> }</a>
<a class="sourceLine" id="cb9-7" title="7"></a>
<a class="sourceLine" id="cb9-8" title="8"> <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) {</a>
<a class="sourceLine" id="cb9-9" title="9"> SpringApplication.<span class="fu">run</span>(FreemarkerJspTaglibSampleApplication.<span class="fu">class</span>, args);</a>
<a class="sourceLine" id="cb9-10" title="10"> }</a>
<a class="sourceLine" id="cb9-11" title="11"></a>
<a class="sourceLine" id="cb9-12" title="12">}</a></code></pre></div>
<p>My template still threw an exception. But wait, the exception had changed!</p>
<pre><code>freemarker.template.TemplateModelException: Error while loading tag library for URI "http://www.springframework.org/security/tags" from TLD location "classpath:/META-INF/security.tld"; see cause exception.
[…]
Caused by: freemarker.ext.jsp.TaglibFactory$TldParsingSAXException: Can't load class "org.springframework.security.taglibs.authz.JspAuthorizeTag" for custom tag "authorize".
[…]
Caused by: java.lang.Exception: Unchecked exception; see cause
[…]
Caused by: java.lang.NoClassDefFoundError: javax/servlet/jsp/tagext/Tag
[…]
Caused by: java.lang.ClassNotFoundException: javax.servlet.jsp.tagext.Tag</code></pre>
<p>That was a huge step forward! FreeMarker finally found the <code>security.tld</code> file, but now couldn’t use it to render the template because of a missing class.</p>
<p>The only thing I had to do now was including the following dependency in my POM file. (Users of other servlet containers will have to use an equivalent library for their container.)</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb11-1" title="1"><span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb11-2" title="2"> <span class="kw"><groupId></span>org.apache.tomcat<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb11-3" title="3"> <span class="kw"><artifactId></span>tomcat-jsp-api<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb11-4" title="4"><span class="kw"></dependency></span></a></code></pre></div>
<p>After that change, the taglib finally worked and I could use it in my templates. The way to use it in your templates is by including the variant with the full TLD URI:</p>
<pre><code><#assign security=JspTaglibs["http://www.springframework.org/security/tags"]/>
<@security.authorize access="isAuthenticated()">
<@security.authentication property="principal.username"/>
</@security.authorize></code></pre>
<p>Since the information is a bit scattered across this article, I compiled a <a href="https://github.com/pvorb/spring-boot-freemarker-spring-security-jsp-taglib-sample">minimal sample project</a> where you can look up the details on how to get everything up and running.</p>
Migration von Bread zu Sokrates
http://vorba.ch/2018/bread-sokrates-migration.html
2018-08-19T00:00:00Z
Paul Vorbach
<p><img src="/2018/sokrates.jpg"></p>
<p>Gestern habe ich meinen vorherigen, selbstgeschriebenen <em>Static-Site-Generator</em> Bread<a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a> durch <a href="https://github.com/pvorb/sokrates">Sokrates</a> abgelöst. Die Software funktioniert weitestgehend gleich, ist jedoch in Java geschrieben, was seit einiger Zeit eher meiner täglichen Arbeit entspricht.</p>
<p>Im Gegensatz zu Bread setzt Sokrates auf die SQL-Datenbank <a href="https://www.h2database.com">H2</a> statt auf MongoDB. Das bringt den Vorteil mit sich, für die Generierung der Site keinen Datenbank-Dienst laufen lassen zu müssen. Während der letzten Jahre musste ich MongoDB immer wieder neu aufsetzen und Bread an die jeweils neueste MongoDB-Version anpassen, um Änderungen an diesem Blog vornehmen zu können. Darauf hatte ich keine Lust mehr, da ich auch das Interesse an MongoDB im Allgemeinen verloren habe.</p>
<p>H2 hingegen ist eine in Java geschriebene relationale Datenbank, die ähnlich zu Sqlite In-Memory oder embedded betrieben werden kann. Das bedeutet, der komplette Inhalt der Datenbank liegt entweder im Speicher oder in einer einzigen Datei (embedded), die jederzeit wieder verworfen und neu generiert werden kann. Das und die Mächtigkeit von SQL und die Kompatibilität zum ganzen Tooling rund um SQL waren der Grund für den Einsatz in Sokrates.</p>
<p>Da ich bei der täglichen Arbeit Spring Boot einsetze, baut Sokrates auf Spring Boot als Dependency-Injection-Framework auf. Die Datenbank-Schemaverwaltung übernimmt <a href="https://boxfuse.org/flyway">Flyway</a> und SQL-Queries sind mit <a href="https://jooq.org">jOOQ</a> geschrieben. Als Template-Engine kommt <a href="https://freemarker.apache.org">Freemarker</a> zum Einsatz und das Übersetzen von Markdown in HTML übernimmt wieder <a href="https://pandoc.org">Pandoc</a>.</p>
<h2 id="features">Features</h2>
<h3 id="einfache-konfiguration-per-yaml-datei">Einfache Konfiguration per YAML-Datei</h3>
<p>Das hier ist die aktuelle Konfiguration dieses Blogs:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode yaml"><code class="sourceCode yaml"><a class="sourceLine" id="cb1-1" title="1"><span class="fu">logging.level:</span></a>
<a class="sourceLine" id="cb1-2" title="2"> <span class="fu">root:</span><span class="at"> WARN</span></a>
<a class="sourceLine" id="cb1-3" title="3"> <span class="fu">de.vorb.sokrates:</span><span class="at"> INFO</span></a>
<a class="sourceLine" id="cb1-4" title="4"></a>
<a class="sourceLine" id="cb1-5" title="5"><span class="fu">spring.datasource:</span></a>
<a class="sourceLine" id="cb1-6" title="6"> <span class="fu">url:</span><span class="at"> jdbc:h2:./sokrates</span></a>
<a class="sourceLine" id="cb1-7" title="7"> <span class="fu">driver-class-name:</span><span class="at"> org.h2.Driver</span></a>
<a class="sourceLine" id="cb1-8" title="8"> <span class="fu">username:</span><span class="at"> sa</span></a>
<a class="sourceLine" id="cb1-9" title="9"> <span class="fu">password:</span></a>
<a class="sourceLine" id="cb1-10" title="10"></a>
<a class="sourceLine" id="cb1-11" title="11"><span class="fu">sokrates:</span></a>
<a class="sourceLine" id="cb1-12" title="12"> <span class="fu">site:</span></a>
<a class="sourceLine" id="cb1-13" title="13"> <span class="fu">title:</span><span class="at"> vorba.ch</span></a>
<a class="sourceLine" id="cb1-14" title="14"> <span class="fu">subtitle:</span><span class="at"> Paul’s personal blog about software and technology</span></a>
<a class="sourceLine" id="cb1-15" title="15"> <span class="fu">default-locale:</span><span class="at"> en-US</span></a>
<a class="sourceLine" id="cb1-16" title="16"> <span class="fu">public-url:</span><span class="at"> https://vorba.ch</span></a>
<a class="sourceLine" id="cb1-17" title="17"> <span class="fu">author:</span><span class="at"> Paul Vorbach</span></a>
<a class="sourceLine" id="cb1-18" title="18"> <span class="fu">author-url:</span><span class="at"> https://paul.vorba.ch</span></a>
<a class="sourceLine" id="cb1-19" title="19"> <span class="fu">translations:</span><span class="at"> src/site/resources/translations</span></a>
<a class="sourceLine" id="cb1-20" title="20"> <span class="fu">properties:</span></a>
<a class="sourceLine" id="cb1-21" title="21"> <span class="fu">hostname:</span><span class="at"> vorba.ch</span></a>
<a class="sourceLine" id="cb1-22" title="22"> <span class="fu">feed:</span></a>
<a class="sourceLine" id="cb1-23" title="23"> <span class="fu">id:</span><span class="at"> http://vorb.de/log/feed.xml</span></a>
<a class="sourceLine" id="cb1-24" title="24"> <span class="fu">categories:</span></a>
<a class="sourceLine" id="cb1-25" title="25"> <span class="kw">-</span> computer</a>
<a class="sourceLine" id="cb1-26" title="26"> <span class="kw">-</span> software</a>
<a class="sourceLine" id="cb1-27" title="27"> <span class="kw">-</span> development</a>
<a class="sourceLine" id="cb1-28" title="28"> <span class="kw">-</span> software engineering</a>
<a class="sourceLine" id="cb1-29" title="29"> <span class="kw">-</span> java</a>
<a class="sourceLine" id="cb1-30" title="30"> <span class="kw">-</span> kotlin</a>
<a class="sourceLine" id="cb1-31" title="31"> <span class="fu">directory:</span></a>
<a class="sourceLine" id="cb1-32" title="32"> <span class="fu">output:</span><span class="at"> target/sokrates/</span></a>
<a class="sourceLine" id="cb1-33" title="33"> <span class="fu">templates:</span><span class="at"> src/site/resources/templates/</span></a>
<a class="sourceLine" id="cb1-34" title="34"> <span class="fu">generator:</span></a>
<a class="sourceLine" id="cb1-35" title="35"> <span class="fu">pandoc-executable:</span><span class="at"> /usr/bin/pandoc</span></a>
<a class="sourceLine" id="cb1-36" title="36"> <span class="fu">extension-mapping:</span></a>
<a class="sourceLine" id="cb1-37" title="37"> <span class="fu">md:</span><span class="at"> html</span></a>
<a class="sourceLine" id="cb1-38" title="38"> <span class="fu">generate-rules:</span></a>
<a class="sourceLine" id="cb1-39" title="39"> <span class="kw">-</span> <span class="fu">pattern:</span><span class="at"> src/site/resources/posts/**/*.md</span></a>
<a class="sourceLine" id="cb1-40" title="40"> <span class="fu">base-directory:</span><span class="at"> src/site/resources/posts</span></a>
<a class="sourceLine" id="cb1-41" title="41"> <span class="fu">format:</span><span class="at"> markdown</span></a>
<a class="sourceLine" id="cb1-42" title="42"> <span class="fu">copy-rules:</span></a>
<a class="sourceLine" id="cb1-43" title="43"> <span class="kw">-</span> <span class="fu">pattern:</span><span class="at"> src/site/resources/posts/**/*</span></a>
<a class="sourceLine" id="cb1-44" title="44"> <span class="fu">base-directory:</span><span class="at"> src/site/resources/posts</span></a>
<a class="sourceLine" id="cb1-45" title="45"> <span class="kw">-</span> <span class="fu">pattern:</span><span class="at"> src/site/resources/tag/**/*</span></a>
<a class="sourceLine" id="cb1-46" title="46"> <span class="fu">base-directory:</span><span class="at"> src/site/resources</span></a>
<a class="sourceLine" id="cb1-47" title="47"> <span class="kw">-</span> <span class="fu">pattern:</span><span class="at"> src/site/resources/static/**/*</span></a>
<a class="sourceLine" id="cb1-48" title="48"> <span class="fu">base-directory:</span><span class="at"> src/site/resources/static</span></a>
<a class="sourceLine" id="cb1-49" title="49"> <span class="fu">tag-rule:</span></a>
<a class="sourceLine" id="cb1-50" title="50"> <span class="fu">source-file-pattern:</span><span class="at"> src/site/resources/tag/%s.md</span></a>
<a class="sourceLine" id="cb1-51" title="51"> <span class="fu">output-file-pattern:</span><span class="at"> tag/%s.html</span></a>
<a class="sourceLine" id="cb1-52" title="52"> <span class="fu">format:</span><span class="at"> markdown</span></a>
<a class="sourceLine" id="cb1-53" title="53"> <span class="fu">template:</span><span class="at"> tag.ftl</span></a>
<a class="sourceLine" id="cb1-54" title="54"> <span class="fu">index-output-file:</span><span class="at"> tags/index.html</span></a>
<a class="sourceLine" id="cb1-55" title="55"> <span class="fu">index-template:</span><span class="at"> tag-index.ftl</span></a>
<a class="sourceLine" id="cb1-56" title="56"> <span class="fu">indexes:</span></a>
<a class="sourceLine" id="cb1-57" title="57"> <span class="kw">-</span> <span class="fu">name:</span><span class="at"> Blog index</span></a>
<a class="sourceLine" id="cb1-58" title="58"> <span class="fu">title:</span><span class="at"> Blog index</span></a>
<a class="sourceLine" id="cb1-59" title="59"> <span class="fu">template:</span><span class="at"> index.ftl</span></a>
<a class="sourceLine" id="cb1-60" title="60"> <span class="fu">output-file:</span><span class="at"> index.html</span></a>
<a class="sourceLine" id="cb1-61" title="61"> <span class="fu">order-by:</span></a>
<a class="sourceLine" id="cb1-62" title="62"> <span class="kw">-</span> created_at DESC</a>
<a class="sourceLine" id="cb1-63" title="63"> <span class="fu">grouping:</span><span class="at"> BY_YEAR_CREATED</span></a>
<a class="sourceLine" id="cb1-64" title="64"> <span class="kw">-</span> <span class="fu">name:</span><span class="at"> Feed</span></a>
<a class="sourceLine" id="cb1-65" title="65"> <span class="fu">title:</span><span class="at"> Atom feed</span></a>
<a class="sourceLine" id="cb1-66" title="66"> <span class="fu">template:</span><span class="at"> feed.ftl</span></a>
<a class="sourceLine" id="cb1-67" title="67"> <span class="fu">output-file:</span><span class="at"> feed.xml</span></a>
<a class="sourceLine" id="cb1-68" title="68"> <span class="fu">order-by:</span></a>
<a class="sourceLine" id="cb1-69" title="69"> <span class="kw">-</span> created_at DESC</a>
<a class="sourceLine" id="cb1-70" title="70"> <span class="fu">limit:</span><span class="at"> </span><span class="dv">10</span></a></code></pre></div>
<p>Interessant sind in erster Linie die Properties unterhalb von <code>sokrates</code>. Unter <code>sokrates.site</code> finden sich Metadaten zur Website. Unter <code>sokrates.generator</code> werden Regeln zum Übersetzen der Blog-Posts und Tags sowie zum Kopieren statischer Dateien definiert. Unter <code>sokrates.indexes</code> lassen sich Indexseiten definieren. Das sind einerseits die <code>index.html</code> und andererseits der Artikel-Feed unter <a href="/feed.xml"><code>feed.xml</code></a>.</p>
<h3 id="übersetzung-der-inhalte">Übersetzung der Inhalte</h3>
<p>Beim Übersetzen der Inhalte hat sich eigentlich nicht viel im Vergleich zu Bread geändert. Es wird nach wie vor Pandoc zum Übersetzen von Markdown zu HTML eingesetzt. Ich hatte bei der Entwicklung kurzzeitig überlegt, direkt <a href="https://commonmark.org">CommonMark</a> einzusetzen, mich dann jedoch vorerst dagegen entschieden, um die Migration dieses Blogs einfacher zu halten. Langfristig ergibt der Einsatz von CommonMark jedoch durchaus Sinn.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode markdown"><code class="sourceCode markdown"><a class="sourceLine" id="cb2-1" title="1">---</a>
<a class="sourceLine" id="cb2-2" title="2">title: Migration von Bread zu Sokrates</a>
<a class="sourceLine" id="cb2-3" title="3">alias: bread-sokrates-migration.md</a>
<a class="sourceLine" id="cb2-4" title="4"></a>
<a class="sourceLine" id="cb2-5" title="5">author: Paul Vorbach</a>
<a class="sourceLine" id="cb2-6" title="6">created-at: 2018-08-19</a>
<a class="sourceLine" id="cb2-7" title="7"></a>
<a class="sourceLine" id="cb2-8" title="8">tags: [ deutsch, self, bread, sokrates, cms, weblog ]</a>
<a class="sourceLine" id="cb2-9" title="9">locale: de-DE</a>
<a class="sourceLine" id="cb2-10" title="10"></a>
<a class="sourceLine" id="cb2-11" title="11">template: post-corristo.ftl</a>
<a class="sourceLine" id="cb2-12" title="12">teaser:</a>
<a class="sourceLine" id="cb2-13" title="13"> image-url: /2018/sokrates.jpg</a>
<a class="sourceLine" id="cb2-14" title="14"></a>
<a class="sourceLine" id="cb2-15" title="15">properties:</a>
<a class="sourceLine" id="cb2-16" title="16"> highlight: "#43679C"</a>
<a class="sourceLine" id="cb2-17" title="17">...</a>
<a class="sourceLine" id="cb2-18" title="18"></a>
<a class="sourceLine" id="cb2-19" title="19">Selbstbezogene Blog-Posts schreiben sich immer am leichtesten. ;-)</a></code></pre></div>
<p>Die Metadaten zum jeweiligen Blog-Post werden in einem sog. YAML-Front-Matter angegeben. Hierdurch bestimmen sich der Titel, die Sprache, die verlinkten Tags, das Template usw.</p>
<h2 id="ausführung-über-die-commandline-oder-maven">Ausführung über die Commandline oder Maven</h2>
<p>Ursprünglich hatte ich auch überlegt, direkt ein Maven-Plugin für Sokrates zu schreiben, da ich plante, Maven als Build-Tool für dieses Blog einzusetzen. Nach <a href="https://stackoverflow.com/q/48085705/432354">anfänglichen Schwierigkeiten, Spring Boot in einem Maven-Plugin einzusetzen</a>, habe ich den Rat von Maven PMC-Member Karl Heinz Marbaise befolgt und mich für die Integration per <a href="https://www.mojohaus.org/exec-maven-plugin/"><code>maven-exec-plugin</code></a> entschieden. Nachfolgend der relevante Auszug aus der <code>pom.xml</code>.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb3-1" title="1"><span class="kw"><dependencies></span></a>
<a class="sourceLine" id="cb3-2" title="2"> <span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb3-3" title="3"> <span class="kw"><groupId></span>de.vorb.sokrates<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb3-4" title="4"> <span class="kw"><artifactId></span>sokrates<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb3-5" title="5"> <span class="kw"><version></span>0.1.1<span class="kw"></version></span></a>
<a class="sourceLine" id="cb3-6" title="6"> <span class="kw"></dependency></span></a>
<a class="sourceLine" id="cb3-7" title="7"><span class="kw"></dependencies></span></a>
<a class="sourceLine" id="cb3-8" title="8"></a>
<a class="sourceLine" id="cb3-9" title="9"><span class="kw"><build></span></a>
<a class="sourceLine" id="cb3-10" title="10"> <span class="kw"><plugins></span></a>
<a class="sourceLine" id="cb3-11" title="11"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb3-12" title="12"> <span class="kw"><groupId></span>org.codehaus.mojo<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb3-13" title="13"> <span class="kw"><artifactId></span>exec-maven-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb3-14" title="14"> <span class="kw"><version></span>1.6.0<span class="kw"></version></span></a>
<a class="sourceLine" id="cb3-15" title="15"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb3-16" title="16"> <span class="kw"><includeProjectDependencies></span>true<span class="kw"></includeProjectDependencies></span></a>
<a class="sourceLine" id="cb3-17" title="17"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb3-18" title="18"> <span class="kw"><executions></span></a>
<a class="sourceLine" id="cb3-19" title="19"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb3-20" title="20"> <span class="kw"><id></span>generate-site<span class="kw"></id></span></a>
<a class="sourceLine" id="cb3-21" title="21"> <span class="kw"><phase></span>prepare-package<span class="kw"></phase></span></a>
<a class="sourceLine" id="cb3-22" title="22"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb3-23" title="23"> <span class="kw"><goal></span>exec<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb3-24" title="24"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb3-25" title="25"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb3-26" title="26"> <span class="kw"><executable></span>java<span class="kw"></executable></span></a>
<a class="sourceLine" id="cb3-27" title="27"> <span class="kw"><arguments></span></a>
<a class="sourceLine" id="cb3-28" title="28"> <span class="kw"><argument></span>-classpath<span class="kw"></argument></span></a>
<a class="sourceLine" id="cb3-29" title="29"> <span class="kw"><classpath/></span></a>
<a class="sourceLine" id="cb3-30" title="30"> <span class="kw"><argument></span>de.vorb.sokrates.app.SokratesApp<span class="kw"></argument></span></a>
<a class="sourceLine" id="cb3-31" title="31"> <span class="kw"><argument></span>generate<span class="kw"></argument></span></a>
<a class="sourceLine" id="cb3-32" title="32"> <span class="kw"><argument></span>--force<span class="kw"></argument></span></a>
<a class="sourceLine" id="cb3-33" title="33"> <span class="kw"></arguments></span></a>
<a class="sourceLine" id="cb3-34" title="34"> <span class="kw"><workingDirectory></span>${project.basedir}<span class="kw"></workingDirectory></span></a>
<a class="sourceLine" id="cb3-35" title="35"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb3-36" title="36"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb3-37" title="37"> <span class="kw"></executions></span></a>
<a class="sourceLine" id="cb3-38" title="38"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb3-39" title="39"> <span class="kw"></plugins></span></a>
<a class="sourceLine" id="cb3-40" title="40"><span class="kw"></build></span></a></code></pre></div>
<p>Alternativ kann man sich auch direkt das Fat-JAR bauen und es per <code>java -jar sokrates-0.1.1.jar generate</code> ausführen. Selbst bevorzuge ich jedoch den Weg über Maven, da man sich so nicht merken muss, wie das Tool aufzurufen ist. Ein einfaches <code>mvn package</code> genügt, um das Blog zu bauen.</p>
<h2 id="fazit">Fazit</h2>
<p>Wer sich den <a href="https://www.staticgen.com">Markt der Static-Site-Generatoren</a> ansieht, wird das alles hier für Zeitverschwendung halten und das ist es mit Sicherheit auch – schließlich gibt es eine Unzahl an Generatoren in allen erdenklichen Sprachen. Aber ich fand es amüsanter, das Tool zu schreiben, als mich in eine der Alternativen hinreichend einzuarbeiten, um dieses Blog ohne großen Anpassungen an der URL-Struktur migrieren zu können. Außerdem sind mir so keine Grenzen gesetzt, was zukünftige Features angeht und ich kann weiterhin sagen, die Software hinter meiner Website selbst geschrieben zu haben.</p>
<p>Den Einsatz für das eigene Blog würde ich anderen jedoch nicht empfehlen, da es Sokrates wie üblich an Dokumentation und Support mangelt. Wer sich aber dafür interessiert, kann sich gerne einmal das <a href="https://github.com/pvorb/sokrates">Git-Repository</a> selbst und das <a href="https://github.com/pvorb/vorba.ch">Repository zu diesem Blog</a> auf GitHub ansehen.</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p><a href="/2012/bread.html">Einführungsartikel zu Bread</a> und <a href="https://github.com/pvorb/node-bread">Repository auf GitHub</a>.<a href="#fnref1" class="footnote-back">↩</a></p></li>
</ol>
</section>
Living up to Your own Standards
http://vorba.ch/2017/jacoco-coverage-threshold.html
2017-12-23T00:00:00Z
Paul Vorbach
<p>My reader Rebeca asked me yesterday if I could post about how to let your test suite fail if it falls below a certain threshold. So here’s how you can do this using the JaCoCo plugins for Maven and Gradle.</p>
<h2 id="maven">Maven</h2>
<p>You can configure a rule in <code>jacoco-maven-plugin</code> as follows.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb1-1" title="1"><span class="kw"><build></span></a>
<a class="sourceLine" id="cb1-2" title="2"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb1-3" title="3"> <span class="kw"><groupId></span>org.jacoco<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-4" title="4"> <span class="kw"><artifactId></span>jacoco-maven-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-5" title="5"> <span class="kw"><version></span>0.7.9<span class="kw"></version></span></a>
<a class="sourceLine" id="cb1-6" title="6"> <span class="kw"><executions></span></a>
<a class="sourceLine" id="cb1-7" title="7"> <span class="co"><!-- ... --></span></a>
<a class="sourceLine" id="cb1-8" title="8"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb1-9" title="9"> <span class="kw"><id></span>check<span class="kw"></id></span></a>
<a class="sourceLine" id="cb1-10" title="10"> <span class="kw"><phase></span>test<span class="kw"></phase></span></a>
<a class="sourceLine" id="cb1-11" title="11"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb1-12" title="12"> <span class="kw"><goal></span>check<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb1-13" title="13"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb1-14" title="14"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb1-15" title="15"> <span class="kw"><rules></span></a>
<a class="sourceLine" id="cb1-16" title="16"> <span class="kw"><rule</span><span class="ot"> implementation=</span><span class="st">"org.jacoco.maven.RuleConfiguration"</span><span class="kw">></span></a>
<a class="sourceLine" id="cb1-17" title="17"> <span class="kw"><element></span>BUNDLE<span class="kw"></element></span></a>
<a class="sourceLine" id="cb1-18" title="18"> <span class="kw"><limits></span></a>
<a class="sourceLine" id="cb1-19" title="19"> <span class="kw"><limit</span><span class="ot"> implementation=</span><span class="st">"org.jacoco.report.check.Limit"</span><span class="kw">></span></a>
<a class="sourceLine" id="cb1-20" title="20"> <span class="kw"><counter></span>INSTRUCTION<span class="kw"></counter></span></a>
<a class="sourceLine" id="cb1-21" title="21"> <span class="kw"><value></span>COVEREDRATIO<span class="kw"></value></span></a>
<a class="sourceLine" id="cb1-22" title="22"> <span class="kw"><minimum></span>0.80<span class="kw"></minimum></span></a>
<a class="sourceLine" id="cb1-23" title="23"> <span class="kw"></limit></span></a>
<a class="sourceLine" id="cb1-24" title="24"> <span class="kw"></limits></span></a>
<a class="sourceLine" id="cb1-25" title="25"> <span class="kw"></rule></span></a>
<a class="sourceLine" id="cb1-26" title="26"> <span class="kw"></rules></span></a>
<a class="sourceLine" id="cb1-27" title="27"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb1-28" title="28"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb1-29" title="29"> <span class="kw"></executions></span></a>
<a class="sourceLine" id="cb1-30" title="30"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb1-31" title="31"><span class="kw"></build></span></a></code></pre></div>
<p>Now, if your test suite falls below 80% instruction coverage, the build will fail with the following message:</p>
<pre><code>$ mvn verify
...
[ERROR] Failed to execute goal org.jacoco:jacoco-maven-plugin:0.7.9:check (check) on project platon: Coverage checks have not been met. See log for details. -> [Help 1]</code></pre>
<p>There are many other possible rules for the check mojo <a href="http://www.jacoco.org/jacoco/trunk/doc/check-mojo.html">documented in the official JaCoCo documentation</a>.</p>
<h2 id="gradle">Gradle</h2>
<p>Configuring these rules is even simpler for Gradle.</p>
<pre class="groovy"><code>apply plugin: 'jacoco'
// ...
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.9
}
}
}
}
jacocoTestCoverageVerification.dependsOn(jacocoTestReport)
check.dependsOn(jacocoTestCoverageVerification)</code></pre>
<p>Here’s what you get when you run <code>gradle check</code> and test coverage is low.</p>
<pre><code>$ gradle check
...
:jacocoTestReport
[ant:jacocoReport] Rule violated for bundle property-providers: instructions covered ratio is 0.7, but expected minimum is 0.8
:jacocoTestCoverageVerification FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':jacocoTestCoverageVerification'.
> Rule violated for bundle property-providers: instructions covered ratio is 0.7, but expected minimum is 0.8
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 14s</code></pre>
<p>You can find more configuration examples in <a href="https://docs.gradle.org/current/userguide/jacoco_plugin.html#sec:jacoco_report_violation_rules">Gradle’s JaCoCo plugin documentation</a>.</p>
<p>I hope this guide will help one or the other trying to achieve something similar.</p>
An Introduction to Project Lombok
http://vorba.ch/2017/lombok.html
2017-08-03T00:00:00Z
Paul Vorbach
<p><img src="/2017/lombok.jpg"></p>
<p>The following map shows the island Lombok’s location in south east Asia. You already know its major neighbor in the west, the one with Indonesia’s capital, Jakarta, on it. Right, that’s <em>Java</em>.</p>
<figure>
<img src="/2017/lombok-map.png" alt="Lombok on a map of Indonesia and Malaysia" /><figcaption>Lombok on a map of Indonesia and Malaysia</figcaption>
</figure>
<p>But this article is not about islands or geography. It’s is about <a href="https://projectlombok.org/">Project Lombok</a>, a plugin to the Java compiler, that allows to automatically generate bytecode for much of the boilerplate that is required when writing Java code.</p>
<p>Here’s a typical Java POJO:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb1-1" title="1"><span class="kw">public</span> <span class="kw">class</span> Movie {</a>
<a class="sourceLine" id="cb1-2" title="2"></a>
<a class="sourceLine" id="cb1-3" title="3"> <span class="kw">private</span> <span class="bu">Long</span> id;</a>
<a class="sourceLine" id="cb1-4" title="4"> <span class="kw">private</span> <span class="bu">String</span> title;</a>
<a class="sourceLine" id="cb1-5" title="5"> <span class="kw">private</span> <span class="bu">String</span> subtitle;</a>
<a class="sourceLine" id="cb1-6" title="6"> <span class="kw">private</span> <span class="bu">String</span> genre;</a>
<a class="sourceLine" id="cb1-7" title="7"> <span class="kw">private</span> <span class="bu">String</span> director;</a>
<a class="sourceLine" id="cb1-8" title="8"> <span class="kw">private</span> LocalDate firstRelease;</a>
<a class="sourceLine" id="cb1-9" title="9"> <span class="kw">private</span> <span class="bu">List</span><Actor> actors;</a>
<a class="sourceLine" id="cb1-10" title="10"></a>
<a class="sourceLine" id="cb1-11" title="11"> <span class="kw">public</span> <span class="bu">Long</span> <span class="fu">getId</span>() {</a>
<a class="sourceLine" id="cb1-12" title="12"> <span class="kw">return</span> id;</a>
<a class="sourceLine" id="cb1-13" title="13"> }</a>
<a class="sourceLine" id="cb1-14" title="14"></a>
<a class="sourceLine" id="cb1-15" title="15"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setId</span>(<span class="bu">Long</span> id) {</a>
<a class="sourceLine" id="cb1-16" title="16"> <span class="kw">this</span>.<span class="fu">id</span> = id;</a>
<a class="sourceLine" id="cb1-17" title="17"> }</a>
<a class="sourceLine" id="cb1-18" title="18"></a>
<a class="sourceLine" id="cb1-19" title="19"> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">getTitle</span>() {</a>
<a class="sourceLine" id="cb1-20" title="20"> <span class="kw">return</span> title;</a>
<a class="sourceLine" id="cb1-21" title="21"> }</a>
<a class="sourceLine" id="cb1-22" title="22"></a>
<a class="sourceLine" id="cb1-23" title="23"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setTitle</span>(<span class="bu">String</span> title) {</a>
<a class="sourceLine" id="cb1-24" title="24"> <span class="kw">this</span>.<span class="fu">title</span> = title;</a>
<a class="sourceLine" id="cb1-25" title="25"> }</a>
<a class="sourceLine" id="cb1-26" title="26"></a>
<a class="sourceLine" id="cb1-27" title="27"> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">getSubtitle</span>() {</a>
<a class="sourceLine" id="cb1-28" title="28"> <span class="kw">return</span> subtitle;</a>
<a class="sourceLine" id="cb1-29" title="29"> }</a>
<a class="sourceLine" id="cb1-30" title="30"></a>
<a class="sourceLine" id="cb1-31" title="31"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setSubtitle</span>(<span class="bu">String</span> subtitle) {</a>
<a class="sourceLine" id="cb1-32" title="32"> <span class="kw">this</span>.<span class="fu">subtitle</span> = subtitle;</a>
<a class="sourceLine" id="cb1-33" title="33"> }</a>
<a class="sourceLine" id="cb1-34" title="34"></a>
<a class="sourceLine" id="cb1-35" title="35"> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">getGenre</span>() {</a>
<a class="sourceLine" id="cb1-36" title="36"> <span class="kw">return</span> genre;</a>
<a class="sourceLine" id="cb1-37" title="37"> }</a>
<a class="sourceLine" id="cb1-38" title="38"></a>
<a class="sourceLine" id="cb1-39" title="39"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setGenre</span>(<span class="bu">String</span> genre) {</a>
<a class="sourceLine" id="cb1-40" title="40"> <span class="kw">this</span>.<span class="fu">genre</span> = genre;</a>
<a class="sourceLine" id="cb1-41" title="41"> }</a>
<a class="sourceLine" id="cb1-42" title="42"></a>
<a class="sourceLine" id="cb1-43" title="43"> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">getDirector</span>() {</a>
<a class="sourceLine" id="cb1-44" title="44"> <span class="kw">return</span> director;</a>
<a class="sourceLine" id="cb1-45" title="45"> }</a>
<a class="sourceLine" id="cb1-46" title="46"></a>
<a class="sourceLine" id="cb1-47" title="47"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setDirector</span>(<span class="bu">String</span> director) {</a>
<a class="sourceLine" id="cb1-48" title="48"> <span class="kw">this</span>.<span class="fu">director</span> = director;</a>
<a class="sourceLine" id="cb1-49" title="49"> }</a>
<a class="sourceLine" id="cb1-50" title="50"></a>
<a class="sourceLine" id="cb1-51" title="51"> <span class="kw">public</span> LocalDate <span class="fu">getFirstRelease</span>() {</a>
<a class="sourceLine" id="cb1-52" title="52"> <span class="kw">return</span> firstRelease;</a>
<a class="sourceLine" id="cb1-53" title="53"> }</a>
<a class="sourceLine" id="cb1-54" title="54"></a>
<a class="sourceLine" id="cb1-55" title="55"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setFirstRelease</span>(LocalDate firstRelease) {</a>
<a class="sourceLine" id="cb1-56" title="56"> <span class="kw">this</span>.<span class="fu">firstRelease</span> = firstRelease;</a>
<a class="sourceLine" id="cb1-57" title="57"> }</a>
<a class="sourceLine" id="cb1-58" title="58"></a>
<a class="sourceLine" id="cb1-59" title="59"> <span class="kw">public</span> <span class="bu">List</span><Actor> <span class="fu">getActors</span>() {</a>
<a class="sourceLine" id="cb1-60" title="60"> <span class="kw">return</span> actors;</a>
<a class="sourceLine" id="cb1-61" title="61"> }</a>
<a class="sourceLine" id="cb1-62" title="62"></a>
<a class="sourceLine" id="cb1-63" title="63"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setActors</span>(<span class="bu">List</span><Actor> actors) {</a>
<a class="sourceLine" id="cb1-64" title="64"> <span class="kw">this</span>.<span class="fu">actors</span> = actors;</a>
<a class="sourceLine" id="cb1-65" title="65"> }</a>
<a class="sourceLine" id="cb1-66" title="66"></a>
<a class="sourceLine" id="cb1-67" title="67"> <span class="kw">public</span> <span class="dt">boolean</span> <span class="fu">equals</span>(<span class="bu">Object</span> object) {</a>
<a class="sourceLine" id="cb1-68" title="68"> <span class="kw">if</span> (<span class="kw">this</span> == object) {</a>
<a class="sourceLine" id="cb1-69" title="69"> <span class="kw">return</span> <span class="kw">true</span>;</a>
<a class="sourceLine" id="cb1-70" title="70"> }</a>
<a class="sourceLine" id="cb1-71" title="71"> <span class="kw">if</span> (object == <span class="kw">null</span> || <span class="fu">getClass</span>() != object.<span class="fu">getClass</span>()) {</a>
<a class="sourceLine" id="cb1-72" title="72"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-73" title="73"> }</a>
<a class="sourceLine" id="cb1-74" title="74"> <span class="kw">if</span> (!<span class="kw">super</span>.<span class="fu">equals</span>(object)) {</a>
<a class="sourceLine" id="cb1-75" title="75"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-76" title="76"> }</a>
<a class="sourceLine" id="cb1-77" title="77"></a>
<a class="sourceLine" id="cb1-78" title="78"> <span class="dt">final</span> Movie movie = (Movie) object;</a>
<a class="sourceLine" id="cb1-79" title="79"></a>
<a class="sourceLine" id="cb1-80" title="80"> <span class="kw">if</span> (id != <span class="kw">null</span> ? !id.<span class="fu">equals</span>(movie.<span class="fu">id</span>) : movie.<span class="fu">id</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-81" title="81"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-82" title="82"> }</a>
<a class="sourceLine" id="cb1-83" title="83"> <span class="kw">if</span> (title != <span class="kw">null</span> ? !title.<span class="fu">equals</span>(movie.<span class="fu">title</span>) : movie.<span class="fu">title</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-84" title="84"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-85" title="85"> }</a>
<a class="sourceLine" id="cb1-86" title="86"> <span class="kw">if</span> (genre != <span class="kw">null</span> ? !genre.<span class="fu">equals</span>(movie.<span class="fu">genre</span>) : movie.<span class="fu">genre</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-87" title="87"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-88" title="88"> }</a>
<a class="sourceLine" id="cb1-89" title="89"> <span class="kw">if</span> (director != <span class="kw">null</span> ? !director.<span class="fu">equals</span>(movie.<span class="fu">director</span>) : movie.<span class="fu">director</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-90" title="90"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-91" title="91"> }</a>
<a class="sourceLine" id="cb1-92" title="92"> <span class="kw">if</span> (firstRelease != <span class="kw">null</span> ? !firstRelease.<span class="fu">equals</span>(movie.<span class="fu">firstRelease</span>) : movie.<span class="fu">firstRelease</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-93" title="93"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-94" title="94"> }</a>
<a class="sourceLine" id="cb1-95" title="95"> <span class="kw">if</span> (actors != <span class="kw">null</span> ? !actors.<span class="fu">equals</span>(movie.<span class="fu">actors</span>) : movie.<span class="fu">actors</span> != <span class="kw">null</span>) {</a>
<a class="sourceLine" id="cb1-96" title="96"> <span class="kw">return</span> <span class="kw">false</span>;</a>
<a class="sourceLine" id="cb1-97" title="97"> }</a>
<a class="sourceLine" id="cb1-98" title="98"></a>
<a class="sourceLine" id="cb1-99" title="99"> <span class="kw">return</span> <span class="kw">true</span>;</a>
<a class="sourceLine" id="cb1-100" title="100"> }</a>
<a class="sourceLine" id="cb1-101" title="101"></a>
<a class="sourceLine" id="cb1-102" title="102"> <span class="kw">public</span> <span class="dt">int</span> <span class="fu">hashCode</span>() {</a>
<a class="sourceLine" id="cb1-103" title="103"> <span class="dt">int</span> result = <span class="kw">super</span>.<span class="fu">hashCode</span>();</a>
<a class="sourceLine" id="cb1-104" title="104"> result = <span class="dv">31</span> * result + (id != <span class="kw">null</span> ? id.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-105" title="105"> result = <span class="dv">31</span> * result + (title != <span class="kw">null</span> ? title.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-106" title="106"> result = <span class="dv">31</span> * result + (subtitle != <span class="kw">null</span> ? subtitle.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-107" title="107"> result = <span class="dv">31</span> * result + (genre != <span class="kw">null</span> ? genre.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-108" title="108"> result = <span class="dv">31</span> * result + (director != <span class="kw">null</span> ? director.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-109" title="109"> result = <span class="dv">31</span> * result + (firstRelease != <span class="kw">null</span> ? firstRelease.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-110" title="110"> result = <span class="dv">31</span> * result + (actors != <span class="kw">null</span> ? actors.<span class="fu">hashCode</span>() : <span class="dv">0</span>);</a>
<a class="sourceLine" id="cb1-111" title="111"> <span class="kw">return</span> result;</a>
<a class="sourceLine" id="cb1-112" title="112"> }</a>
<a class="sourceLine" id="cb1-113" title="113"></a>
<a class="sourceLine" id="cb1-114" title="114"> <span class="at">@Override</span></a>
<a class="sourceLine" id="cb1-115" title="115"> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">toString</span>() {</a>
<a class="sourceLine" id="cb1-116" title="116"> <span class="kw">return</span> <span class="st">"Movie{"</span> +</a>
<a class="sourceLine" id="cb1-117" title="117"> <span class="st">"id="</span> + id +</a>
<a class="sourceLine" id="cb1-118" title="118"> <span class="st">", title='"</span> + title + <span class="ch">'\''</span> +</a>
<a class="sourceLine" id="cb1-119" title="119"> <span class="st">", subtitle='"</span> + subtitle + <span class="ch">'\''</span> +</a>
<a class="sourceLine" id="cb1-120" title="120"> <span class="st">", genre='"</span> + genre + <span class="ch">'\''</span> +</a>
<a class="sourceLine" id="cb1-121" title="121"> <span class="st">", director='"</span> + director + <span class="ch">'\''</span> +</a>
<a class="sourceLine" id="cb1-122" title="122"> <span class="st">", firstRelease="</span> + firstRelease +</a>
<a class="sourceLine" id="cb1-123" title="123"> <span class="st">", actors="</span> + actors +</a>
<a class="sourceLine" id="cb1-124" title="124"> <span class="ch">'}'</span>;</a>
<a class="sourceLine" id="cb1-125" title="125"> }</a>
<a class="sourceLine" id="cb1-126" title="126">}</a></code></pre></div>
<p>That’s a hell lot of code (126 LOC, to be exact) for something as simple as a class holding only metadata about movies.</p>
<p>“That’s a lot of code, but my IDE can generate it for me,” I here you say. That’s absolutely correct, but I’d like to point out that writing boilerplate code is just one side of the coin. A lot of your time you’re reading some other developer’s code, hunting for that tricky bug, where the all that boilerplate will obfuscate the essence of the code.</p>
<p>By the way, have you noticed the error/unexpected behavior in the code snippet above?</p>
<p>It’s likely you just skimmed through it and thus have missed it. It’s in the <code>equals()</code> method, where I forgot to check for equality of <code>subtitle</code>. It’s included in <code>hashCode()</code>, though, so that will likely lead to tricky problems when keeping a <code>Set<Movie></code>, for example. Errors like these can easily happen when you add a new field to a class, but forget to re-generate your methods.</p>
<p>In contrast, here’s the same class using Lombok, just without the mistake:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb2-1" title="1"><span class="at">@Data</span></a>
<a class="sourceLine" id="cb2-2" title="2"><span class="kw">public</span> <span class="kw">class</span> Movie {</a>
<a class="sourceLine" id="cb2-3" title="3"></a>
<a class="sourceLine" id="cb2-4" title="4"> <span class="kw">private</span> <span class="bu">Long</span> id;</a>
<a class="sourceLine" id="cb2-5" title="5"> <span class="kw">private</span> <span class="bu">String</span> title;</a>
<a class="sourceLine" id="cb2-6" title="6"> <span class="kw">private</span> <span class="bu">String</span> subtitle;</a>
<a class="sourceLine" id="cb2-7" title="7"> <span class="kw">private</span> <span class="bu">String</span> genre;</a>
<a class="sourceLine" id="cb2-8" title="8"> <span class="kw">private</span> <span class="bu">String</span> director;</a>
<a class="sourceLine" id="cb2-9" title="9"> <span class="kw">private</span> LocalDate firstRelease;</a>
<a class="sourceLine" id="cb2-10" title="10"> <span class="kw">private</span> <span class="bu">List</span><Actor> actors;</a>
<a class="sourceLine" id="cb2-11" title="11"></a>
<a class="sourceLine" id="cb2-12" title="12">}</a></code></pre></div>
<p>Just by annotating the class with <code>@Data</code>, Lombok will generate all getters, setters, <code>equals()</code>, <code>hashCode()</code>, and even a decent <code>toString()</code>. But that’s only the tip of the iceberg. There are many other annotations, e.g. for constructors, the builder pattern, or instantiating a logger. Just have a look at <a href="https://projectlombok.org/features/all">the feature list on their website</a> for an overview.</p>
<p>Now that you’ve seen a code sample, you may worry about <a href="https://stackoverflow.com/q/3852091/432354">whether it’s safe to use it in your project</a>. Will my IDE still be able to navigate my code? Will it show errors for missing methods? Will I still be able to refactor using the IDE?</p>
<p>I also wasn’t sure until a year ago. But then a colleague of mine just started to use it in one of our products and I used that as an opportunity to give it a try. I haven’t looked back ever since.</p>
<p>Getting started is quite simple. Just add the following dependency:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb3-1" title="1"><span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb3-2" title="2"> <span class="kw"><groupId></span>org.projectlombok<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb3-3" title="3"> <span class="kw"><artifactId></span>lombok<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb3-4" title="4"> <span class="kw"><version></span>1.16.18<span class="kw"></version></span></a>
<a class="sourceLine" id="cb3-5" title="5"> <span class="kw"><scope></span>provided<span class="kw"></scope></span></a>
<a class="sourceLine" id="cb3-6" title="6"><span class="kw"></dependency></span></a></code></pre></div>
<p><code>javac</code> will automatically detect Lombok on the classpath (Lombok makes use of <a href="https://www.jcp.org/en/jsr/detail?id=269">JSR-269: “Pluggable Annotation Processing API”</a>). The dependency can be <em>provided</em>, since there’s no need for Lombok to be around at run-time. All the magic happens at compile-time.</p>
<p>As for IDE support, there’s a brilliant Lombok plugin for IntelliJ IDEA. Just search the plugin repository for “lombok”. If you want to use it in Eclipse it’s even simpler. Download the lombok.jar from the project website and run it. A graphical installer will lead you through the required steps.</p>
<p>For instance, here’s what the Structure view will look like in IDEA:</p>
<figure>
<img src="/2017/lombok-idea.png" alt="Class Movie in IDEA" /><figcaption>Class <code>Movie</code> in IDEA</figcaption>
</figure>
<p>As you can see, everything from the first code snippet is there.</p>
<p>Of course another way to avoid the tedious work of writing and maintaining Java’s boilerplate is using other JVM languages like Kotlin or Scala, which solve Java’s problems using special syntax. But that’s a different story.</p>
<p>What do you think about Lombok? Are you also thinking about using it in your software or did you already give it a try? Let me and others know in the comments.</p>
Automating Integration Testing of Spring Boot Applications on Travis CI and Sauce Labs
http://vorba.ch/2016/integration-testing-spring-boot-travis-saucelabs.html
2016-12-01T00:00:00Z
Paul Vorbach
<p><img src="/2016/flowers.jpg"></p>
<p>Writing Unit Tests often is not enough to make sure your software is working correctly. In order to make sure your components are <em>integrated</em> correctly, writing automated <em>Integration Tests</em> will help you accomplish that goal. Unfortunately, setting up a project for automated integration testing is not straight forward. In this article I’ll show how to set up a Spring Boot web application with Maven that automatically runs on Travis CI and connects to Selenium WebDrivers running on Sauce Labs for testing with real browsers. This is done step-by-step so hopefully it will be easy for you to follow along and set up integration tests for your own Spring Boot project.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>You’re going to need accounts on <a href="https://github.com/">GitHub</a>, <a href="https://travis-ci.org/">Travis CI</a> and <a href="https://saucelabs.com/">Sauce Labs</a> to follow the instructions in this article. All of these services are entirely free for open source projects.</p>
<h2 id="local-setup">Local setup</h2>
<p>In order to help with development and testing the setup, we’re first going to set up the environment locally. Let’s start with this simple <code>pom.xml</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb1-1" title="1"><span class="kw"><project</span><span class="ot"> xmlns=</span><span class="st">"http://maven.apache.org/POM/4.0.0"</span></a>
<a class="sourceLine" id="cb1-2" title="2"><span class="ot"> xmlns:xsi=</span><span class="st">"http://www.w3.org/2001/XMLSchema-instance"</span></a>
<a class="sourceLine" id="cb1-3" title="3"><span class="ot"> xsi:schemaLocation=</span><span class="st">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="kw">></span></a>
<a class="sourceLine" id="cb1-4" title="4"> <span class="kw"><modelVersion></span>4.0.0<span class="kw"></modelVersion></span></a>
<a class="sourceLine" id="cb1-5" title="5"></a>
<a class="sourceLine" id="cb1-6" title="6"> <span class="kw"><groupId></span>org.example<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-7" title="7"> <span class="kw"><artifactId></span>webapp<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-8" title="8"> <span class="kw"><version></span>0.1.0-SNAPSHOT<span class="kw"></version></span></a>
<a class="sourceLine" id="cb1-9" title="9"> <span class="kw"><packaging></span>jar<span class="kw"></packaging></span></a>
<a class="sourceLine" id="cb1-10" title="10"></a>
<a class="sourceLine" id="cb1-11" title="11"> <span class="kw"><parent></span></a>
<a class="sourceLine" id="cb1-12" title="12"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-13" title="13"> <span class="kw"><artifactId></span>spring-boot-starter-parent<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-14" title="14"> <span class="kw"><version></span>1.4.2.RELEASE<span class="kw"></version></span></a>
<a class="sourceLine" id="cb1-15" title="15"> <span class="kw"></parent></span></a>
<a class="sourceLine" id="cb1-16" title="16"></a>
<a class="sourceLine" id="cb1-17" title="17"> <span class="kw"><properties></span></a>
<a class="sourceLine" id="cb1-18" title="18"> <span class="kw"><java.version></span>1.8<span class="kw"></java.version></span></a>
<a class="sourceLine" id="cb1-19" title="19"> <span class="kw"><project.build.sourceEncoding></span>UTF-8<span class="kw"></project.build.sourceEncoding></span></a>
<a class="sourceLine" id="cb1-20" title="20"></a>
<a class="sourceLine" id="cb1-21" title="21"> <span class="kw"><selenium.version></span>2.53.1<span class="kw"></selenium.version></span></a>
<a class="sourceLine" id="cb1-22" title="22"> <span class="kw"></properties></span></a>
<a class="sourceLine" id="cb1-23" title="23"></a>
<a class="sourceLine" id="cb1-24" title="24"> <span class="kw"><dependencies></span></a>
<a class="sourceLine" id="cb1-25" title="25"> <span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb1-26" title="26"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-27" title="27"> <span class="kw"><artifactId></span>spring-boot-starter-web<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-28" title="28"> <span class="kw"></dependency></span></a>
<a class="sourceLine" id="cb1-29" title="29"> <span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb1-30" title="30"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-31" title="31"> <span class="kw"><artifactId></span>spring-boot-starter-test<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-32" title="32"> <span class="kw"><scope></span>test<span class="kw"></scope></span></a>
<a class="sourceLine" id="cb1-33" title="33"> <span class="kw"></dependency></span></a>
<a class="sourceLine" id="cb1-34" title="34"> <span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb1-35" title="35"> <span class="kw"><groupId></span>org.seleniumhq.selenium<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-36" title="36"> <span class="kw"><artifactId></span>selenium-remote-driver<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-37" title="37"> <span class="kw"><version></span>${selenium.version}<span class="kw"></version></span></a>
<a class="sourceLine" id="cb1-38" title="38"> <span class="kw"><scope></span>test<span class="kw"></scope></span></a>
<a class="sourceLine" id="cb1-39" title="39"> <span class="kw"></dependency></span></a>
<a class="sourceLine" id="cb1-40" title="40"> <span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb1-41" title="41"> <span class="kw"><groupId></span>org.seleniumhq.selenium<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-42" title="42"> <span class="kw"><artifactId></span>selenium-support<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-43" title="43"> <span class="kw"><version></span>${selenium.version}<span class="kw"></version></span></a>
<a class="sourceLine" id="cb1-44" title="44"> <span class="kw"><scope></span>test<span class="kw"></scope></span></a>
<a class="sourceLine" id="cb1-45" title="45"> <span class="kw"></dependency></span></a>
<a class="sourceLine" id="cb1-46" title="46"> <span class="kw"></dependencies></span></a>
<a class="sourceLine" id="cb1-47" title="47"></a>
<a class="sourceLine" id="cb1-48" title="48"> <span class="kw"><build></span></a>
<a class="sourceLine" id="cb1-49" title="49"> <span class="kw"><plugins></span></a>
<a class="sourceLine" id="cb1-50" title="50"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb1-51" title="51"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-52" title="52"> <span class="kw"><artifactId></span>spring-boot-maven-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-53" title="53"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb1-54" title="54"> <span class="kw"></plugins></span></a>
<a class="sourceLine" id="cb1-55" title="55"> <span class="kw"></build></span></a>
<a class="sourceLine" id="cb1-56" title="56"><span class="kw"></project></span></a></code></pre></div>
<p>This is the main application class (<code>src/main/java/org/example/webapp/WebApp.java</code>):</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb2-1" title="1"><span class="kw">package</span><span class="im"> org.example.webapp;</span></a>
<a class="sourceLine" id="cb2-2" title="2"></a>
<a class="sourceLine" id="cb2-3" title="3"><span class="kw">import</span><span class="im"> org.springframework.boot.SpringApplication;</span></a>
<a class="sourceLine" id="cb2-4" title="4"><span class="kw">import</span><span class="im"> org.springframework.boot.autoconfigure.SpringBootApplication;</span></a>
<a class="sourceLine" id="cb2-5" title="5"></a>
<a class="sourceLine" id="cb2-6" title="6"><span class="at">@SpringBootApplication</span></a>
<a class="sourceLine" id="cb2-7" title="7"><span class="kw">public</span> <span class="kw">class</span> WebApp {</a>
<a class="sourceLine" id="cb2-8" title="8"></a>
<a class="sourceLine" id="cb2-9" title="9"> <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span><span class="kw">... </span>args) {</a>
<a class="sourceLine" id="cb2-10" title="10"> SpringApplication.<span class="fu">run</span>(WebApp.<span class="fu">class</span>, args);</a>
<a class="sourceLine" id="cb2-11" title="11"> }</a>
<a class="sourceLine" id="cb2-12" title="12"></a>
<a class="sourceLine" id="cb2-13" title="13">}</a></code></pre></div>
<p>And there’s also an HTML file we want to navigate our WebDriver to (<code>src/main/resources/static/index.html</code>):</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode html"><code class="sourceCode html"><a class="sourceLine" id="cb3-1" title="1"><span class="kw"><p</span><span class="ot"> class=</span><span class="st">"working"</span><span class="kw">></span>It works!<span class="kw"></p></span></a></code></pre></div>
<h2 id="simple-integration-test">Simple integration test</h2>
<p>In order to be able to inject a Selenium WebDriver into the test classes, we add a Spring configuration class (<code>src/test/java/org/example/webapp/integration/ITConfig.java</code>):</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb4-1" title="1"><span class="kw">package</span><span class="im"> org.example.webapp.integration;</span></a>
<a class="sourceLine" id="cb4-2" title="2"></a>
<a class="sourceLine" id="cb4-3" title="3"><span class="kw">import</span><span class="im"> org.openqa.selenium.WebDriver;</span></a>
<a class="sourceLine" id="cb4-4" title="4"><span class="kw">import</span><span class="im"> org.openqa.selenium.remote.DesiredCapabilities;</span></a>
<a class="sourceLine" id="cb4-5" title="5"><span class="kw">import</span><span class="im"> org.openqa.selenium.remote.RemoteWebDriver;</span></a>
<a class="sourceLine" id="cb4-6" title="6"><span class="kw">import</span><span class="im"> org.springframework.beans.factory.annotation.Autowired;</span></a>
<a class="sourceLine" id="cb4-7" title="7"><span class="kw">import</span><span class="im"> org.springframework.context.annotation.Bean;</span></a>
<a class="sourceLine" id="cb4-8" title="8"><span class="kw">import</span><span class="im"> org.springframework.context.annotation.Configuration;</span></a>
<a class="sourceLine" id="cb4-9" title="9"><span class="kw">import</span><span class="im"> org.springframework.core.env.Environment;</span></a>
<a class="sourceLine" id="cb4-10" title="10"></a>
<a class="sourceLine" id="cb4-11" title="11"><span class="kw">import</span><span class="im"> java.net.MalformedURLException;</span></a>
<a class="sourceLine" id="cb4-12" title="12"><span class="kw">import</span><span class="im"> java.net.URL;</span></a>
<a class="sourceLine" id="cb4-13" title="13"></a>
<a class="sourceLine" id="cb4-14" title="14"><span class="at">@Configuration</span></a>
<a class="sourceLine" id="cb4-15" title="15"><span class="kw">public</span> <span class="kw">class</span> ITConfig {</a>
<a class="sourceLine" id="cb4-16" title="16"></a>
<a class="sourceLine" id="cb4-17" title="17"> <span class="at">@Autowired</span></a>
<a class="sourceLine" id="cb4-18" title="18"> <span class="kw">private</span> <span class="bu">Environment</span> env;</a>
<a class="sourceLine" id="cb4-19" title="19"></a>
<a class="sourceLine" id="cb4-20" title="20"> <span class="at">@Bean</span></a>
<a class="sourceLine" id="cb4-21" title="21"> <span class="kw">public</span> WebDriver <span class="fu">webDriver</span>() <span class="kw">throws</span> <span class="bu">MalformedURLException</span> {</a>
<a class="sourceLine" id="cb4-22" title="22"> <span class="kw">return</span> <span class="kw">new</span> <span class="fu">RemoteWebDriver</span>(<span class="fu">getRemoteUrl</span>(), <span class="fu">getDesiredCapabilities</span>());</a>
<a class="sourceLine" id="cb4-23" title="23"> }</a>
<a class="sourceLine" id="cb4-24" title="24"></a>
<a class="sourceLine" id="cb4-25" title="25"> <span class="kw">private</span> DesiredCapabilities <span class="fu">getDesiredCapabilities</span>() {</a>
<a class="sourceLine" id="cb4-26" title="26"> <span class="kw">return</span> DesiredCapabilities.<span class="fu">firefox</span>();</a>
<a class="sourceLine" id="cb4-27" title="27"> }</a>
<a class="sourceLine" id="cb4-28" title="28"></a>
<a class="sourceLine" id="cb4-29" title="29"> <span class="kw">private</span> <span class="bu">URL</span> <span class="fu">getRemoteUrl</span>() <span class="kw">throws</span> <span class="bu">MalformedURLException</span> {</a>
<a class="sourceLine" id="cb4-30" title="30"> <span class="kw">return</span> <span class="kw">new</span> <span class="bu">URL</span>(<span class="st">"http://localhost:4445/wd/hub"</span>);</a>
<a class="sourceLine" id="cb4-31" title="31"> }</a>
<a class="sourceLine" id="cb4-32" title="32">}</a></code></pre></div>
<p>Here’s a very basic integration test (<code>src/test/java/org/example/webapp/integration/IndexTest.java</code>):</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb5-1" title="1"><span class="kw">package</span><span class="im"> org.example.webapp.integration;</span></a>
<a class="sourceLine" id="cb5-2" title="2"></a>
<a class="sourceLine" id="cb5-3" title="3"><span class="kw">import</span><span class="im"> org.junit.Assert;</span></a>
<a class="sourceLine" id="cb5-4" title="4"><span class="kw">import</span><span class="im"> org.junit.Test;</span></a>
<a class="sourceLine" id="cb5-5" title="5"><span class="kw">import</span><span class="im"> org.junit.runner.RunWith;</span></a>
<a class="sourceLine" id="cb5-6" title="6"><span class="kw">import</span><span class="im"> org.openqa.selenium.By;</span></a>
<a class="sourceLine" id="cb5-7" title="7"><span class="kw">import</span><span class="im"> org.openqa.selenium.WebDriver;</span></a>
<a class="sourceLine" id="cb5-8" title="8"><span class="kw">import</span><span class="im"> org.openqa.selenium.WebElement;</span></a>
<a class="sourceLine" id="cb5-9" title="9"><span class="kw">import</span><span class="im"> org.springframework.beans.factory.annotation.Autowired;</span></a>
<a class="sourceLine" id="cb5-10" title="10"><span class="kw">import</span><span class="im"> org.springframework.test.context.ContextConfiguration;</span></a>
<a class="sourceLine" id="cb5-11" title="11"><span class="kw">import</span><span class="im"> org.springframework.test.context.junit4.SpringJUnit4ClassRunner;</span></a>
<a class="sourceLine" id="cb5-12" title="12"></a>
<a class="sourceLine" id="cb5-13" title="13"><span class="kw">import static</span><span class="im"> org.hamcrest.Matchers.equalTo;</span></a>
<a class="sourceLine" id="cb5-14" title="14"><span class="kw">import static</span><span class="im"> org.hamcrest.Matchers.is;</span></a>
<a class="sourceLine" id="cb5-15" title="15"></a>
<a class="sourceLine" id="cb5-16" title="16"><span class="at">@RunWith</span>(SpringJUnit4ClassRunner.<span class="fu">class</span>)</a>
<a class="sourceLine" id="cb5-17" title="17"><span class="at">@ContextConfiguration</span>(classes = ITConfig.<span class="fu">class</span>)</a>
<a class="sourceLine" id="cb5-18" title="18"><span class="kw">public</span> <span class="kw">class</span> IndexTest {</a>
<a class="sourceLine" id="cb5-19" title="19"></a>
<a class="sourceLine" id="cb5-20" title="20"> <span class="at">@Autowired</span></a>
<a class="sourceLine" id="cb5-21" title="21"> <span class="kw">private</span> WebDriver webDriver;</a>
<a class="sourceLine" id="cb5-22" title="22"></a>
<a class="sourceLine" id="cb5-23" title="23"> <span class="at">@Test</span></a>
<a class="sourceLine" id="cb5-24" title="24"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">visitIndexPage</span>() <span class="kw">throws</span> <span class="bu">Exception</span> {</a>
<a class="sourceLine" id="cb5-25" title="25"></a>
<a class="sourceLine" id="cb5-26" title="26"> webDriver.<span class="fu">get</span>(<span class="st">"http://localhost:8080/"</span>);</a>
<a class="sourceLine" id="cb5-27" title="27"> WebElement working = webDriver.<span class="fu">findElement</span>(By.<span class="fu">id</span>(<span class="st">"working"</span>));</a>
<a class="sourceLine" id="cb5-28" title="28"></a>
<a class="sourceLine" id="cb5-29" title="29"> Assert.<span class="fu">assertThat</span>(working.<span class="fu">getText</span>(), <span class="fu">is</span>(<span class="fu">equalTo</span>(<span class="st">"It works!"</span>)));</a>
<a class="sourceLine" id="cb5-30" title="30"> }</a>
<a class="sourceLine" id="cb5-31" title="31">}</a></code></pre></div>
<p>That’s everything you need for the integration test itself. In order to run it locally, you’ll have to download the Selenium standalone server (get <code>selenium-server-standalone-2.53.1.jar</code> from <a href="http://selenium-release.storage.googleapis.com/index.html?path=2.53/">here</a>) as well as the <a href="https://github.com/mozilla/geckodriver/releases">geckodriver for your platform</a>. Then put both the geckodriver binary and the selenium jar in a directory anywhere on your computer and run selenium from a command prompt:</p>
<pre><code>java -jar selenium-server-standalone-2.53.1.jar -port 4445</code></pre>
<p>From a separate command prompt, you can now run</p>
<pre><code>mvn spring-boot:run</code></pre>
<p>to start the application and</p>
<pre><code>mvn test</code></pre>
<p>in a third command prompt. Hopefully, you’ll see your test start Firefox after a while an it navigating to <code>http://localhost:8080/</code>.</p>
<h2 id="automating-the-configuration">Automating the configuration</h2>
<p>The next step on our way is to be able to start our web app and the integration tests in a single command. This can be achieved using the <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">maven-failsafe-plugin</a>. Alter the <code>build</code> definition in your <code>pom.xml</code> to the following:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb9-1" title="1"> <span class="kw"><build></span></a>
<a class="sourceLine" id="cb9-2" title="2"> <span class="kw"><plugins></span></a>
<a class="sourceLine" id="cb9-3" title="3"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb9-4" title="4"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb9-5" title="5"> <span class="kw"><artifactId></span>spring-boot-maven-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb9-6" title="6"> <span class="kw"><executions></span></a>
<a class="sourceLine" id="cb9-7" title="7"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb9-8" title="8"> <span class="kw"><id></span>pre-integration-test<span class="kw"></id></span></a>
<a class="sourceLine" id="cb9-9" title="9"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb9-10" title="10"> <span class="kw"><goal></span>start<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb9-11" title="11"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb9-12" title="12"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb9-13" title="13"> <span class="kw"><arguments></span>--server.port=${server.port}<span class="kw"></arguments></span></a>
<a class="sourceLine" id="cb9-14" title="14"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb9-15" title="15"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb9-16" title="16"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb9-17" title="17"> <span class="kw"><id></span>post-integration-test<span class="kw"></id></span></a>
<a class="sourceLine" id="cb9-18" title="18"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb9-19" title="19"> <span class="kw"><goal></span>stop<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb9-20" title="20"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb9-21" title="21"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb9-22" title="22"> <span class="kw"></executions></span></a>
<a class="sourceLine" id="cb9-23" title="23"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb9-24" title="24"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb9-25" title="25"> <span class="kw"><groupId></span>org.codehaus.mojo<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb9-26" title="26"> <span class="kw"><artifactId></span>build-helper-maven-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb9-27" title="27"> <span class="kw"><version></span>1.12<span class="kw"></version></span></a>
<a class="sourceLine" id="cb9-28" title="28"> <span class="kw"><executions></span></a>
<a class="sourceLine" id="cb9-29" title="29"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb9-30" title="30"> <span class="kw"><id></span>reserve-container-port<span class="kw"></id></span></a>
<a class="sourceLine" id="cb9-31" title="31"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb9-32" title="32"> <span class="kw"><goal></span>reserve-network-port<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb9-33" title="33"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb9-34" title="34"> <span class="kw"><phase></span>process-resources<span class="kw"></phase></span></a>
<a class="sourceLine" id="cb9-35" title="35"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb9-36" title="36"> <span class="kw"><portNames></span></a>
<a class="sourceLine" id="cb9-37" title="37"> <span class="kw"><portName></span>server.port<span class="kw"></portName></span></a>
<a class="sourceLine" id="cb9-38" title="38"> <span class="kw"></portNames></span></a>
<a class="sourceLine" id="cb9-39" title="39"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb9-40" title="40"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb9-41" title="41"> <span class="kw"></executions></span></a>
<a class="sourceLine" id="cb9-42" title="42"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb9-43" title="43"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb9-44" title="44"> <span class="kw"><groupId></span>org.apache.maven.plugins<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb9-45" title="45"> <span class="kw"><artifactId></span>maven-failsafe-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb9-46" title="46"> <span class="kw"><version></span>2.18.1<span class="kw"></version></span></a>
<a class="sourceLine" id="cb9-47" title="47"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb9-48" title="48"> <span class="kw"><systemPropertyVariables></span></a>
<a class="sourceLine" id="cb9-49" title="49"> <span class="kw"><server.port></span>${server.port}<span class="kw"></server.port></span></a>
<a class="sourceLine" id="cb9-50" title="50"> <span class="kw"><selenium.version></span>${selenium.version}<span class="kw"></selenium.version></span></a>
<a class="sourceLine" id="cb9-51" title="51"> <span class="kw"></systemPropertyVariables></span></a>
<a class="sourceLine" id="cb9-52" title="52"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb9-53" title="53"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb9-54" title="54"> <span class="kw"></plugins></span></a>
<a class="sourceLine" id="cb9-55" title="55"> <span class="kw"></build></span></a></code></pre></div>
<p>Failsafe adds several integration-test phases to the Maven lifecycle. We added an execution to the spring-boot-maven-plugin that starts the app before integration tests are run. The build-helper-maven-plugin is used to reserve a free port, so it won’t have port collisions if port 8080 is already in use.</p>
<p>Failsafe automatically detects integration tests by their name. All classes under <code>src/test/java</code> that end with <code>IT</code> will be executed by Failsafe instead of Surefire. Therefore we must rename the class <code>IndexTest</code> to <code>IndexIT</code>. Also we need to dynamically set the port of the requested URL in the test. Here’s how the test looks after these changes:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb10-1" title="1"><span class="at">@RunWith</span>(SpringJUnit4ClassRunner.<span class="fu">class</span>)</a>
<a class="sourceLine" id="cb10-2" title="2"><span class="at">@ContextConfiguration</span>(classes = ITConfig.<span class="fu">class</span>)</a>
<a class="sourceLine" id="cb10-3" title="3"><span class="kw">public</span> <span class="kw">class</span> IndexIT {</a>
<a class="sourceLine" id="cb10-4" title="4"></a>
<a class="sourceLine" id="cb10-5" title="5"> <span class="at">@Autowired</span></a>
<a class="sourceLine" id="cb10-6" title="6"> <span class="kw">private</span> WebDriver webDriver;</a>
<a class="sourceLine" id="cb10-7" title="7"></a>
<a class="sourceLine" id="cb10-8" title="8"> <span class="at">@Value</span>(<span class="st">"${server.port}"</span>)</a>
<a class="sourceLine" id="cb10-9" title="9"> <span class="kw">private</span> <span class="dt">int</span> serverPort;</a>
<a class="sourceLine" id="cb10-10" title="10"></a>
<a class="sourceLine" id="cb10-11" title="11"> <span class="at">@Test</span></a>
<a class="sourceLine" id="cb10-12" title="12"> <span class="kw">public</span> <span class="dt">void</span> <span class="fu">visitIndexPage</span>() <span class="kw">throws</span> <span class="bu">Exception</span> {</a>
<a class="sourceLine" id="cb10-13" title="13"></a>
<a class="sourceLine" id="cb10-14" title="14"> webDriver.<span class="fu">get</span>(<span class="bu">String</span><span class="fu">.format</span>(<span class="st">"http://localhost:</span><span class="sc">%d</span><span class="st">/"</span>, serverPort));</a>
<a class="sourceLine" id="cb10-15" title="15"> WebElement working = webDriver.<span class="fu">findElement</span>(By.<span class="fu">id</span>(<span class="st">"working"</span>));</a>
<a class="sourceLine" id="cb10-16" title="16"></a>
<a class="sourceLine" id="cb10-17" title="17"> Assert.<span class="fu">assertThat</span>(working.<span class="fu">getText</span>(), <span class="fu">is</span>(<span class="fu">equalTo</span>(<span class="st">"It works!"</span>)));</a>
<a class="sourceLine" id="cb10-18" title="18"> }</a>
<a class="sourceLine" id="cb10-19" title="19">}</a></code></pre></div>
<p>Now you should be able to run the integration tests with the following command:</p>
<pre><code>mvn verify</code></pre>
<h2 id="testing-continuously">Testing continuously</h2>
<p>Since programmers are lazy and soon won’t want to run these time consuming tests on their own machines, it’s time to configure a CI server to do it for you. Travis CI is a solution that integrates well into GitHub and also has support to run Selenium tests via Sauce Labs.</p>
<p>First you need to enable the GitHub project in your Travis profile. This should only be one click:</p>
<figure>
<img src="/2016/travis-config.png" alt="Travis project activation" /><figcaption>Travis project activation</figcaption>
</figure>
<p>Then you need to set up your project on Sauce Labs. I won’t cover this here, but once your project is set up, you will see your access key in your account settings. This access key is needed to verify the Travis CI server that runs your tests is allowed to control a Sauce Labs WebDriver. This key shouldn’t become publicy visible, so it makes sense to encrypt it. This can be done by using the Travis CLI tool (<a href="https://github.com/travis-ci/travis.rb#installation">installation instructions</a>).</p>
<p>Encryption is done with the following command:</p>
<pre><code>travis encrypt "SAUCE_ACCESS_KEY=your-access-key"</code></pre>
<p>This returns a string that you need for the file <code>.travis.yml</code>:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode yaml"><code class="sourceCode yaml"><a class="sourceLine" id="cb13-1" title="1"><span class="fu">language:</span><span class="at"> java</span></a>
<a class="sourceLine" id="cb13-2" title="2"></a>
<a class="sourceLine" id="cb13-3" title="3"><span class="fu">jdk:</span></a>
<a class="sourceLine" id="cb13-4" title="4"> <span class="kw">-</span> oraclejdk8</a>
<a class="sourceLine" id="cb13-5" title="5"></a>
<a class="sourceLine" id="cb13-6" title="6"><span class="fu">addons:</span></a>
<a class="sourceLine" id="cb13-7" title="7"> <span class="fu">sauce_connect:</span></a>
<a class="sourceLine" id="cb13-8" title="8"> <span class="fu">username:</span><span class="at"> your-user-name</span></a>
<a class="sourceLine" id="cb13-9" title="9"> <span class="fu">jwt:</span></a>
<a class="sourceLine" id="cb13-10" title="10"> <span class="fu">secure:</span><span class="at"> your-encrypted-access-key</span></a>
<a class="sourceLine" id="cb13-11" title="11"></a>
<a class="sourceLine" id="cb13-12" title="12"><span class="fu">script:</span></a>
<a class="sourceLine" id="cb13-13" title="13"> mvn verify -B</a></code></pre></div>
<p>Replace <code>your-user-name</code> with the name of your Sauce Labs account and <code>your-encrypted-access-key</code> with the encrypted access key string. Additionally, Sauce Labs user name and access key need to be set as environment variables in the settings of your project as <code>SAUCE_USERNAME</code> and <code>SAUCE_ACCESS_KEY</code> (unencrypted).</p>
<p>When this is done, our <code>ITConfig</code> needs to be modified slightly to provide the <code>RemoteWebDriver</code> with a URL that contains the information of the Sauce Labs account:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb14-1" title="1"><span class="at">@Configuration</span></a>
<a class="sourceLine" id="cb14-2" title="2"><span class="kw">public</span> <span class="kw">class</span> ITConfig {</a>
<a class="sourceLine" id="cb14-3" title="3"></a>
<a class="sourceLine" id="cb14-4" title="4"> <span class="at">@Autowired</span></a>
<a class="sourceLine" id="cb14-5" title="5"> <span class="kw">private</span> <span class="bu">Environment</span> env;</a>
<a class="sourceLine" id="cb14-6" title="6"></a>
<a class="sourceLine" id="cb14-7" title="7"> <span class="at">@Bean</span></a>
<a class="sourceLine" id="cb14-8" title="8"> <span class="kw">public</span> WebDriver <span class="fu">webDriver</span>() <span class="kw">throws</span> <span class="bu">MalformedURLException</span> {</a>
<a class="sourceLine" id="cb14-9" title="9"> <span class="kw">return</span> <span class="kw">new</span> <span class="fu">RemoteWebDriver</span>(<span class="fu">getRemoteUrl</span>(), <span class="fu">getDesiredCapabilities</span>());</a>
<a class="sourceLine" id="cb14-10" title="10"> }</a>
<a class="sourceLine" id="cb14-11" title="11"></a>
<a class="sourceLine" id="cb14-12" title="12"> <span class="kw">private</span> DesiredCapabilities <span class="fu">getDesiredCapabilities</span>() {</a>
<a class="sourceLine" id="cb14-13" title="13"></a>
<a class="sourceLine" id="cb14-14" title="14"> <span class="dt">final</span> DesiredCapabilities capabilities = DesiredCapabilities.<span class="fu">firefox</span>();</a>
<a class="sourceLine" id="cb14-15" title="15"> <span class="kw">if</span> (<span class="fu">useSauceLabs</span>()) {</a>
<a class="sourceLine" id="cb14-16" title="16"> capabilities.<span class="fu">setCapability</span>(<span class="st">"tunnel-identifier"</span>, env.<span class="fu">getProperty</span>(<span class="st">"TRAVIS_JOB_NUMBER"</span>));</a>
<a class="sourceLine" id="cb14-17" title="17"> capabilities.<span class="fu">setCapability</span>(<span class="st">"seleniumVersion"</span>, env.<span class="fu">getProperty</span>(<span class="st">"selenium.version"</span>));</a>
<a class="sourceLine" id="cb14-18" title="18"> }</a>
<a class="sourceLine" id="cb14-19" title="19"></a>
<a class="sourceLine" id="cb14-20" title="20"> <span class="kw">return</span> capabilities;</a>
<a class="sourceLine" id="cb14-21" title="21"> }</a>
<a class="sourceLine" id="cb14-22" title="22"></a>
<a class="sourceLine" id="cb14-23" title="23"> <span class="kw">private</span> <span class="dt">boolean</span> <span class="fu">useSauceLabs</span>() {</a>
<a class="sourceLine" id="cb14-24" title="24"> <span class="kw">return</span> env.<span class="fu">getProperty</span>(<span class="st">"SAUCE_USERNAME"</span>) != <span class="kw">null</span>;</a>
<a class="sourceLine" id="cb14-25" title="25"> }</a>
<a class="sourceLine" id="cb14-26" title="26"></a>
<a class="sourceLine" id="cb14-27" title="27"> <span class="kw">private</span> <span class="bu">URL</span> <span class="fu">getRemoteUrl</span>() <span class="kw">throws</span> <span class="bu">MalformedURLException</span> {</a>
<a class="sourceLine" id="cb14-28" title="28"> <span class="kw">if</span> (<span class="fu">useSauceLabs</span>()) {</a>
<a class="sourceLine" id="cb14-29" title="29"> <span class="kw">return</span> <span class="kw">new</span> <span class="bu">URL</span>(<span class="bu">String</span><span class="fu">.format</span>(<span class="st">"http://</span><span class="sc">%s</span><span class="st">:</span><span class="sc">%s</span><span class="st">@localhost:4445/wd/hub"</span>,</a>
<a class="sourceLine" id="cb14-30" title="30"> env.<span class="fu">getProperty</span>(<span class="st">"SAUCE_USERNAME"</span>), env.<span class="fu">getProperty</span>(<span class="st">"SAUCE_ACCESS_KEY"</span>)));</a>
<a class="sourceLine" id="cb14-31" title="31"> } <span class="kw">else</span> {</a>
<a class="sourceLine" id="cb14-32" title="32"> <span class="kw">return</span> <span class="kw">new</span> <span class="bu">URL</span>(<span class="st">"http://localhost:4445/wd/hub"</span>);</a>
<a class="sourceLine" id="cb14-33" title="33"> }</a>
<a class="sourceLine" id="cb14-34" title="34"> }</a>
<a class="sourceLine" id="cb14-35" title="35">}</a></code></pre></div>
<p>Push these changes to GitHub and you’re done! You can watch your tests run on Travis and even watch the browser in your Sauce Lab account.</p>
<figure>
<img src="/2016/it-works.png" alt="It works!" /><figcaption>It works!</figcaption>
</figure>
<p>The complete sources of this sample project are <a href="https://github.com/pvorb/spring-boot-integration-test-example">available on GitHub</a>.</p>
<p>Happy testing!</p>
Fast Front End Development Cycle with Spring Boot
http://vorba.ch/2016/fast-spring-boot-development-cycle.html
2016-10-07T00:00:00Z
Paul Vorbach
<p><img src="/2016/train.jpg"></p>
<p><a href="https://projects.spring.io/spring-boot/">Spring Boot</a> makes developing microservices a breeze. It’s easy to get to speed without the need for much configuration. However, it can be quite annoying to compile and restart the program every time you change a component. To overcome this drawback, the Spring Boot documentation includes a how-to guide about configuring <a href="https://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/html/howto-hotswapping.html">hot swapping</a> such as static web resources, templates and Java classes while the server keeps running. A big part of the solution is to use <a href="https://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/html/using-boot-devtools.html">the developer tools</a>, which provide some helpful settings that improve the overall development experience.</p>
<p>When it comes to front end development, it can be quite annoying to have to manually hit “Make Project” every time a file changes. Especially when running a separate build tool for front end components like Webpack even IntelliJ’s setting “Make project automatically” won’t always update the generated resources on the server in time.</p>
<p>What I wanted to have instead was to be able to just refresh the browser and see the exact same static resources as in the IDE. As it shows, this is possible with Spring Boot and Maven. The described method has been tested with Spring Boot 1.3.8.RELEASE and IntelliJ IDEA 2016.2.4.</p>
<p>In my <code>pom.xml</code> file I have configured the <code>spring-boot-starter-parent</code> artifact as the parent of my project, so I don’t need to specify Spring Boot-managed dependency versions on my own. You may also want to explicitly specify dependency and plugin versions instead.</p>
<p>While not required, it makes sense to add a dependency on <code>spring-boot-devtools</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb1-1" title="1"><span class="kw"><dependency></span></a>
<a class="sourceLine" id="cb1-2" title="2"> <span class="kw"><groupId></span>org.springframework.boot<span class="kw"></groupId></span></a>
<a class="sourceLine" id="cb1-3" title="3"> <span class="kw"><artifactId></span>spring-boot-devtools<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb1-4" title="4"> <span class="kw"><optional></span>true<span class="kw"></optional></span></a>
<a class="sourceLine" id="cb1-5" title="5"><span class="kw"></dependency></span></a></code></pre></div>
<h2 id="solution">Solution</h2>
<p>Key to the solution is to keep static resources in <code>src/main/webapp</code> instead of <code>src/main/resources/static</code>. You may already know this directory from typical WAR-packaged applications. Then, you’ll also need to configure <code>maven-resources-plugin</code> to copy files under <code>src/main/webapp</code> directly to <code>target/classes/public</code> or <code>target/classes/static</code>:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode xml"><code class="sourceCode xml"><a class="sourceLine" id="cb2-1" title="1"><span class="kw"><build></span></a>
<a class="sourceLine" id="cb2-2" title="2"> <span class="kw"><plugins></span></a>
<a class="sourceLine" id="cb2-3" title="3"> <span class="co"><!-- ... --></span></a>
<a class="sourceLine" id="cb2-4" title="4"> <span class="kw"><plugin></span></a>
<a class="sourceLine" id="cb2-5" title="5"> <span class="kw"><artifactId></span>maven-resources-plugin<span class="kw"></artifactId></span></a>
<a class="sourceLine" id="cb2-6" title="6"> <span class="kw"><version></span>3.0.1<span class="kw"></version></span></a>
<a class="sourceLine" id="cb2-7" title="7"> <span class="kw"><executions></span></a>
<a class="sourceLine" id="cb2-8" title="8"> <span class="kw"><execution></span></a>
<a class="sourceLine" id="cb2-9" title="9"> <span class="kw"><id></span>copy-resources<span class="kw"></id></span></a>
<a class="sourceLine" id="cb2-10" title="10"> <span class="kw"><phase></span>generate-resources<span class="kw"></phase></span></a>
<a class="sourceLine" id="cb2-11" title="11"> <span class="kw"><goals></span></a>
<a class="sourceLine" id="cb2-12" title="12"> <span class="kw"><goal></span>copy-resources<span class="kw"></goal></span></a>
<a class="sourceLine" id="cb2-13" title="13"> <span class="kw"></goals></span></a>
<a class="sourceLine" id="cb2-14" title="14"> <span class="kw"><configuration></span></a>
<a class="sourceLine" id="cb2-15" title="15"> <span class="kw"><outputDirectory></span>${basedir}/target/classes/public<span class="kw"></outputDirectory></span></a>
<a class="sourceLine" id="cb2-16" title="16"> <span class="kw"><resources></span></a>
<a class="sourceLine" id="cb2-17" title="17"> <span class="kw"><resource></span></a>
<a class="sourceLine" id="cb2-18" title="18"> <span class="kw"><directory></span>src/main/webapp<span class="kw"></directory></span></a>
<a class="sourceLine" id="cb2-19" title="19"> <span class="kw"><filtering></span>false<span class="kw"></filtering></span></a>
<a class="sourceLine" id="cb2-20" title="20"> <span class="kw"></resource></span></a>
<a class="sourceLine" id="cb2-21" title="21"> <span class="kw"></resources></span></a>
<a class="sourceLine" id="cb2-22" title="22"> <span class="kw"></configuration></span></a>
<a class="sourceLine" id="cb2-23" title="23"> <span class="kw"></execution></span></a>
<a class="sourceLine" id="cb2-24" title="24"> <span class="kw"></executions></span></a>
<a class="sourceLine" id="cb2-25" title="25"> <span class="kw"></plugin></span></a>
<a class="sourceLine" id="cb2-26" title="26"> <span class="kw"></plugins></span></a>
<a class="sourceLine" id="cb2-27" title="27"><span class="kw"></build></span></a></code></pre></div>
<p>This way a running instance of Spring Boot can always serve the most recent version of a file. Hitting refresh in the browser will show it immediately.</p>
<p>You can find the <a href="https://github.com/pvorb/platon/blob/913647edbaf69d309df75f1b871ff922a5b23aca/pom.xml">complete POM file on GitHub</a>.</p>
<p><em>P.S.:</em> If you run a front end build tool like Webpack with <code>--watch</code>, changing a file will not always trigger a rebuild of affected bundles, when you use IntelliJ for editing. This is caused by a feature called “safe write” which you can turn off in the settings under “Appearance & Behavior” → “System Settings”.</p>
How to Set Up Code Coverage for a Java Project using Gradle, Travis, JaCoCo and Codecov
http://vorba.ch/2015/java-gradle-travis-jacoco-codecov.html
2015-07-23T00:00:00Z
Paul Vorbach
<p><img src="/2015/roof.jpg"></p>
<p>The code coverage of a project’s test suite can be a useful measure for finding out about the quality of the project. There are several tools for Java that can calculate the code coverage, for example <a href="http://www.sonarqube.org/">SonarQube</a> and <a href="http://www.eclemma.org/jacoco/">JaCoCo</a>.</p>
<p>During my recent work on <a href="https://github.com/pvorb/property-providers">property-providers</a>, I found out how to give users a quick overview of the test coverage of the code using <a href="https://codecov.io/">Codecov</a>. It is not well documented for projects that don’t use Maven, so here I present the complete setup that brings the code coverage badge to the GitHub page of the project.</p>
<h2 id="gradle">Gradle</h2>
<p>The project uses Gradle instead of Maven. Here’s its <code>build.gradle</code>. It configures JaCoCo reports. XML reports are disabled by default but those are needed in order to work with Codecov, so enable them. Also the <code>check</code> task, which is run by Travis, needs to depend on <code>jacocoTestReport</code>.</p>
<pre class="groovy"><code>apply plugin: 'java'
apply plugin: 'jacoco'
version = '0.0.1'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
// project dependencies
// ...
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
check.dependsOn jacocoTestReport</code></pre>
<h2 id="travis-ci">Travis-CI</h2>
<p><a href="https://travis-ci.org/">Travis</a> is a hosted continuous integration service. You can sign up with a GitHub account. It’s free for open source projects.</p>
<p>This is what the Travis <code>.travis.yml</code> configuration file looks like. Whenever a build succeeds, the last line uploads the JaCoCo report to Codecov.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode yaml"><code class="sourceCode yaml"><a class="sourceLine" id="cb2-1" title="1"><span class="fu">language:</span><span class="at"> java</span></a>
<a class="sourceLine" id="cb2-2" title="2"><span class="fu">jdk:</span></a>
<a class="sourceLine" id="cb2-3" title="3"> <span class="kw">-</span> oraclejdk8</a>
<a class="sourceLine" id="cb2-4" title="4"><span class="fu">after_success:</span></a>
<a class="sourceLine" id="cb2-5" title="5"> <span class="kw">-</span> bash <(curl -s https://codecov.io/bash)</a></code></pre></div>
<h2 id="codecov">Codecov</h2>
<p>In order to use Codecov, you need to sign up first. It is straight forward for Github users: just <a href="https://codecov.io/login/github">sign up with GitHub</a> and then add the repository that you wish to monitor. The service, like Travis, is free for open source projects.</p>
<h2 id="readme">Readme</h2>
<p>Finally, I added this markdown snippet to my <code>README.md</code> file in order to show the coverage badge. I used the badge from <a href="http://shields.io/">shields.io</a> instead of Codecov’s own badge, because it gives better consistency with other badges.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode markdown"><code class="sourceCode markdown"><a class="sourceLine" id="cb3-1" title="1"><span class="ot">[![Code Coverage](https://img.shields.io/codecov/c/github/pvorb/property-providers/develop.svg)</span>](https://codecov.io/github/pvorb/property-providers?branch=develop)</a></code></pre></div>
<p>That’s all! I hope this article may help you on your way to set up code coverage monitoring for your Java project.</p>
isso
http://vorba.ch/2014/isso.html
2014-11-13T00:00:00Z
Paul Vorbach
<p><img src="/2014/isso.png"></p>
<p>Okay. Disqus nervt. Darum habe ich mir <a href="https://news.ycombinator.com/item?id=6818416">Ersatz gesucht</a> und <em><a href="http://posativ.org/isso/">isso</a></em> entdeckt.</p>
<p><em>isso</em> ist ein kleines Python-Programm, das Disqus ersetzen soll. Es wird lokal aufgesetzt und kann, wenn es einmal läuft, ähnlich wie Disqus per JavaScript in jede HTML-Seite eingebunden werden.</p>
<p>Der Funktionsumfang lässt sich sehen:</p>
<ul>
<li>Einfaches Einbinden per <code><script></code>-Tag</li>
<li>Kommentarzähler für jeden Artikel</li>
<li>Auszeichnung per Markdown</li>
<li>Name, E-Mail-Adresse und Website sind optional.</li>
<li>Optionale Moderation neuer Kommentare per Mail</li>
<li>Mehrsprachigkeit</li>
<li>Kommentare lassen sich nachträglich bearbeiten und löschen.</li>
</ul>
<p>Als Datenbank kommt SQLite zum Einsatz.</p>
<blockquote>
<p>„Because comments are not Big Data.“</p>
</blockquote>
<p>Eine separate Einrichtung der Datenbankanbindung entfällt damit. Außerdem liegen die Kommentare allesamt in einer einfachen Datei, die jederzeit und ohne Probleme gesichert und ersetzt werden kann.</p>
<p>Das alles klingt ziemlich vielversprechend. Ein paar Dinge fehlen jedoch, die Disqus konnte:</p>
<ul>
<li>Es gibt keine Möglichkeit, sich per E-Mail über neue Kommentare benachrichtigen zu lassen.</li>
<li>Einen Feed gibt es ebenfalls nicht.</li>
<li>Gravatare gibt es nicht, dafür aber generierte Avatare, durch die sich die Kommentatoren auf einen Blick unterscheiden lassen – immerhin.</li>
</ul>
<p>Die Einrichtung ging leider auf meinem Debian 7 nicht ganz so problemlos, wie die Dokumentation es mir weismachen wollte. Letztlich lag das jedoch weniger an <em>isso</em> selbst, sondern daran, dass ich mich in der Welt der Python-Webserver nur wenig auskenne. Außerdem funktionierte der Import aus Disqus <a href="https://github.com/posativ/isso/issues/135">nicht ohne weiteres</a>. Nun läuft aber alles rund, soweit ich das sehe, und darf ausprobiert werden. ;-)</p>