<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-21580788</id><updated>2011-12-27T02:16:10.249Z</updated><category term='Shell'/><category term='Acer Aspire One'/><category term='JavaEE'/><category term='Jenkins'/><category term='BookReview'/><category term='Subversion'/><category term='Donny Don&apos;t'/><category term='Maven'/><category term='CloudBees'/><title type='text'>Stephen's Java Adventures</title><subtitle type='html'>Various musings arising from exploring JavaEE5, glassfish, NetBeans, Eclipse, JUnit, Jester, Easymock...</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>54</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-21580788.post-3698858365941743291</id><published>2011-10-24T11:19:00.005+01:00</published><updated>2011-10-24T11:19:47.318+01:00</updated><title type='text'>Note to self: Java Generics - The Get and Put principle</title><content type='html'>With Java Generics, it can be useful to insert wildcards wherever possible, but how do you decide &lt;i&gt;which&lt;/i&gt;&amp;nbsp;wildcard is correct? When do you use &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;? super T&lt;/span&gt;, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;? extends T&lt;/span&gt; and when should you not use a wildcard at all.&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;The Get and Put principle:&lt;/i&gt;&amp;nbsp;Use an &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extends&lt;/span&gt; wildcard when you only &lt;i&gt;get&lt;/i&gt; values from the structure. Use a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;super&lt;/span&gt; wildcard when you only &lt;i&gt;put&lt;/i&gt; values into the structure. Don't use a wildcard when you both &lt;i&gt;get&lt;/i&gt; and &lt;i&gt;put&lt;/i&gt; values from/into the structure.&lt;/blockquote&gt;The best example of this principle is the following copy method signature:&lt;br /&gt;&lt;blockquote&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;public static &amp;lt;T&amp;gt; void copy(&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Collection&amp;lt;?&amp;nbsp;super&amp;nbsp;T&amp;gt;&amp;nbsp;destination,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Collection&amp;lt;? extends T&amp;gt; source);&lt;/span&gt;&lt;/blockquote&gt;The method gets values out of the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;source&lt;/span&gt;, so &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;source&lt;/span&gt; uses &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;extends&lt;/span&gt;, and it puts values into the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;destination&lt;/span&gt;, so that is declared with the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;super&lt;/span&gt; wildcard.&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-3698858365941743291?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/3698858365941743291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=3698858365941743291' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3698858365941743291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3698858365941743291'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/10/note-to-self-java-generics-get-and-put.html' title='Note to self: Java Generics - The Get and Put principle'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1667930798624059893</id><published>2011-10-19T14:33:00.000+01:00</published><updated>2011-10-19T15:16:00.326+01:00</updated><title type='text'>How to add Mac-like shadow to your screenshots</title><content type='html'>Cmd+Shift+4 followed by Space is a lovely way to get a screen shot of a window on an Apple Mac...&lt;br /&gt;&lt;br /&gt;In fact here is one such screenshot:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-oXPtZ0HfE5c/Tp7QcWFe36I/AAAAAAAAAFs/-uWRAn6p3sc/s1600/Screen+shot+2011-10-19+at+14.27.44.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-oXPtZ0HfE5c/Tp7QcWFe36I/AAAAAAAAAFs/-uWRAn6p3sc/s320/Screen+shot+2011-10-19+at+14.27.44.png" width="243" /&gt;&lt;/a&gt;&lt;/div&gt;Notice the nice shadow effect around the screenshot?&amp;nbsp;Ever wanted to add that shadow effect to your non-mac screenshots?&lt;br /&gt;&lt;br /&gt;ImageMagick is your friend&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;convert &lt;i&gt;NoShadowScreenshot.png&lt;/i&gt; \( +clone -background black -shadow 80x20+0+15 \) +swap -background transparent -layers merge +repage &lt;i&gt;WithShadowScreenshot.png&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There you are, nice equivalent shadow added.&lt;br /&gt;&lt;br /&gt;Oh and if you hate the Mac's shadow:&lt;br /&gt;&lt;br /&gt;To remove from existing screenshots?&lt;br /&gt;&lt;br /&gt;ImageMagick is your friend again&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;convert&amp;nbsp;&lt;i&gt;WithShadowScreenshot.png&lt;/i&gt;&amp;nbsp;-crop +40+25 -crop -40-55&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;i&gt;WithoutShadowScreenshot.png&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you get sick of doing that all the time, you can just disable it.&lt;br /&gt;Here’s the command. Just paste this line into your Terminal, and press return.&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;defaults write com.apple.screencapture disable-shadow -bool true&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then, you’ll need to restart the System’s UI Server, which is the component of the operating system responsible for doing things like taking screenshots and drawing drop shadows. To accomplish this, simply paste this line into the Terminal.&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;killall SystemUIServer&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1667930798624059893?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1667930798624059893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1667930798624059893' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1667930798624059893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1667930798624059893'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/10/how-to-add-mac-like-shadow-to-your.html' title='How to add Mac-like shadow to your screenshots'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-oXPtZ0HfE5c/Tp7QcWFe36I/AAAAAAAAAFs/-uWRAn6p3sc/s72-c/Screen+shot+2011-10-19+at+14.27.44.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6981849531110469837</id><published>2011-08-15T12:17:00.003+01:00</published><updated>2011-08-15T12:19:31.766+01:00</updated><title type='text'>Java Serialization and locks</title><content type='html'>Java has a couple of “nice” features, specifically:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Built-in locks&lt;/li&gt;&lt;li&gt;Baked in support for object serialization&lt;/li&gt;&lt;/ul&gt;Now I don't want to get into a war about how both of these are flawed (there was a reason that I put “nice” in quotes) but here is a quick note on how to make the two play nice (more as a reference to myself... but if others find this useful, well and good).&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;General principal&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In essence, in each case you implement the readObject and writeObject methods per the &lt;a href="http://download.oracle.com/javase/1.3/docs/guide/serialization/spec"&gt;Java Object Serialization Specification&lt;/a&gt;.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Using object's intrinsic lock&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In this case you can just make the readObject and writeObject methods synchronized:&lt;br /&gt;&lt;pre&gt;    private synchronized void readObject(ObjectInputStream stream)&lt;br /&gt;            throws ClassNotFoundException, IOException {&lt;br /&gt;        stream.defaultReadObject();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private synchronized void writeObject(ObjectOutputStream stream) throws IOException {&lt;br /&gt;        stream.defaultWriteObject();&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Using Java 5 style locks&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In this case you just wrap the functionality inside a try...finally block&lt;br /&gt;&lt;pre&gt;    private void readObject(ObjectInputStream stream)&lt;br /&gt;            throws ClassNotFoundException, IOException {&lt;br /&gt;        lock.lock();&lt;br /&gt;        try {&lt;br /&gt;            stream.defaultReadObject();&lt;br /&gt;        } finally {&lt;br /&gt;            lock.unlock();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private void writeObject(ObjectOutputStream stream) throws IOException {&lt;br /&gt;        lock.lock();&lt;br /&gt;        try {&lt;br /&gt;            stream.defaultWriteObject();&lt;br /&gt;        } finally {&lt;br /&gt;            lock.unlock();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Using multiple locks&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If your object uses multiple locks to guard the different fields, there are really two techniques you can use:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Get all the locks in one go, ensuring that you always get multiple locks in the same sequence (high-risk)&lt;/li&gt;&lt;li&gt;Stop relying on stream.default____Object() and get/set each field manually in code as that allows you to hold one lock at a time (may write a partially modified object)&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Gotchas&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Watch out for the fields you are writing being mutable and modified by other threads while you are writing them to the stream. Reads from a stream are less of an issue as the object should not be given to another thread until fully read back in.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6981849531110469837?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6981849531110469837/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6981849531110469837' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6981849531110469837'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6981849531110469837'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/08/java-serialization-and-locks.html' title='Java Serialization and locks'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-8158099570134206264</id><published>2011-08-11T09:49:00.001+01:00</published><updated>2011-08-11T10:28:02.418+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='BookReview'/><title type='text'></title><content type='html'>&lt;h1&gt;Book review: “EJB 3.1 Cookbook” by Richard M. Reese&lt;/h1&gt;&lt;div style="text-align: center;"&gt;&lt;a href="http://www.packtpub.com/ejb-3-1-cookbook/book"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-MmZS7VO6ydc/TkOWV-6jiyI/AAAAAAAAAFY/nFsHF7rpE7Y/s1600/2381_EJB+3.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2&gt;Review&lt;/h2&gt;The kind people at Packt gave me a copy of “EJB 3.1 Cookbook” by Richard M. Reese to review.  This book should be viewed as a collection of building block recipes, not as a collection of meal plans, and not as a how to cook book that you would read from cover to cover.  When I look at the book from this point of view, I think that it is well structured and put together and the content is pitched just about right.  This book does not tell you when to use a specific recipe, so it would be a mistake to think that this is the only book you would need for EJB 3.1. However, when you need a “Béchamel sauce”, this is a book that you can go to, quickly find and open up the right section and read the recipe to refresh your mind and get cooking.&lt;br /&gt;&lt;br /&gt;The book itself is aimed at Java EE and EJB developers, promising to bring them quickly up to speed on how to use EJB 3.1 techniques through the use of step-by-step examples.  I think that the back cover of this book could be designed a little better in terms of putting the book into context with regards to skill level and where the book should sit in a learning tree.  In my view this book should be aimed for three audiences: the occasional EJB developer who has to work on other things a lot; the intermediate EJB developer who is moving to the EJB 3.1 platform from a previous release; and the beginning EJB 3.1 developer who has just read their first book or two on EJB 3.1 and now is trying to develop EJB 3.1 applications.&lt;br /&gt;&lt;br /&gt;The cookbook format works well for a reference book when you know that you need to do XYZ, it will have a section that you can find quickly, and that section will be easy to find and contain some of the common tricks and pitfalls that apply to doing XYZ.  When reviewing this book it is all too easy to fall into the “as an EJB architect” trap and point out that the book does not tell you when to do XYZ or how to decide between XYZ and ABC.  Architects should not be reading cookbooks to find the answers to these kinds of questions.&lt;br /&gt;&lt;br /&gt;There are some specific criticisms that I have with this book:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The book says that it uses Netbeans and Glassfish. To be honest I found very little in the book that is specific to either, and I think that the author would have been better off saying that all the examples were tested on Glassfish (which, as the reference implementation, should guarantee that they will work on all EJB 3.1 containers) and that IDE examples used Netbeans.&lt;/li&gt;&lt;li&gt;Chapter 11 covers packaging the EJBs, yet it does not cover or even hint at how to achieve this using the build tools that most developers use, e.g. ANT, Maven, etc.  I think chapter 11 would be significantly enhanced if it included at least “Packaging EJBs using Apache ANT” and “Packaing EJBs using Apache Maven” recipes. Recipes for some of the other newer build tools like Buildr would be a bonus, but one could be forgiven for leaving them out.&lt;/li&gt;&lt;li&gt;There is no chapter on testing EJBs. In fact there are no testing recipes at all.  No developer should be writing code without tests. At a minimum I would like to see a recipes like “Unit testing EJBs with mocking frameworks”, “Testing EJBs using OpenEJB’s embeddable container”, “Testing EJBs using JBoss’s embeddable container”, “Testing EJBs using Glassfish’s embeddable container”. I’m sure books could be written on the fine arts of each of these recipes but a book like this should at least be sketching out that this kind of thing is possible.&lt;/li&gt;&lt;/ul&gt;Despite these criticisms, the recipes that it does provide are clearly written and well set out in a consistent format. I specifically liked the “There’s more” section that each recipe has.  If you try to read this book cover to cover you will be annoyed at the repetition, but then cookbooks are not meant to be read that way!&lt;br /&gt;&lt;br /&gt;I would recommend this book for beginner and intermediate level developers who are either occasionally working with EJB 3.1 or who are moving from a previous version of EJB to 3.1.  More advanced developers may not find many recipes that interest them, and architects will not find any guides about how to pick which recipes to combine to make the meal.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Overall:&lt;/b&gt; 6 out 10.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Book details&lt;/h2&gt;&lt;b&gt;URL:&amp;nbsp;&lt;/b&gt;&lt;a href="http://www.packtpub.com/ejb-3-1-cookbook/book"&gt;http://www.packtpub.com/ejb-3-1-cookbook/book&lt;/a&gt;&lt;br /&gt;&lt;b&gt;Publisher:&amp;nbsp;&lt;/b&gt;Packt Publishing&lt;br /&gt;&lt;b&gt;Language:&amp;nbsp;&lt;/b&gt;English&lt;br /&gt;&lt;b&gt;Paperback:&amp;nbsp;&lt;/b&gt;436 pages [ 235mm x 191mm ]&lt;br /&gt;&lt;b&gt;Release Date:&amp;nbsp;&lt;/b&gt;June 2011&lt;br /&gt;&lt;b&gt;ISBN:&amp;nbsp;&lt;/b&gt;1849682380&lt;br /&gt;&lt;b&gt;ISBN 13:&amp;nbsp;&lt;/b&gt;978-1-84968-238-1&lt;br /&gt;&lt;b&gt;Author(s):&amp;nbsp;&lt;/b&gt;Richard M. Reese&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-8158099570134206264?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/8158099570134206264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=8158099570134206264' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8158099570134206264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8158099570134206264'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/08/book-review-ejb-3.html' title=''/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-MmZS7VO6ydc/TkOWV-6jiyI/AAAAAAAAAFY/nFsHF7rpE7Y/s72-c/2381_EJB+3.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1619472904270827101</id><published>2011-08-04T11:38:00.003+01:00</published><updated>2011-08-04T11:48:25.988+01:00</updated><title type='text'>Open Source: the Meritocracy vs the Circle of Trust</title><content type='html'>There has been this idea running around the back of my head for a while, and it's only now that it is starting to crystalize into something that I can express.&lt;br /&gt;When we look at Open Source projects, we see that there is a hierarchy of involvement. There are different levels at which you can be involved, and at each higher level, there will be less and less individuals. For now I am going to divide involvement up like this:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Interested: this group of individuals know that the project exists, and might even be following what it is doing, but have not been able to actually use the project at all yet (for whatever reason)&lt;/li&gt;&lt;li&gt;Consumers: these are the people who actually use the project. They may not even be following the project, e.g. a lot of people consume log4j or ANT without following the mailing lists, seeing what features are on the roadmap, or even filing issues.&lt;/li&gt;&lt;li&gt;Contributors: if you are filing issues, submitting patches, etc, but you do not have commit access to the project, then you are a Contributor.&lt;/li&gt;&lt;li&gt;Committers: you have commit access to the project&lt;/li&gt;&lt;li&gt;Management: you get to have a say in some of the following: whether the new release of a project can be released; the architecture/direction of the project going forward; who has commit access; etc.&lt;/li&gt;&lt;/ul&gt;Different Open Source projects but different barriers at different points. For example:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Contributor road-blocks:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;You may have to create an account to file issues. [Technical]&lt;/li&gt;&lt;li&gt;You may have to sign a CLA and fax it off before you can commit any patches. [Legal]&lt;/li&gt;&lt;li&gt;You may have to get a&amp;nbsp;notarized signed CLA sent via snail-mail before you can commit any patches. [Legal]&lt;/li&gt;&lt;li&gt;You may have to get your employer to sign-off on you contributing patches. [Legal]&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Committer road-blocks:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;You may have to be invited to become a committer.&lt;/li&gt;&lt;li&gt;You may have to establish merit before you can be invited to become a committer.&lt;/li&gt;&lt;li&gt;You may have&amp;nbsp;to sign a CLA and fax it off before you can get commit access. [Legal]&lt;/li&gt;&lt;li&gt;&lt;/li&gt;&lt;li&gt;You may have to get a&amp;nbsp;notarized signed CLA sent via snail-mail before you can get commit access. [Legal]&lt;/li&gt;&lt;li&gt;You may have to get your employer to sign-off on you committing code to the project. [Legal]&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Management road-blocks:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;You may have to be invited to become management.&lt;/li&gt;&lt;li&gt;You may have to establish merit before you can be invited to become management.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;(Note: I'm not going to even try and pretend that the above is a complete list of road-blocks)&lt;br /&gt;&lt;div&gt;Different people view success of Open Source projects differently. Some measures of success include: the number of consumers; &amp;nbsp;the number of active contributors; the number of committers; the number of releases; the number of downloads; the number of issues raised and closed; the activity of mailing lists.&lt;/div&gt;&lt;div&gt;None of these are correct, and much like psychologists&amp;nbsp;hypothesize a (in reality) unmeasurable&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/G_factor_(psychometrics)"&gt;“g factor”&lt;/a&gt; as a true measure of intelligence against which all real measures (such as IQ) are only partial measures. We could hypothesize an unmeasurable “s factor” which is the true measure of the success of an Open Source project.&lt;/div&gt;&lt;div&gt;But I don't want to go down such an academic road today. Instead lets look at the dependency tree in the measures of success that we know of...&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Number of downloads depends on number of consumers: as a consumer has to download your project at least once... it is not a perfect dependency, because you could download each release of the project once and every time decide it is a load of rubbish and never actually consume it... and a consumer may have just downloaded version 1.0 once and stayed on that version for ever more, but in general, if you have a large number of consumers, you will have a large number of downloads and the opposite does not necessarily follow.&lt;/li&gt;&lt;li&gt;Activity of mailing lists depends on the number of active contributors: when you have a lot of active contributors, the mailing lists will be active... however the mailing lists can be active without any active contributors.&lt;/li&gt;&lt;li&gt;Number of issues raised depends on the number of active contributors: for similar reasons to the previous.&lt;/li&gt;&lt;li&gt;Number of issues closed depends on the number of committers: because you need commit access to close most issues (appart from the "not a bug" type of issues)&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;So a lot of the measures of success can be raised by increasing the number of consumers, active contributors, and committers. However, just because we have a large number of consumers/active contributors/committers does not imply that we have a successful project, it just says that projects that&amp;nbsp;have a large number of consumers/active contributors/committers have a greater&amp;nbsp;likelihood of being successful projects.&lt;/div&gt;&lt;div&gt;So how do we increase that number, and thereby increase the probability that our Open Source project is a “success”?&lt;/div&gt;&lt;div&gt;Well, if you are picking an project to use, i.e. deciding to become a consumer, one of the things you will look at is how active the community is (i.e. number of active&amp;nbsp;contributors&amp;nbsp;and number of committers). It's not the only thing you will look at, but assuming you have two projects which have the feature-set you need, and they are comparable in usability, etc. When you come to make the decision, the social beast in you will pick the larger active community (who wants to pick the dead project).&amp;nbsp;The active community also act as evangelists out selling the project to potential consumers. So one way to grow the number of consumers is to grow the active community.&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Number of consumers (partially) depends on the number of active contributors&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;If we have lots of active contributors and very few committers, very soon there will be a glut of patches that don't get applied, and the active contributors will wonder off to another project where there contributions can be consumed. So for each project, there is probably some upper limit to the number of active contributors that depends on the number of committers. That limit depends on things like the complexity of the project, the code base, the toolchain used by the project, the project management processes, etc. So I don't think there is any hard and fast rule like you can have X active contributors for every Y committers on any open source project.&lt;/div&gt;&lt;div&gt;But what I do think we can say is:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Number of active contributors is limited by the number of committers.&lt;/li&gt;&lt;/ul&gt;So at this point you might feel that my chain of logic looks a little like this:&lt;/div&gt;&lt;div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-H10LaHHYGuI/Tjp4aG25B-I/AAAAAAAAAFQ/uSx2SyoC91M/s1600/2260187416_be564a30a4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-H10LaHHYGuI/Tjp4aG25B-I/AAAAAAAAAFQ/uSx2SyoC91M/s320/2260187416_be564a30a4.jpg" width="212" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;But I should point out that what I am trying to say is really that:&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Having a lot of committers&lt;br /&gt;&amp;nbsp;indicates that a project is more likely to be successful, &lt;br /&gt;but it cannot guarantee that success.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;Hopefully your back thinking I have a solid argument again.&lt;/div&gt;&lt;div&gt;So how do we get a lot of committers? Well the answer is easy, remove as many road-blocks to becoming a committer.&lt;/div&gt;&lt;div&gt;Some of the road-blocks are technical. So you have to create an account to file issues... well we can switch to a issue tracking tool that uses OpenID or oAuth and now you can use an account you already have to file those issues... or perhaps you can just provide your email address and fill in a captcha... etc. None of these completely remove the technical road-block, but they can make it easier for the consumer to graduate to active contributor.&lt;/div&gt;&lt;div&gt;Some of the road-blocks are legal. So you need a CLA signed with the blood of an Armur leopard before you can submit a patch. These are harder to work around, but not always impossible e.g. perhaps small patches (less than some size metric) don't need the CLA.&lt;/div&gt;&lt;div&gt;After all these road-blocks, we start to hit what I like to call the “merit wall”. And this is the problem that I see. In essence you need to establish that you have the merit to work on the project. You need to establish a chain of consistent patches (which get picked up by the existing committers) of good quality before you are invited to become a committer.&amp;nbsp;&lt;/div&gt;&lt;div&gt;That to me is a crazy way to gate commit access.&amp;nbsp;&lt;/div&gt;&lt;div&gt;Just because your submitted patches were all stellar quality, that could just be the result of the areas being patched having good test coverage, so you were forced to get the code right. It could be that the nature of bundling up your changes as a patch forced you to think more about the changes. You could become sloppy as a coder, etc.&lt;/div&gt;&lt;div&gt;There are many reasons why “past performance is not a guarantee of future returns”. Some people think the solution to this is to have merit expire, e.g. if you have not worked on the project recently, then you loose the right to work on the project. All that happens then is that people start doing busy work on the project just before their merit is due to expire, and such busy work is, by its very nature, not the work that drives a project forward.&lt;/div&gt;&lt;div&gt;In my view, the only merit to contribute to a project is the code you are contributing right now. Not the code you contributed yesterday, last month, or last year. Not the code you might contribute tomorrow, next month, or next year. But the code you are contributing right now. You don't have merit, but your code might.&lt;/div&gt;&lt;div&gt;Merit is the wrong thing to use as a gate for commit access.&amp;nbsp;&lt;/div&gt;&lt;div&gt;Because what is giving commit access really about? It is about saying that we trust you to commit changes to the project. If you commit code which has no merit, we (or you) can use the SCM to back out those changes. The merit of your previous submissions is a very poor measure of our trust in you using the SCM. In fact because for a lot of projects, generating patches actually is about not using the SCM at all (Git pull requests changes that) it is exactly the wrong thing to use to establish trust.&lt;/div&gt;&lt;div&gt;Once we realize that commit access is about trust and not merit, then we can really start to build a community, and increase the likelihood of our project being a success:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;You don't start with zero trust.&lt;/li&gt;&lt;li&gt;One big abuse of trust, and you have lost all your trust and you have to work a lot harder to earn what you had back again.&lt;/li&gt;&lt;li&gt;You have to be given scope in order to earn more trust.&lt;/li&gt;&lt;li&gt;Trust does not expire easily.&lt;/li&gt;&lt;/ul&gt;So what I am arguing is that if somebody shows up knocking at the door of your project looking to contribute, at some point you have to trust them enough to open the door and let them in. It's fine if you have a few hurdles that they have to jump, but remember that at the end of the day you are trying to decided if you trust them enough to let them make changes to the code, or to put it another way, if you trust them to make good judgements on the merit of any changes they want to commit.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Your merit as a committer is not the &lt;br /&gt;merit of the code changes you have committed, &lt;br /&gt;but all the code changes you did not commit&amp;nbsp;&lt;/i&gt;&lt;i&gt;because those changes had no merit.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;A good committer is somebody who does not make changes that are bad. The ideal committer only makes commits that improve the project. Joe might be the ideal committer for your project even though he has never submitted a patch because Joe knows when a patch is improving the project and when it is not, but because Joe has never submitted a patch, he has earned no merit in your projects eyes, so he will never become a committer.&lt;/div&gt;&lt;div&gt;Commit access is about letting somebody inside the circle of trust. The easier you make that process, the more vibrant your community, and the more successful your project. Put a merit wall in place and you are alienating your potential community (never mind that it is the wrong barrier in the first place)&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div&gt;Look at the &lt;a href="http://jenkins-ci.org/"&gt;Jenkins&lt;/a&gt; project. If you want commit access, you just ask for it. How is that for initial trust. The result is a very successful project, oh and the sky has not fallen. Everyone that has been trusted so far has respected the trust they have been shown.&lt;/div&gt;&lt;div&gt;Look at most projects hosted on github. Pull requests have effectively removed a large chunk of the need for commit access, and because you can see the person's skills with the SCM, they can earn trust to be let loose on the canonical source directly.&lt;/div&gt;&lt;div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-QHIt80xqV0k/Tjp449CedNI/AAAAAAAAAFU/Hi0lynTYXpw/s1600/Circle-of-Trust1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="193" src="http://2.bp.blogspot.com/-QHIt80xqV0k/Tjp449CedNI/AAAAAAAAAFU/Hi0lynTYXpw/s320/Circle-of-Trust1.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A merit wall can leave you feeling like this&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;So my message to all open source projects that use merit based on previous patches as a measure of the worth of a potential committer, please look again at your policy. Are you sure you are looking for the right qualities to drive your project to bigger successes?&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1619472904270827101?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1619472904270827101/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1619472904270827101' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1619472904270827101'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1619472904270827101'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/08/open-source-meritocracy-vs-circle-of.html' title='Open Source: the Meritocracy vs the Circle of Trust'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-H10LaHHYGuI/Tjp4aG25B-I/AAAAAAAAAFQ/uSx2SyoC91M/s72-c/2260187416_be564a30a4.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1523654892152066473</id><published>2011-05-30T14:45:00.000+01:00</published><updated>2011-05-30T14:56:33.153+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Donny Don&apos;t'/><title type='text'>The worst Unit Tests I ever saw</title><content type='html'>In relation to&amp;nbsp;&lt;a href="http://dhanji.github.com/#unit-tests-false-idol"&gt;http://dhanji.github.com/#unit-tests-false-idol&lt;/a&gt;&amp;nbsp;here is the tail of the worst test case I ever came across...&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In a former employers, there was an employee who we will call &lt;a href="http://en.wikipedia.org/wiki/Kevin_McCallister"&gt;Kevin McCallister&lt;/a&gt; in order to protect the guilty. In any case, for various reasons, I ended up having to maintain some of the code that Kevin wrote...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I ran all the test cases and measured the code coverage, it seemed high and there were lots of test cases... but bugs a plenty kept on hitting me... I finally found the answer in one test class... which looked a little something like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;/**&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;* Tests the FooBar class&lt;br /&gt;&amp;nbsp;* @author Kevin McCallister&lt;br /&gt;&amp;nbsp;*/&lt;br /&gt;public void FooBarTest {&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void smokes() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; try {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; new FooBar().method1();&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; } catch (Throwable t) {}&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; try {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; new FooBar().method2();&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; } catch (Throwable t) {}&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; try {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; new FooBar().method3();&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; } catch (Throwable t) {}&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; ...&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; try {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; new FooBar().method15();&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; } catch (Throwable t) {}&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; ...&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; try {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; new FooBar().method30();&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; } catch (Throwable t) {}&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomething() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomethingElse() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesAnotherThing() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomethingWhenFoo() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomethingWhenBar() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomethingWhenBarAfterFoo() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;div&gt;&amp;nbsp; @Test&lt;/div&gt;&lt;div&gt;&amp;nbsp; public void method1DoesSomethingWhenFooAfterBar() {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; // write later&lt;/div&gt;&lt;div&gt;&amp;nbsp; }&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; ...&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To make myself clear, there was one method at the top that called every function and would never fail (gives you the code coverage) and a couple of hundred empty test methods that did nothing but had names that sounded like nice test cases...&lt;br /&gt;&lt;br /&gt;&lt;iframe allowfullscreen="" frameborder="0" height="349" src="http://www.youtube.com/embed/4DgbUBoxa48" width="425"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;So there you have it, how not to write unit tests&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1523654892152066473?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1523654892152066473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1523654892152066473' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1523654892152066473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1523654892152066473'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/05/worst-unit-tests-i-ever-saw.html' title='The worst Unit Tests I ever saw'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://img.youtube.com/vi/4DgbUBoxa48/default.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4113129733343964320</id><published>2011-05-27T14:55:00.000+01:00</published><updated>2011-05-30T14:56:55.395+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><category scheme='http://www.blogger.com/atom/ns#' term='CloudBees'/><title type='text'>Backwards compatibility</title><content type='html'>This is a tail about Jenkins (née Hudson) and Kohsuke's policy of maintaining backwards compatibility...&lt;br /&gt;&lt;br /&gt;Back in 2006 I started working for my previous employer, just a month or two after Peter Reilly started. Initially we were working on the same team. This was a team who's CI system was a nightly cron job that emailed off a list of failing tests to everyone... obviously Peter and I had many a WTF over that old system... so I convinced our boss that we should put some effort into setting up a proper CI system... Initially this was CruiseControl (as he thought Hudson at version 1.64 was too new and unheard of... go with the old reliable)... but after a couple of pains with the CruiseControl system (monolithic xml config file), we convinced him to switch to Hudson... (I don't think we ever looked back!)&lt;br /&gt;&lt;br /&gt;What we really liked was that Kohsuke had put in place a plugin framework, so I started writing plugins, and shortly afterwards convinced Peter that it was "cool" (even if he did have to use Maven to build the plugins - Peter is on the Apache ANT PMC).&lt;br /&gt;&lt;br /&gt;One of the earlier plugins that Peter wrote during his spare time was his "Simple Cobertura plugin" which allowed you to get the Cobertura coverage results on the build page&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;i&gt;[1]&lt;/i&gt;&lt;/span&gt;.&amp;nbsp;Peter created his plugin against Hudson 1.129.&amp;nbsp;By this stage Peter and I had moved onto separate projects, eventually Peter left the company, but the team he was working on still had the .hpi for his "Simple Cobertura plugin". As they moved to newer and newer Hudson builds, the plugin kept on working (no recompile needed). I had my own plugins which were built for an earlier version of Hudson (my closed source SilkTest plugin, my original closed source AccuRev plugin [not to be confused with the open source one I subsequently developed and handed on to others to maintain]) which also were still working (with no recompile) in newer versions of Hudson&lt;br /&gt;&lt;br /&gt;Recently, I met up with Peter, and he told me the sad news.... his plugin is now broken... it no longer works in the latest version of Jenkins... this was strange news to me, as before I left my previous employer to join &lt;a href="http://www.cloudbees.com/"&gt;CloudBees&lt;/a&gt; I'd upgraded our CI servers to the latest and one of those was using Peter's&amp;nbsp;"Simple Cobertura plugin" which did not seem broken...&lt;br /&gt;&lt;br /&gt;After looking at Peter's code I finally found the problem... he was using the old javadoc annotation to mark the constructor as one for data binding, with one simple change:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;6,7d5&lt;br /&gt;&amp;lt; import org.kohsuke.stapler.DataBoundConstructor;&lt;br /&gt;&amp;lt; &lt;br /&gt;22a21&lt;br /&gt;&amp;gt;      * @stapler-constructor&lt;br /&gt;24d22&lt;br /&gt;&amp;lt;     @DataBoundConstructor&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;His plugin was back in action... Now I was puzzled, as I couldn't understand how I'd upgraded this old old plugin from Hudson 1.129 and got it running on Jenkins 1.397 without recompiling...&lt;br /&gt;&lt;br /&gt;Well I found out the answer, the version Peter had left in behind, didn't use the data bound constructor at all... it was doing&lt;br /&gt;&lt;pre&gt;    public Publisher newInstance(StaplerRequest req) throws FormException {&lt;br /&gt;        return new C2Publisher(req.getParameter("c2_coverReportDir"));&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;While the code Peter had was doing&amp;nbsp;&lt;/div&gt;&lt;pre&gt;    public C2Publisher newInstance(StaplerRequest req, JSONObject obj) &lt;br /&gt;            throws FormException {&lt;br /&gt;        return req.bindParameters(C2Publisher.class, "c2_");&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;If you change it back to the old way, and you have enough Maven-foo to build against that old a version of Jenkins (née Hudson) you can end up with a plugin that works on both Hudson 1.129 and Jenkins 1.413...&lt;br /&gt;&lt;br /&gt;I am fairly sure that if I had a SilkTest license and a copy of the SilkTest plugin that I wrote against Hudson 1.96, it would also still run unmodified on Jenkins 1.413.&lt;br /&gt;&lt;br /&gt;How many projects can maintain &lt;b&gt;&lt;u&gt;that&lt;/u&gt;&lt;/b&gt; level of backwards compatibility.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;i&gt;[1]&lt;/i&gt; Peter wanted to have the coverage results on the main page, and at the time Kohsuke did not want to allow plugins to add columns to the main page... I came up with the compromise of adding a column which would be just one icon wide and have a tooltip to which plugins could contribute information... and that was the birth of the weather (health) icons&lt;i&gt;[2]&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;i&gt;[2]&lt;/i&gt; at the time Peter thought it would be cool if he could generate his own weather icon on the fly so that if you had high code coverage that would be a full&amp;nbsp;umbrella&amp;nbsp;while poor code coverage would be a tattered umbrella with no fabric and only the frame remaining... this is actually part of the API, so the technology to implement this has been there since 1.115... just nobody has done it... yet!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4113129733343964320?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4113129733343964320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4113129733343964320' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4113129733343964320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4113129733343964320'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/05/backwards-compatibility.html' title='Backwards compatibility'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-828264878015915640</id><published>2011-05-18T10:29:00.000+01:00</published><updated>2011-05-30T14:56:55.396+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CloudBees'/><title type='text'>Continuously deploy your java apps to the cloud</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: left;"&gt;In my &lt;a href="http://javaadventure.blogspot.com/2011/05/deploy-your-java-apps-to-cloud.html"&gt;previous post&lt;/a&gt;&amp;nbsp;showed how easy it is to run your java application on &lt;a href="http://www.cloudbees.com/"&gt;CloudBees&lt;/a&gt;' RUN@cloud service. Today I'm going to use the &lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/Cloudbees+Deployer+Plugin"&gt;CloudBees Deployer plugin for Jenkins&lt;/a&gt; that allows you to deploy your app to the cloud from your CI server. I am using the &lt;a href="http://www.cloudbees.com/dev.cb"&gt;DEV@cloud&lt;/a&gt; Jenkins service for my CI infrastructure, but you can use this plugin from your own &lt;a href="http://jenkins-ci.org/"&gt;Jenkins&lt;/a&gt; (or &lt;a href="http://nectar.cloudbees.com/"&gt;Nectar&lt;/a&gt;) server.&lt;/div&gt;&lt;br /&gt;So first step is to install the CloudBees Deployer plugin...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;1. Goto Manage Jenkins&lt;br /&gt;&lt;div class="separator" style="clear: both; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-3KYlccVpLcs/TdOMFgqS-7I/AAAAAAAAABw/ap-fQVzcVqI/s1600/Screen+shot+2011-05-18+at+09.43.35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-3KYlccVpLcs/TdOMFgqS-7I/AAAAAAAAABw/ap-fQVzcVqI/s640/Screen+shot+2011-05-18+at+09.43.35.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;2. Goto Manage Plugins&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-EcHJmUwcbWk/TdOMF0JGRSI/AAAAAAAAAB0/bvmEn6krkz4/s1600/Screen+shot+2011-05-18+at+09.43.40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-EcHJmUwcbWk/TdOMF0JGRSI/AAAAAAAAAB0/bvmEn6krkz4/s640/Screen+shot+2011-05-18+at+09.43.40.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;3. Goto the Available tab&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-3byLi7A49QI/TdOMG77BJfI/AAAAAAAAAB4/nmj7te6mIA0/s1600/Screen+shot+2011-05-18+at+09.43.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://2.bp.blogspot.com/-3byLi7A49QI/TdOMG77BJfI/AAAAAAAAAB4/nmj7te6mIA0/s640/Screen+shot+2011-05-18+at+09.43.46.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;4. Scroll down until you find the cloudbees-deployer-plugin. Check the box.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-74aFOsDEhoU/TdOMHNELSrI/AAAAAAAAAB8/ctFi1xo4a_8/s1600/Screen+shot+2011-05-18+at+09.43.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://1.bp.blogspot.com/-74aFOsDEhoU/TdOMHNELSrI/AAAAAAAAAB8/ctFi1xo4a_8/s640/Screen+shot+2011-05-18+at+09.43.55.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;5. Scroll down and click the Install button&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-ibEv8NMGnho/TdOMHYNJyBI/AAAAAAAAACA/x4kyR53m6Gk/s1600/Screen+shot+2011-05-18+at+09.44.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://3.bp.blogspot.com/-ibEv8NMGnho/TdOMHYNJyBI/AAAAAAAAACA/x4kyR53m6Gk/s640/Screen+shot+2011-05-18+at+09.44.03.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;6. Restart Jenkins after it's installed.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Yb2_nP0qrzo/TdOMHlzLUaI/AAAAAAAAACE/xTth0p5F2CQ/s1600/Screen+shot+2011-05-18+at+09.44.22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-Yb2_nP0qrzo/TdOMHlzLUaI/AAAAAAAAACE/xTth0p5F2CQ/s640/Screen+shot+2011-05-18+at+09.44.22.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div&gt;7. Goto Manage Jenkins, select Configure and&amp;nbsp;scroll down to the bottom&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/--NTXGgwXdWc/TdONL4nVnJI/AAAAAAAAACI/mf7Z7NNyPI8/s1600/Screen+shot+2011-05-18+at+09.45.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://3.bp.blogspot.com/--NTXGgwXdWc/TdONL4nVnJI/AAAAAAAAACI/mf7Z7NNyPI8/s640/Screen+shot+2011-05-18+at+09.45.55.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;8. Click the Add button beside the CloudBees accounts&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-qW9UJEoxp7o/TdONMCTiszI/AAAAAAAAACM/B-Qt1QKF4YE/s1600/Screen+shot+2011-05-18+at+09.46.02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://2.bp.blogspot.com/-qW9UJEoxp7o/TdONMCTiszI/AAAAAAAAACM/B-Qt1QKF4YE/s640/Screen+shot+2011-05-18+at+09.46.02.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;9. Add in your CloudBees account details (which you can find on your&amp;nbsp;&lt;a href="https://grandcentral.cloudbees.com/user/keys"&gt;user keys screen on grandcentral&lt;/a&gt;)&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-HsiABb95L2g/TdOOLf40YpI/AAAAAAAAACQ/LGpw2kPjCcU/s1600/Screen+shot+2011-05-18+at+09.51.19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-HsiABb95L2g/TdOOLf40YpI/AAAAAAAAACQ/LGpw2kPjCcU/s640/Screen+shot+2011-05-18+at+09.51.19.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;10. Click on Save and then goto the Configure page for your project. Enable CloudBees Deployment and fill in the details:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-dw_E0_47oDk/TdOOL50qoeI/AAAAAAAAACU/Ql_I0ZYvNPk/s1600/Screen+shot+2011-05-18+at+09.52.08.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-dw_E0_47oDk/TdOOL50qoeI/AAAAAAAAACU/Ql_I0ZYvNPk/s640/Screen+shot+2011-05-18+at+09.52.08.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;11. Save and then kick off a build&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-hjYfaX7DT6w/TdOOMpYEKCI/AAAAAAAAACc/3lUwEZsFJGM/s1600/Screen+shot+2011-05-18+at+09.53.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-hjYfaX7DT6w/TdOOMpYEKCI/AAAAAAAAACc/3lUwEZsFJGM/s640/Screen+shot+2011-05-18+at+09.53.06.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;12. When the build is finished, your application has been deployed:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-9lXahzAh9Ac/TdOONLIAnyI/AAAAAAAAACg/abImJ0JZ1PQ/s1600/Screen+shot+2011-05-18+at+09.54.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://2.bp.blogspot.com/-9lXahzAh9Ac/TdOONLIAnyI/AAAAAAAAACg/abImJ0JZ1PQ/s640/Screen+shot+2011-05-18+at+09.54.36.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;There you go. Continuous Deployment on RUN@cloud.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is a whole host of things you can do with this. &amp;nbsp;You can use build promotion to trigger the deployment, you can set up a staging deployment followed by the real thing if a test staging project builds successfully... I could go on... but there is always another day! Speaking of which, my next step will probably be enabling deployment straight from the project build (for those unfortunate enough to not have a CI server) probably using the &lt;a href="http://mojo.codehaus.org/ship-maven-plugin"&gt;ship-maven-plugin&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-828264878015915640?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/828264878015915640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=828264878015915640' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/828264878015915640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/828264878015915640'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/05/continuously-deploy-your-java-apps-to.html' title='Continuously deploy your java apps to the cloud'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-3KYlccVpLcs/TdOMFgqS-7I/AAAAAAAAABw/ap-fQVzcVqI/s72-c/Screen+shot+2011-05-18+at+09.43.35.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1753910047661907517</id><published>2011-05-17T10:40:00.001+01:00</published><updated>2011-05-30T14:56:55.396+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CloudBees'/><title type='text'>Deploy your java apps to the cloud</title><content type='html'>I work for &lt;a href="http://www.cloudbees.com/"&gt;CloudBees Inc.&lt;/a&gt;, they are a great company with great products. I have mostly been working on the &lt;a href="http://www.cloudbees.com/dev.cb"&gt;DEV@&lt;/a&gt; side of the fence which is focused on continuous integration and basically the development side of your application, but we also have the &lt;a href="http://www.cloudbees.com/run.cb"&gt;RUN@&lt;/a&gt; side of the fence where we provide a &lt;a href="http://en.wikipedia.org/wiki/Platform_as_a_service"&gt;platform as a service (PaaS)&lt;/a&gt; for running your java web applications on the cloud. I could give you the sales pitch, but I'll leave it at: the technologies and people behind RUN@ were one of the key reasons why I decided to join CloudBees.&lt;br /&gt;&lt;br /&gt;Well I've been busy on &lt;a href="http://nectar.cloudbees.com/products-features-scale.cb#rbac"&gt;some stuff&lt;/a&gt; since joining, so I decided it was time to actually try out the RUN@ stuff for my self. &amp;nbsp;So here is my experience:&lt;br /&gt;&lt;br /&gt;My test application:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I'm on the &lt;a href="http://maven.apache.org/"&gt;Apache Maven&lt;/a&gt; PMC, so I'm going to build it with... shock... horror... Maven.&lt;/li&gt;&lt;li&gt;I am partial to the odd bit of JSF, so it will be a JSF 2.0 application based off of &lt;a href="http://myfaces.apache.org/"&gt;Apache MyFaces&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;I love &lt;a href="http://eclipse.org/jetty/"&gt;Jetty&lt;/a&gt; as a servlet container for local testing, so we'll use that hammer too.&lt;/li&gt;&lt;/ul&gt;Let's get started...&lt;br /&gt;&lt;br /&gt;First the pom.xml&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&amp;gt;&lt;br /&gt;  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- basic information --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;com.blogspot.javaadventure.cloudbees.run&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;jsf2-hello-world&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;0.1-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Project information --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;name&amp;gt;JSF 2.0 Hello World&amp;lt;/name&amp;gt;&lt;br /&gt;  &amp;lt;description&amp;gt;&lt;br /&gt;    A JSF 2.0 web application that says hello world.&lt;br /&gt;  &amp;lt;/description&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;properties&amp;gt;&lt;br /&gt;    &amp;lt;project.reporting.outputEncoding&amp;gt;UTF-8&amp;lt;/project.reporting.outputEncoding&amp;gt;&lt;br /&gt;    &amp;lt;project.build.outputEncoding&amp;gt;UTF-8&amp;lt;/project.build.outputEncoding&amp;gt;&lt;br /&gt;    &amp;lt;project.build.sourceEncoding&amp;gt;UTF-8&amp;lt;/project.build.sourceEncoding&amp;gt;&lt;br /&gt;  &amp;lt;/properties&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Dependency details --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependencies&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.apache.myfaces.core&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;myfaces-api&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0.5&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.apache.myfaces.core&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;myfaces-impl&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0.5&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;4.8.2&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;  &amp;lt;/dependencies&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- Build settings --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;build&amp;gt;&lt;br /&gt;    &amp;lt;pluginManagement&amp;gt;&lt;br /&gt;      &amp;lt;plugins&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-clean-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.4.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.3.2&amp;lt;/version&amp;gt;&lt;br /&gt;          &amp;lt;configuration&amp;gt;&lt;br /&gt;            &amp;lt;source&amp;gt;1.6&amp;lt;/source&amp;gt;&lt;br /&gt;            &amp;lt;target&amp;gt;1.6&amp;lt;/target&amp;gt;&lt;br /&gt;          &amp;lt;/configuration&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-deploy-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.6&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-failsafe-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.8.1&amp;lt;/version&amp;gt;&lt;br /&gt;          &amp;lt;executions&amp;gt;&lt;br /&gt;            &amp;lt;execution&amp;gt;&lt;br /&gt;              &amp;lt;goals&amp;gt;&lt;br /&gt;                &amp;lt;goal&amp;gt;integration-test&amp;lt;/goal&amp;gt;&lt;br /&gt;                &amp;lt;goal&amp;gt;verify&amp;lt;/goal&amp;gt;&lt;br /&gt;              &amp;lt;/goals&amp;gt;&lt;br /&gt;            &amp;lt;/execution&amp;gt;&lt;br /&gt;          &amp;lt;/executions&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-install-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.3.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-jar-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.3.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-surefire-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.8.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-release-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;maven-resources-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;2.5&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;        &amp;lt;plugin&amp;gt;&lt;br /&gt;          &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;jetty-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;8.0.0.M2&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/plugin&amp;gt;&lt;br /&gt;      &amp;lt;/plugins&amp;gt;&lt;br /&gt;    &amp;lt;/pluginManagement&amp;gt;&lt;br /&gt;    &amp;lt;plugins&amp;gt;&lt;br /&gt;      &amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-release-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;autoVersionSubmodules&amp;gt;true&amp;lt;/autoVersionSubmodules&amp;gt;&lt;br /&gt;          &amp;lt;goals&amp;gt;install&amp;lt;/goals&amp;gt;&lt;br /&gt;        &amp;lt;/configuration&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;    &amp;lt;/plugins&amp;gt;&lt;br /&gt;  &amp;lt;/build&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;Then the src/main/webapp/WEB-INF/web.xml&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"&lt;br /&gt;         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"&amp;gt;&lt;br /&gt;  &amp;lt;display-name&amp;gt;JSF 2.0 Hello World&amp;lt;/display-name&amp;gt;&lt;br /&gt;  &amp;lt;description&amp;gt;&lt;br /&gt;    A JSF 2.0 web application that says hello world.&lt;br /&gt;  &amp;lt;/description&amp;gt;&lt;br /&gt;  &amp;lt;context-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;javax.faces.STATE_SAVING_METHOD&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;server&amp;lt;/param-value&amp;gt;&lt;br /&gt;  &amp;lt;/context-param&amp;gt;&lt;br /&gt;  &amp;lt;context-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;javax.faces.DEFAULT_SUFFIX&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;.xhtml&amp;lt;/param-value&amp;gt;&lt;br /&gt;  &amp;lt;/context-param&amp;gt;&lt;br /&gt;  &amp;lt;context-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;javax.faces.FACELETS_SKIP_COMMENTS&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;true&amp;lt;/param-value&amp;gt;&lt;br /&gt;  &amp;lt;/context-param&amp;gt;&lt;br /&gt;  &amp;lt;context-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;javax.faces.PROJECT_STAGE&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;Production&amp;lt;/param-value&amp;gt;&lt;br /&gt;    &amp;lt;!--param-value&amp;gt;Development&amp;lt;/param-value--&amp;gt;&lt;br /&gt;  &amp;lt;/context-param&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;!-- [jetty] does not initialize myfaces correctly for some reason --&amp;gt;&lt;br /&gt;  &amp;lt;listener&amp;gt;&lt;br /&gt;    &amp;lt;listener-class&amp;gt;org.apache.myfaces.webapp.StartupServletContextListener&amp;lt;/listener-class&amp;gt;&lt;br /&gt;  &amp;lt;/listener&amp;gt;&lt;br /&gt;  &amp;lt;!-- [/jetty] --&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;servlet&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;Faces Servlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;servlet-class&amp;gt;javax.faces.webapp.FacesServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;    &amp;lt;load-on-startup&amp;gt;1&amp;lt;/load-on-startup&amp;gt;&lt;br /&gt;  &amp;lt;/servlet&amp;gt;&lt;br /&gt;  &amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;Faces Servlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;url-pattern&amp;gt;*.xhtml&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;session-config&amp;gt;&lt;br /&gt;    &amp;lt;session-timeout&amp;gt;60&amp;lt;/session-timeout&amp;gt;&lt;br /&gt;  &amp;lt;/session-config&amp;gt;&lt;br /&gt;  &amp;lt;welcome-file-list&amp;gt;&lt;br /&gt;    &amp;lt;welcome-file&amp;gt;index.xhtml&amp;lt;/welcome-file&amp;gt;&lt;br /&gt;  &amp;lt;/welcome-file-list&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/web-app&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;Then the backing bean (src/main/java/com/blogspot/javaadventure/cloudbees/run/GreeterBean.java)&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;package com.blogspot.javaadventure.cloudbees.run;&lt;br /&gt;&lt;br /&gt;import javax.faces.bean.ManagedBean;&lt;br /&gt;import javax.faces.bean.ViewScoped;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;&lt;br /&gt;@ManagedBean(name="greeter")&lt;br /&gt;@ViewScoped&lt;br /&gt;public class GreeterBean implements Serializable {&lt;br /&gt;    private String name;&lt;br /&gt;&lt;br /&gt;    public String getName() {&lt;br /&gt;        return name;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setName(String name) {&lt;br /&gt;        this.name = name;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getResponse() {&lt;br /&gt;        if (name != null &amp;amp;&amp;amp; !name.isEmpty()) {&lt;br /&gt;            return "Hello " + name;&lt;br /&gt;        } else {&lt;br /&gt;            return null;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;Should always have some tests (src/test/java/com/blogspot/javaadventure/cloudbees/run/GreeterBeanTest.java)&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;package com.blogspot.javaadventure.cloudbees.run;&lt;br /&gt;&lt;br /&gt;import org.junit.Test;&lt;br /&gt;&lt;br /&gt;import static org.hamcrest.CoreMatchers.*;&lt;br /&gt;import static org.junit.Assert.*;&lt;br /&gt;&lt;br /&gt;public class GreeterBeanTest {&lt;br /&gt;    @Test&lt;br /&gt;    public void nullNameMeansNoGreeting() throws Exception {&lt;br /&gt;        GreeterBean instance = new GreeterBean();&lt;br /&gt;        instance.setName(null);&lt;br /&gt;        assertThat(instance.getResponse(), nullValue());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Test&lt;br /&gt;    public void noNameMeansNoGreeting() throws Exception {&lt;br /&gt;        GreeterBean instance = new GreeterBean();&lt;br /&gt;        instance.setName("");&lt;br /&gt;        assertThat(instance.getResponse(), nullValue());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Test&lt;br /&gt;    public void aNameMeansGreeting() throws Exception {&lt;br /&gt;        GreeterBean instance = new GreeterBean();&lt;br /&gt;        instance.setName("Fred");&lt;br /&gt;        assertThat(instance.getResponse(), notNullValue());&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/blockquote&gt;Next the page of our web application (src/main/webapp/index.xhtml), i'm going to use the JSF 2.0 ajax support (because it's there)&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;      xmlns:ui="http://java.sun.com/jsf/facelets"&lt;br /&gt;      xmlns:h="http://java.sun.com/jsf/html"&lt;br /&gt;      xmlns:f="http://java.sun.com/jsf/core"&amp;gt;&lt;br /&gt;&amp;lt;ui:insert name="metadata"/&amp;gt;&lt;br /&gt;&amp;lt;h:head&amp;gt;&lt;br /&gt;    &amp;lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;&lt;br /&gt;        JSF 2.0 Hello World&lt;br /&gt;    &amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/h:head&amp;gt;&lt;br /&gt;&amp;lt;h:body&amp;gt;&lt;br /&gt;    &amp;lt;f:view&amp;gt;&lt;br /&gt;        &amp;lt;h:form&amp;gt;&lt;br /&gt;            &amp;lt;h:outputLabel for="greeter" value="Please tell me your name:"/&amp;gt;&lt;br /&gt;            &amp;lt;h:inputText id="greeter" value="#{greeter.name}"&amp;gt;&lt;br /&gt;                &amp;lt;f:ajax event="keyup" render="text"/&amp;gt;&lt;br /&gt;            &amp;lt;/h:inputText&amp;gt;&lt;br /&gt;        &amp;lt;/h:form&amp;gt;&lt;br /&gt;        &amp;lt;h:outputText id="text" value="${greeter.response}"/&amp;gt;&lt;br /&gt;    &amp;lt;/f:view&amp;gt;&lt;br /&gt;&amp;lt;/h:body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;Let's test it locally&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ mvn jetty:run&lt;/pre&gt;&lt;pre&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO]                                                                         &lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Building JSF 2.0 Hello World 0.1-SNAPSHOT&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] &lt;br /&gt;&lt;/pre&gt;&lt;pre&gt;...&lt;/pre&gt;&lt;pre&gt;WARNING: &lt;br /&gt;&lt;br /&gt;*******************************************************************&lt;br /&gt;*** WARNING: Apache MyFaces-2 is running in DEVELOPMENT mode.   ***&lt;br /&gt;***                                         ^^^^^^^^^^^         ***&lt;br /&gt;*** Do NOT deploy to your live server(s) without changing this. ***&lt;br /&gt;*** See Application#getProjectStage() for more information.     ***&lt;br /&gt;*******************************************************************&lt;br /&gt;&lt;br /&gt;2011-05-17 10:22:10.982:INFO::Started SelectChannelConnector@0.0.0.0:8080&lt;br /&gt;[INFO] Started Jetty Server&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fire up a browser to http://localhost:8080/ and here's what we get:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-uT5H_QxEFJ4/TdI-3s8A-VI/AAAAAAAAABo/vDQmZXZf_Vk/s1600/Screen+shot+2011-05-17+at+10.24.27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="http://1.bp.blogspot.com/-uT5H_QxEFJ4/TdI-3s8A-VI/AAAAAAAAABo/vDQmZXZf_Vk/s320/Screen+shot+2011-05-17+at+10.24.27.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;OK, so now I turn off DEVELOPMENT mode in the web.xml, build my app and deploy it to RUN@cloud... and &lt;a href="http://jsf2-hello-world.stephenc.cloudbees.net/"&gt;here&lt;/a&gt;'s what we get:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-uqTMwA58wW4/TdI_3ZaTi1I/AAAAAAAAABs/M_kvGO67ll0/s1600/Screen+shot+2011-05-17+at+10.28.45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="http://4.bp.blogspot.com/-uqTMwA58wW4/TdI_3ZaTi1I/AAAAAAAAABs/M_kvGO67ll0/s320/Screen+shot+2011-05-17+at+10.28.45.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;That was cool. Didn't have to change anything (other than switch to production mode for safety as it's being deployed in the wild) and I did all this in under 20 minutes (including signing up for RUN@cloud)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My next steps will be to integrate this web application with DEV@cloud and our Jenkins plugin for deployment to RUN@cloud so that I can show off continuous deployment! But that will be a different day!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1753910047661907517?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1753910047661907517/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1753910047661907517' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1753910047661907517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1753910047661907517'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/05/deploy-your-java-apps-to-cloud.html' title='Deploy your java apps to the cloud'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-uT5H_QxEFJ4/TdI-3s8A-VI/AAAAAAAAABo/vDQmZXZf_Vk/s72-c/Screen+shot+2011-05-17+at+10.24.27.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4861736527599840448</id><published>2011-04-26T15:21:00.000+01:00</published><updated>2011-05-30T14:57:19.839+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><title type='text'>Quick and very Dirty Mavenizer</title><content type='html'>The following quick and dirty bash script will take a pom and a jar and fake a maven build based on the source files for that that can be found in the current directory.&lt;br /&gt;&lt;br /&gt;Really useful when running &lt;code&gt;mvn dependency:analyze&lt;/code&gt; on a project you are validating POMs for.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash&lt;br /&gt;&lt;br /&gt;if [ "A$3" == "A" ]&lt;br /&gt;then&lt;br /&gt;  echo "Syntax: $0 pomfile jarfile dir"&lt;br /&gt;  return&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;rm -rvf "$3/src"&lt;br /&gt;mkdir -p "$3/src/main/java"&lt;br /&gt;cp -f "$1" "$3/pom.xml"&lt;br /&gt;for name in $(jar -tf "$2" | sed -n -e "/\\$/d;s/\\.class/.java/p")&lt;br /&gt;do &lt;br /&gt;  echo -n "Looking for $name ... "&lt;br /&gt;  loc="$(find . | fgrep $name | head -n 1)"&lt;br /&gt;  if [ "A$loc" == "A" ]&lt;br /&gt;  then &lt;br /&gt;    echo "NOT FOUND"&lt;br /&gt;  else &lt;br /&gt;    echo "$loc"&lt;br /&gt;    mkdir -p "$3/src/main/java/$(dirname $name)"&lt;br /&gt;    cp "$loc" "$3/src/main/java/$name"&lt;br /&gt;  fi&lt;br /&gt;done&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4861736527599840448?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4861736527599840448/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4861736527599840448' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4861736527599840448'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4861736527599840448'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/04/quick-and-very-dirty-mavenizer.html' title='Quick and very Dirty Mavenizer'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-18060485254198505</id><published>2011-03-25T09:45:00.001Z</published><updated>2011-05-30T14:59:01.583+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Shell'/><title type='text'>My magic Maven and ANT version selection bash functions (for the Mac)</title><content type='html'>OK, so a while back I posted my bash script for selecting the maven version to use for the current session &lt;a href="http://s.apache.org/FQ2"&gt;http://s.apache.org/FQ2&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now that I have a Mac for my full time development machine, I thought I would share my version of these functions for Mac users:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;usemvn ()&lt;br /&gt;{&lt;br /&gt;    if [ -z "$1" -o ! -x "/usr/share/java/maven-$1/bin/mvn" ]&lt;br /&gt;    then&lt;br /&gt;        local prefix="Syntax: usemvn "&lt;br /&gt;        for i in /usr/share/java/maven-*&lt;br /&gt;        do&lt;br /&gt;            if [ -x "$i/bin/mvn" ]; then&lt;br /&gt;                echo -n "$prefix$(basename $i | sed 's/^maven-//')"&lt;br /&gt;                prefix=" | "&lt;br /&gt;            fi&lt;br /&gt;        done&lt;br /&gt;        echo ""&lt;br /&gt;    else&lt;br /&gt;        if [ -z "$MAVEN_HOME" ]&lt;br /&gt;        then&lt;br /&gt;            export PATH=/usr/share/java/maven-$1/bin:$PATH&lt;br /&gt;        else&lt;br /&gt;            export PATH=$(echo $PATH|sed -e "s:$MAVEN_HOME/bin:/usr/share/java/maven-$1/bin:g")&lt;br /&gt;        fi&lt;br /&gt;        export MAVEN_HOME=/usr/share/java/maven-$1&lt;br /&gt;    fi&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;useant ()&lt;br /&gt;{&lt;br /&gt;    if [ -z "$1" -o ! -x "/usr/share/java/ant-$1/bin/ant" ]&lt;br /&gt;    then&lt;br /&gt;        local prefix="Syntax: useant "&lt;br /&gt;        for i in /usr/share/java/ant-*&lt;br /&gt;        do&lt;br /&gt;            if [ -x "$i/bin/ant" ]; then&lt;br /&gt;                echo -n "$prefix$(basename $i | sed 's/^ant-//')"&lt;br /&gt;                prefix=" | "&lt;br /&gt;            fi&lt;br /&gt;        done&lt;br /&gt;        echo ""&lt;br /&gt;    else&lt;br /&gt;        if [ -z "$ANT_HOME" ]&lt;br /&gt;        then&lt;br /&gt;            export PATH=/usr/share/java/ant-$1/bin:$PATH&lt;br /&gt;        else&lt;br /&gt;            export PATH=$(echo $PATH|sed -e "s:$ANT_HOME/bin:/usr/share/java/ant-$1/bin:g")&lt;br /&gt;        fi&lt;br /&gt;        export ANT_HOME=/usr/share/java/ant-$1&lt;br /&gt;    fi&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;Simply add the above into your ~/.bash_profile and you can use them from any bash shell.&lt;br /&gt;&lt;br /&gt;For example:&lt;br /&gt;&lt;pre&gt;[stephenc@stephenc ~]$ &lt;b&gt;mvn -version&lt;/b&gt;&lt;br /&gt;Apache Maven 3.0.2 (r1056850; 2011-01-09 00:58:10+0000)&lt;br /&gt;Java version: 1.6.0_24, vendor: Apple Inc.&lt;br /&gt;Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home&lt;br /&gt;Default locale: en_US, platform encoding: MacRoman&lt;br /&gt;OS name: "mac os x", version: "10.6.6", arch: "x86_64", family: "mac"&lt;br /&gt;[stephenc@stephenc ~]$ &lt;b&gt;usemvn&lt;/b&gt;&lt;br /&gt;Syntax: usemvn 2.2.0 | 2.2.1 | 3.0.2&lt;br /&gt;[stephenc@stephenc ~]$ &lt;b&gt;usemvn 2.2.1&lt;/b&gt;&lt;br /&gt;[stephenc@stephenc ~]$ &lt;b&gt;mvn -version&lt;/b&gt;&lt;br /&gt;Apache Maven 2.2.1 (r801777; 2009-08-06 20:16:01+0100)&lt;br /&gt;Java version: 1.6.0_24&lt;br /&gt;Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home&lt;br /&gt;Default locale: en_US, platform encoding: MacRoman&lt;br /&gt;OS name: "mac os x" version: "10.6.6" arch: "x86_64" Family: "mac"&lt;br /&gt;[stephenc@stephenc ~]$ &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The joy of these as bash functions is that they only affect the current session. So I can have one Terminal tab running Maven 2.2.1 and another running Maven 3.0.2&lt;br /&gt;&lt;br /&gt;Other tricks that people use by re-pointing the symlink are global and have global effect which makes hopping between tasks or testing different versions of Maven a whole lot harder.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-18060485254198505?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/18060485254198505/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=18060485254198505' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/18060485254198505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/18060485254198505'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/03/my-magic-maven-and-ant-version.html' title='My magic Maven and ANT version selection bash functions (for the Mac)'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4594175601340431134</id><published>2011-03-01T15:49:00.002Z</published><updated>2011-05-30T14:59:01.584+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><category scheme='http://www.blogger.com/atom/ns#' term='Shell'/><title type='text'>Handy bash script to fix a pom's SCM section to the current Subversion location</title><content type='html'>&lt;code&gt; #!/bin/bash&lt;br /&gt;&lt;br /&gt;URL="$(svn info | sed -n -e '/^URL:/{s/URL: *//p}')"&lt;br /&gt;ROOT="$(svn info | sed -n -e "/^Repository Root:/{s/Repository Root: *//p}")"&lt;br /&gt;NEW_PATH="${URL#$ROOT}"&lt;br /&gt;OLD_URL="$(sed -n '/&amp;lt; *scm *&amp;gt;/,/&amp;lt; *\/scm *&amp;gt;/p' pom.xml | sed -n '/&amp;lt; *connection *&amp;gt;/,/&amp;lt; *\/ *connection *&amp;gt;/{s/.*connection *&amp;gt; *scm:svn:\([^ &amp;lt;]*\)[ &amp;lt;].*/\1/p}')"&lt;br /&gt;OLD_PATH="${OLD_URL#$ROOT}"&lt;br /&gt;echo "OLD URL: $OLD_URL"&lt;br /&gt;echo "NEW URL: $URL"&lt;br /&gt;echo "ROOT: $ROOT"&lt;br /&gt;echo "OLD PATH: $OLD_PATH"&lt;br /&gt;echo "NEW PATH: $NEW_PATH"&lt;br /&gt;&lt;br /&gt;sed -i '/&amp;lt; *scm *&amp;gt;/,/&amp;lt; *\/scm *&amp;gt;/{s/'${OLD_PATH//\//\\\/}'/'${NEW_PATH//\//\\\/}'/}' pom.xml&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4594175601340431134?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4594175601340431134/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4594175601340431134' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4594175601340431134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4594175601340431134'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/03/handy-bash-script-to-fix-poms-scm.html' title='Handy bash script to fix a pom&apos;s SCM section to the current Subversion location'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-462852153297878600</id><published>2011-02-10T10:10:00.000Z</published><updated>2011-02-10T10:10:41.081Z</updated><title type='text'>A handy string for when testing i18n</title><content type='html'>Here's a handy string for i18n testing:&lt;br /&gt;&lt;br /&gt;用户汉语&lt;br /&gt;&lt;br /&gt;It can be handy to have a string in a non-english charater set that can be pushed end-to-end.&lt;br /&gt;&lt;br /&gt;This should be a sad sad joke similar to the writing on the bottom of a leprechaun I saw in a gift shop in Ennis:&lt;br /&gt;&lt;br /&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;Déanta&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;i dtír&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;eile&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I should point out that it was probably&lt;br /&gt;&lt;br /&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;Déanta sa&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;tSín&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;Still&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt; &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;Mura féidir&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;leat&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;tuiscint a fháil ar&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;an&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;ansin&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;is féidir leat&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;léamh&lt;/span&gt; &lt;span class="hps" title="Click for alternate translations"&gt;na Gaeilge.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt;Google seems to suggest that 中国扬声器可以阅读本 would be a better string to go with though!&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span class="short_text" id="result_box" lang="ga"&gt;&lt;span class="hps" title="Click for alternate translations"&gt; &lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-462852153297878600?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/462852153297878600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=462852153297878600' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/462852153297878600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/462852153297878600'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2011/02/handy-string-for-when-testing-i18n_10.html' title='A handy string for when testing i18n'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6063912227357002103</id><published>2010-08-24T21:35:00.000+01:00</published><updated>2010-08-24T21:35:59.427+01:00</updated><title type='text'>The Clean Coder: QA or When do you flip a pancake?</title><content type='html'>An interesting blog on QA and development: &lt;a href="http://thecleancoder.blogspot.com/2010/08/qa-or-when-do-you-flip-pancake.html"&gt;The Clean Coder: QA or When do you flip a pancake?&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6063912227357002103?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://thecleancoder.blogspot.com/2010/08/qa-or-when-do-you-flip-pancake.html' title='The Clean Coder: QA or When do you flip a pancake?'/><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6063912227357002103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6063912227357002103' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6063912227357002103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6063912227357002103'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/08/clean-coder-qa-or-when-do-you-flip.html' title='The Clean Coder: QA or When do you flip a pancake?'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4406143326562719562</id><published>2010-07-26T09:25:00.002+01:00</published><updated>2010-08-05T10:38:07.328+01:00</updated><title type='text'>3D TV, level headed people will say it looks fine, looks bad lying  down</title><content type='html'>I had my first oportunity to see the new 3D TV's this weekend.&amp;nbsp; They looked OK to me.&amp;nbsp; There was a 3D effect.&amp;nbsp; All around me were people who were going "wow! this is great! this is absolutely fantastic!".&lt;br /&gt;&lt;br /&gt;But then I tilted my head, and low and behold, because the system uses horizontal separation to deliver 3D, it requires that your eyes be horizontally separated in order to deliver the 3D effect... when your eyes are vertical, however, it looks just as shit as when you have not got the glasses on.&lt;br /&gt;&lt;br /&gt;Now I don't know about you, but one of my favoured ways of watching TV is lying on the couch with my head turned sideways to face rthe TV... which results in my eyes being vertical not horizontal...&lt;br /&gt;&lt;br /&gt;So I'll add that to the top of the list of reasons why I'll not be buying a 1st generation 3D TV&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4406143326562719562?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4406143326562719562/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4406143326562719562' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4406143326562719562'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4406143326562719562'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/07/3d-tv-level-headed-people-will-say-it.html' title='3D TV, level headed people will say it looks fine, looks bad lying  down'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-7989810213452741773</id><published>2010-07-19T09:36:00.001+01:00</published><updated>2011-05-30T14:59:01.584+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><category scheme='http://www.blogger.com/atom/ns#' term='Shell'/><title type='text'>Quick and dirty remove -SNAPSHOTS from your local maven repository</title><content type='html'>Works on *nix&lt;br&gt;&lt;br&gt;&lt;span style="font-family: courier new,monospace;"&gt;find ~/.m2/repository -type d -name \*-SNAPSHOT -exec rm -rvf {} \;&lt;/span&gt;&lt;br&gt;&lt;br&gt;By searching for the directories we should catch the -YYYYMMDD.HHMMSS format of snapshots also&lt;br&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-7989810213452741773?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/7989810213452741773/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=7989810213452741773' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7989810213452741773'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7989810213452741773'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/07/quick-and-dirty-remove-snapshots-from.html' title='Quick and dirty remove -SNAPSHOTS from your local maven repository'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-8218071183667219577</id><published>2010-07-08T11:54:00.002+01:00</published><updated>2011-05-30T14:58:19.449+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Keeping Hudson configuration and data in SVN</title><content type='html'>We recently lost our hudson server due to a multiple disk failure in the RAID array storing our hudson configuration. [5 of the 15 disks died]&lt;br /&gt;&lt;br /&gt;So I've been looking into a backup script that will allow us to keep a backup of the configuration.&amp;nbsp; We use Maven for most of our builds, so the released artifacts are in our Maven Repository (which is hosted on two servers each with RAID arrays and using DRBD to mirror between the pair, with an rsync to a NAS in another cabinet and we are trying to get an rsych to an off-site storage going as well).&lt;br /&gt;&lt;br /&gt;There seem to be two main options:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use the &lt;a href="http://wiki.hudson-ci.org/display/HUDSON/Backup+Plugin?showComments=false"&gt;Hudson Backup Plugin&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Backup your Hudson configuration to Source Control.&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;Option 1 is nice, but you still have to copy off the backup files and find  some safe place to store them.&amp;nbsp; Option 2 sounds better to me as if we loose our source control system, we'll there's nothing to build anyway!&lt;br /&gt;&lt;br /&gt;So a quick search of the interwebs revealed &lt;a href="http://eng.genius.com/blog/2010/02/02/hudson-in-svn/"&gt;Mike Rooney&lt;/a&gt; has been here before... cool... I have somewhere to start... with a few tweaks I have ended up with the following:&lt;br /&gt;&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;if [[ ! -d $HUDSON_HOME ]]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;then&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; echo "Hudson home directory ($HUDSON_HOME) is missing or undefined"&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; exit 1&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;cd $HUDSON_HOME&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# Add any new conf files, jobs, users, and content.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn add -q --parents *.xml jobs/*/config.xml users/*/config.xml userContent/*&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# Add the names of plugins so that we know what plugins we have&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;ls -l plugins &amp;gt; plugins.list&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn add -q -N --parents plugins.list&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# Ignore things in the root we don't care about.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;echo -e "war\nlog\n*.log\n*.tmp\n*.old\n*.bak\n*.jar\n*.json\nsecret.key\ntools\nshelvedProjects\n.owner\nupdates\nplugins" &amp;gt; myignores&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn ps -q svn:ignore -F myignores . &amp;amp;&amp;amp; rm myignores&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# Ignore things in jobs/* we don't care about.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;echo -e "builds\nlast*\nnext*\n*.txt\n*.log\nworkspace*\ncobertura\njavadoc\nhtmlreports\nncover\ndoclinks" &amp;gt; myignores&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn ps -q svn:ignore -F myignores jobs/* &amp;amp;&amp;amp; rm myignores&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# Remove anything from SVN that no longer exists in Hudson.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn st | sed -n -e "s/^\!//p" | xargs -r svn rm&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;# And finally, check in of course, showing status before and after for logging.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-family: courier new,monospace;"&gt;svn st &amp;amp;&amp;amp; svn ci --non-interactive --username=hudson-build -m "automated commit of Hudson configuration" &amp;amp;&amp;amp; svn st&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;The main changes are that I've updated the root level ignores and I capture a listing of the plugins directory so that you can know exactly what plugins you had installed&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-8218071183667219577?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/8218071183667219577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=8218071183667219577' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8218071183667219577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8218071183667219577'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/07/keeping-hudson-configuration-and-data.html' title='Keeping Hudson configuration and data in SVN'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6435736093690705747</id><published>2010-05-14T10:52:00.001+01:00</published><updated>2011-05-30T14:59:01.585+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Shell'/><title type='text'>Easy parallelization with Bash in Linux</title><content type='html'>Cool post I just found:&lt;br&gt;&lt;br&gt;&lt;a href="http://coldattic.info/shvedsky/pro/blogs/a-foo-walks-into-a-bar/posts/7"&gt;http://coldattic.info/shvedsky/pro/blogs/a-foo-walks-into-a-bar/posts/7&lt;/a&gt;&lt;br&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6435736093690705747?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6435736093690705747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6435736093690705747' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6435736093690705747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6435736093690705747'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/05/easy-parallelization-with-bash-in-linux.html' title='Easy parallelization with Bash in Linux'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4876278249066015490</id><published>2010-04-03T18:14:00.001+01:00</published><updated>2010-04-03T18:14:08.121+01:00</updated><title type='text'>Crazy counting...</title><content type='html'>When you are keeping build/version number in strings, you really need to left pad with 0&amp;#39;s in order for string sorting to work...&lt;br&gt;&lt;br&gt;e.g. 1.0-alpha-2 &amp;gt; 1.0-alpha-1 &amp;gt; 1.0-alpha-10&lt;br&gt;&lt;br&gt;So as long as we start off with a leading zero, we are fine, e.g. 1.0-alpha-01&lt;br&gt; &lt;br&gt;Of course the next question is how much padding...&lt;br&gt;&lt;br&gt;Eventually you will reach a point where the amount of padding is too much... enter crazy counting...&lt;br&gt;&lt;br&gt;Start off with a small amount/no padding and when it looks likely that you will run out, skip versions...&lt;br&gt; &lt;br&gt;With base 10, you&amp;#39;ll probably want to start skipping somewhere around the 5-7 mark, so we&amp;#39;d have&lt;br&gt;&lt;br&gt;1.0-alpha-1, 1.0-alpha-2, 1.0-alpha-3, 1.0-alpha-4 &lt;br&gt;(hmmm we&amp;#39;re burning a lot of alphas... looks like we might make 10 or 15, better start crazy counting)&lt;br&gt; 1.0-alpha-50, 1.0-alpha-51, ... 1.0-alpha-99 and we&amp;#39;re screwed unless we did another crazy counting...&lt;br&gt;&lt;br&gt;If we thought we&amp;#39;d need lots more than 50 builds, we could go really crazy&lt;br&gt;&lt;br&gt;1.0-alpha-4 -&amp;gt; 1.0-alpha-500 -&amp;gt; 1.0-alpha-501, etc&lt;br&gt; &lt;br&gt;BTW, crazy counting is handy with Maven which uses string based comparison for qualifiers&lt;br&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4876278249066015490?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4876278249066015490/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4876278249066015490' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4876278249066015490'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4876278249066015490'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/04/crazy-counting.html' title='Crazy counting...'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-2532377059957024211</id><published>2010-03-24T11:58:00.002Z</published><updated>2010-03-24T12:25:46.674Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Donny Don&apos;t'/><title type='text'>Don't Do What Donny Don't Does #1 - Seam @Synchronized and  synchronized methods</title><content type='html'>I am starting a series of things you should not do:&lt;br /&gt;&lt;img alt="0011.JPG" src="http://springfieldfiles.com/albums/books/0011.JPG" title="0011.JPG" /&gt;&lt;br /&gt;&lt;br /&gt;Seam has this handy annotation: &lt;a href="http://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;ct=res&amp;amp;cd=1&amp;amp;ved=0CAgQFjAA&amp;amp;url=http%3A%2F%2Fdocs.jboss.org%2Fseam%2F1.1GA%2Fapi%2Forg%2Fjboss%2Fseam%2Fannotations%2FSynchronized.html&amp;amp;ei=0-upS5uqJYvyNP3V_eQB&amp;amp;usg=AFQjCNG8cnfD4zCN-VH28nWD_W1eoRMqxA&amp;amp;sig2=Yxc0t-IQgA5yST-uA40img"&gt;@Synchronized&lt;/a&gt; which ensures that only a single thread may access the methods/fields of the component at the same time.&lt;br /&gt;&lt;br /&gt;Often times it is easy to forget that SESSION scoped Seam components are automatically @Synchronized&lt;br /&gt;&lt;br /&gt;Java has this (formerly) handy modifier &lt;a href="http://java.sun.com/docs/books/tutorial/essential/concurrency/locksync.html"&gt;synchronized&lt;/a&gt; which when applied to a method, ensures that the object's implicit lock is held whenever the method is invoked.&lt;br /&gt;&lt;br /&gt;Hilarity will ensue if you have an @Synchronized component with synchronized methods (which you should not have to apply because the component is @Synchronized), e.g.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;@Name("donny")&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;@Scope(ScopeType.SESSION)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;public class DonnyDont {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;&amp;nbsp; public synchronized String dont() {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // some stuff&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;&amp;nbsp; }&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new,monospace;"&gt;}&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-2532377059957024211?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/2532377059957024211/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=2532377059957024211' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2532377059957024211'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2532377059957024211'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/03/dont-do-what-donny-dont-does-1-seam.html' title='Don&apos;t Do What Donny Don&apos;t Does #1 - Seam @Synchronized and  synchronized methods'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-2003796456241737032</id><published>2010-03-10T13:01:00.001Z</published><updated>2010-03-24T12:28:20.440Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><title type='text'>OpenEjb, Jetty and Maven - Transaction Management</title><content type='html'>I&amp;#39;ve been meaning to blog about getting transaction management working with OpenEjb and Jetty using jetty:run... it&amp;#39;s still an on-going story... but the following might get you going...&lt;br&gt;&lt;br&gt;First off, in your pom.xml you need to add the configuration for maven-jetty-plugin... we need to dance around the various activemq/activeio versions and ensure that we get the correct version of ant...&lt;br&gt;  &lt;br&gt;&lt;div style="margin-left: 40px; font-family: courier new,monospace;"&gt;&lt;font size="1"&gt;&amp;lt;project xmlns=&amp;quot;&lt;a href="http://maven.apache.org/POM/4.0.0" target="_blank"&gt;http://maven.apache.org/POM/4.0.0&lt;/a&gt;&amp;quot; xmlns:xsi=&amp;quot;&lt;a href="http://www.w3.org/2001/XMLSchema-instance" target="_blank"&gt;http://www.w3.org/2001/XMLSchema-instance&lt;/a&gt;&amp;quot;&lt;br&gt;           xsi:schemaLocation=&amp;quot;&lt;a href="http://maven.apache.org/POM/4.0.0" target="_blank"&gt;http://maven.apache.org/POM/4.0.0&lt;/a&gt; &lt;a href="http://maven.apache.org/maven-v4_0_0.xsd" target="_blank"&gt;http://maven.apache.org/maven-v4_0_0.xsd&lt;/a&gt;&amp;quot;&amp;gt;&lt;br&gt;      &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br&gt;    &amp;lt;groupId&amp;gt;org.apache.openejb.examples&amp;lt;/groupId&amp;gt;&lt;br&gt;    &amp;lt;artifactId&amp;gt;jetty-openejb&amp;lt;/artifactId&amp;gt;&lt;br&gt;    &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br&gt;      &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br&gt;    &amp;lt;name&amp;gt;jetty-openejb Maven Webapp&amp;lt;/name&amp;gt;&lt;br&gt;    &amp;lt;url&amp;gt;&lt;a href="http://maven.apache.org" target="_blank"&gt;http://maven.apache.org&lt;/a&gt;&amp;lt;/url&amp;gt;&lt;br&gt;    &amp;lt;dependencies&amp;gt;&lt;br&gt;          &amp;lt;dependency&amp;gt;&lt;br&gt;            &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br&gt;            &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br&gt;            &amp;lt;version&amp;gt;3.8.1&amp;lt;/version&amp;gt;&lt;br&gt;            &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br&gt;          &amp;lt;/dependency&amp;gt;&lt;br&gt;    &amp;lt;/dependencies&amp;gt;&lt;br&gt;    &amp;lt;build&amp;gt;&lt;br&gt;        &amp;lt;finalName&amp;gt;${project.artifactId}&amp;lt;/finalName&amp;gt;&lt;br&gt;        &amp;lt;plugins&amp;gt;&lt;br&gt;            &amp;lt;plugin&amp;gt;&lt;br&gt;                &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br&gt;                  &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt;                &amp;lt;version&amp;gt;6.1.22&amp;lt;/version&amp;gt;&lt;br&gt;&lt;br&gt;                &amp;lt;dependencies&amp;gt;&lt;br&gt;                    &amp;lt;dependency&amp;gt;&lt;br&gt;                        &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                          &amp;lt;artifactId&amp;gt;activemq-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                        &amp;lt;version&amp;gt;4.1.1&amp;lt;/version&amp;gt;&lt;br&gt;                        &amp;lt;exclusions&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                  &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                             &amp;lt;exclusion&amp;gt;&lt;br&gt;                                 &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging-api&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                              &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;activeio-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                              &amp;lt;/exclusion&amp;gt;&lt;br&gt;                        &amp;lt;/exclusions&amp;gt;&lt;br&gt;                    &amp;lt;/dependency&amp;gt;&lt;br&gt;&lt;br&gt;                    &amp;lt;dependency&amp;gt;&lt;br&gt;                        &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                          &amp;lt;artifactId&amp;gt;activemq-ra&amp;lt;/artifactId&amp;gt;&lt;br&gt;                        &amp;lt;version&amp;gt;4.1.1&amp;lt;/version&amp;gt;&lt;br&gt;                        &amp;lt;exclusions&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                  &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                             &amp;lt;exclusion&amp;gt;&lt;br&gt;                                 &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging-api&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                              &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;activeio-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                              &amp;lt;/exclusion&amp;gt;&lt;br&gt;                        &amp;lt;/exclusions&amp;gt;&lt;br&gt;                    &amp;lt;/dependency&amp;gt;&lt;br&gt;&lt;br&gt;                    &amp;lt;dependency&amp;gt;&lt;br&gt;                        &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                          &amp;lt;artifactId&amp;gt;activeio-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                        &amp;lt;version&amp;gt;3.1.2&amp;lt;/version&amp;gt;&lt;br&gt;                        &amp;lt;exclusions&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                  &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                             &amp;lt;exclusion&amp;gt;&lt;br&gt;                                 &amp;lt;groupId&amp;gt;commons-logging&amp;lt;/groupId&amp;gt;&lt;br&gt;                                &amp;lt;artifactId&amp;gt;commons-logging-api&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                          &amp;lt;/exclusions&amp;gt;&lt;br&gt;                    &amp;lt;/dependency&amp;gt;&lt;br&gt;&lt;br&gt;                    &amp;lt;dependency&amp;gt;&lt;br&gt;                        &amp;lt;groupId&amp;gt;org.apache.openejb&amp;lt;/groupId&amp;gt;&lt;br&gt;                        &amp;lt;artifactId&amp;gt;openejb-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                          &amp;lt;version&amp;gt;3.1.2&amp;lt;/version&amp;gt;&lt;br&gt;                        &amp;lt;exclusions&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                                  &amp;lt;artifactId&amp;gt;activemq-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                                  &amp;lt;artifactId&amp;gt;activemq-ra&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;org.apache.activemq&amp;lt;/groupId&amp;gt;&lt;br&gt;                                  &amp;lt;artifactId&amp;gt;activeio-core&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br&gt;                                  &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                        &amp;lt;/exclusions&amp;gt;&lt;br&gt;                    &amp;lt;/dependency&amp;gt;&lt;br&gt;                    &amp;lt;!-- in order to use the latest version of openejb, we need to exclude&lt;br&gt;                           the dependencies provided in jsp-2.1-jetty --&amp;gt;&lt;br&gt;                    &amp;lt;dependency&amp;gt;&lt;br&gt;                        &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br&gt;                        &amp;lt;artifactId&amp;gt;jsp-2.1-jetty&amp;lt;/artifactId&amp;gt;&lt;br&gt;                          &amp;lt;version&amp;gt;6.1.22&amp;lt;/version&amp;gt;&lt;br&gt;                        &amp;lt;exclusions&amp;gt;&lt;br&gt;                            &amp;lt;exclusion&amp;gt;&lt;br&gt;                                &amp;lt;groupId&amp;gt;ant&amp;lt;/groupId&amp;gt;&lt;br&gt;                                  &amp;lt;artifactId&amp;gt;ant&amp;lt;/artifactId&amp;gt;&lt;br&gt;                            &amp;lt;/exclusion&amp;gt;&lt;br&gt;                        &amp;lt;/exclusions&amp;gt;&lt;br&gt;                    &amp;lt;/dependency&amp;gt;&lt;br&gt;&lt;br&gt;                 &amp;lt;/dependencies&amp;gt;&lt;br&gt;                 &amp;lt;configuration&amp;gt;&lt;br&gt;                    &amp;lt;jettyConfig&amp;gt;${basedir}/src/main/jetty/jetty.xml&amp;lt;/jettyConfig&amp;gt;&lt;br&gt;                &amp;lt;/configuration&amp;gt;&lt;br&gt;            &amp;lt;/plugin&amp;gt;&lt;br&gt;        &amp;lt;/plugins&amp;gt;&lt;br&gt;      &amp;lt;/build&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;/project&amp;gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;br&gt;Next we need to configure a src/main/jetty/jetty.xml to bind the UserTransaction instance into jetty...&lt;br&gt;&lt;br&gt;&lt;div style="margin-left: 40px; font-family: courier new,monospace;"&gt;  &lt;font size="1"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;&lt;br&gt;&amp;lt;!DOCTYPE Configure PUBLIC &amp;quot;-//Mort Bay Consulting//DTD Configure//EN&amp;quot; &amp;quot;&lt;a href="http://jetty.mortbay.org/configure.dtd" target="_blank"&gt;http://jetty.mortbay.org/configure.dtd&lt;/a&gt;&amp;quot;&amp;gt;&lt;br&gt;  &amp;lt;Configure id=&amp;quot;srv&amp;quot; class=&amp;quot;org.mortbay.jetty.Server&amp;quot;&amp;gt;&lt;br&gt;&lt;br&gt;    &amp;lt;New class=&amp;quot;javax.naming.InitialContext&amp;quot;&amp;gt;&lt;br&gt;        &amp;lt;Arg&amp;gt;&lt;br&gt;            &amp;lt;New class=&amp;quot;java.util.Properties&amp;quot;&amp;gt;&lt;br&gt;                  &amp;lt;Call name=&amp;quot;setProperty&amp;quot;&amp;gt;&lt;br&gt;                    &amp;lt;Arg&amp;gt;java.naming.factory.initial&amp;lt;/Arg&amp;gt;&lt;br&gt;                    &amp;lt;Arg&amp;gt;org.apache.openejb.client.LocalInitialContextFactory&amp;lt;/Arg&amp;gt;&lt;br&gt;                  &amp;lt;/Call&amp;gt;&lt;br&gt;            &amp;lt;/New&amp;gt;&lt;br&gt;        &amp;lt;/Arg&amp;gt;&lt;br&gt;        &amp;lt;Call name=&amp;quot;lookup&amp;quot; id=&amp;quot;tm&amp;quot;&amp;gt;&lt;br&gt;            &amp;lt;Arg&amp;gt;openejb:TransactionManager&amp;lt;/Arg&amp;gt;&lt;br&gt;        &amp;lt;/Call&amp;gt;&lt;br&gt;      &amp;lt;/New&amp;gt;&lt;br&gt;&lt;br&gt;    &amp;lt;New class=&amp;quot;org.mortbay.jetty.plus.naming.Transaction&amp;quot;&amp;gt;&lt;br&gt;        &amp;lt;Arg&amp;gt;&lt;br&gt;            &amp;lt;New class=&amp;quot;org.apache.openejb.core.CoreUserTransaction&amp;quot;&amp;gt;&lt;br&gt;                &amp;lt;Arg&amp;gt;&lt;br&gt;                      &amp;lt;Ref id=&amp;quot;tm&amp;quot;/&amp;gt;&lt;br&gt;                &amp;lt;/Arg&amp;gt;&lt;br&gt;            &amp;lt;/New&amp;gt;&lt;br&gt;        &amp;lt;/Arg&amp;gt;&lt;br&gt;    &amp;lt;/New&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;/Configure&amp;gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;br&gt;And presto-chango, now jetty has a transaction manager provided by openejb.  (Note: if we don&amp;#39;t mind storing that in a jetty-env in /WEB-INF, you can put the same config in WEB-INF/jetty-env.xml)&lt;br&gt;  &lt;br&gt;OK, so here are the issues:&lt;br&gt;&lt;ul&gt;&lt;li&gt;Reloading does not work (because org.apache.openejb.core.ivm.naming.IvmContext does not support the destroySubcontext(Context) method&lt;/li&gt;&lt;li&gt;We are using jetty&amp;#39;s JNDI provider in the web-app and openejb&amp;#39;s JNDI provider for the EJBs... this is because&lt;br&gt;  &lt;br&gt;When jetty binds names to JNDI (using org.mortbay.jetty.plus.naming.Resource or org.mortbay.jetty.plus.naming.Transaction) it binds the object to JNDIName and it also binds a NamingEnrtry for the object to __/JNDIName&lt;br&gt;  &lt;br&gt;Unfortunately, openejb&amp;#39;s JNDI implementation seems to be somewhat strange in this regard... if we add the SystemProperties to jetty to have it use openejb&amp;#39;s JNDI implementation, e.g. add the following to /project/build/plugins/plugin[maven-jetty-plugin]/configuration/systemProperties&lt;br&gt; &lt;br&gt;&lt;font style="font-family: courier new,monospace;" size="1"&gt;                        &amp;lt;systemProperty&amp;gt;&lt;br&gt;                            &amp;lt;name&amp;gt;java.naming.factory.initial&amp;lt;/name&amp;gt;&lt;br&gt;                            &amp;lt;value&amp;gt;org.apache.openejb.client.LocalInitialContextFactory&amp;lt;/value&amp;gt;&lt;br&gt;                         &amp;lt;/systemProperty&amp;gt;&lt;/font&gt;&lt;br&gt;&lt;br&gt;Then when we bind __/UserTransaction it gets bound to openejb:__/UserTransaction but when we lookup __/UserTransaction openejb looks up openejb:local/__/UserTransaction&lt;br&gt; &lt;br&gt;And that is just for starters... there seems to be a whole host of other JNDI strangeness between jetty&amp;#39;s side and openejb&amp;#39;s side&lt;br&gt;&lt;br&gt;The side effect of all this is that if you want resource refs to work correctly, you need to fish them out of openejb&amp;#39;s JNDI context and push them into jetty&amp;#39;s JNDI context&lt;br&gt; &lt;/li&gt;&lt;/ul&gt;In any case this is at least a start!&lt;br&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-2003796456241737032?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/2003796456241737032/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=2003796456241737032' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2003796456241737032'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2003796456241737032'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/03/openejb-jetty-and-maven-transaction.html' title='OpenEjb, Jetty and Maven - Transaction Management'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-7534786279840886346</id><published>2010-02-17T13:20:00.004Z</published><updated>2010-08-31T12:25:29.761+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Subversion'/><title type='text'>Review Board and Subversion Hooks</title><content type='html'>&lt;a bitly="BITLY_PROCESSED" href="http://www.reviewboard.org/"&gt;Review Board&lt;/a&gt; is quite nice... it has a handy program for posting reviews (&lt;a bitly="BITLY_PROCESSED" href="http://www.reviewboard.org/docs/manual/1.0/users/tools/post-review/"&gt;postreview&lt;/a&gt;)... and you can integrate this into your &lt;a bitly="BITLY_PROCESSED" href="http://subversion.apache.org/"&gt;subversion&lt;/a&gt; hook scripts quite nicely...&lt;br /&gt;&lt;br /&gt;But what if you want to automate submitting reviews on only parts of your code base...&lt;br /&gt;&lt;br /&gt;What I want is to be able to set a property on a folder and then any time a file is changed in that folder or it's children, then a review will automatically be scheduled...&lt;br /&gt;&lt;br /&gt;So I wrote the following C/C++ helper (because we would potentially be forking a lot of svnlook processes) which checks to see if a property is set on any of the changed paths or the changed path parents.&lt;br /&gt;&lt;span style="font-size: x-small;"&gt;&lt;br style="font-family: courier new,monospace;" /&gt; &lt;/span&gt;&lt;br /&gt;&lt;div style="font-family: courier new,monospace; font-size: x-small; margin-left: 40px;"&gt;&lt;pre&gt;/*&lt;br /&gt;* Licensed to the Apache Software Foundation (ASF) under one&lt;br /&gt;* or more contributor license agreements.  See the NOTICE file&lt;br /&gt;* distributed with this work for additional information&lt;br /&gt;* regarding copyright ownership.  The ASF licenses this file&lt;br /&gt;* to you under the Apache License, Version 2.0 (the&lt;br /&gt;* "License"); you may not use this file except in compliance&lt;br /&gt;* with the License.  You may obtain a copy of the License at&lt;br /&gt;*&lt;br /&gt;*   &lt;a bitly="BITLY_PROCESSED" href="http://www.apache.org/licenses/LICENSE-2.0"&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br /&gt;*&lt;br /&gt;* Unless required by applicable law or agreed to in writing,&lt;br /&gt;* software distributed under the License is distributed on an&lt;br /&gt;* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY&lt;br /&gt;* KIND, either express or implied.  See the License for the&lt;br /&gt;* specific language governing permissions and limitations&lt;br /&gt;* under the License.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;br /&gt;#include &amp;lt;sys/wait.h&amp;gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;&amp;nbsp;* Returns a stream for the output of dirs-changed from svnlook.&lt;br /&gt;&amp;nbsp;*/&lt;br /&gt;FILE *svnlook_dirs_changed(char *svnlook, char *repo, char *rev) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char *cmd;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmd = (char *) malloc(strlen(svnlook) + strlen(" dirs-changed ") + strlen(repo) + strlen(" -r ") + strlen(rev) + 1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcpy(cmd, svnlook);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcat(cmd, " dirs-changed ");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcat(cmd, repo);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcat(cmd, " -r ");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcat(cmd, rev);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (FILE *) popen(cmd, "r");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;&amp;nbsp;* Uses svnlook to check and see if the specified property is set on the specified path in the specified repository.&lt;br /&gt;&amp;nbsp;*/&lt;br /&gt;int svnlook_is_prop_set(char *svnlook, char *repopath, char *propname, char *path) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int status;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; pid_t pid;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; pid = fork();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (pid == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; fclose(stdin);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FILE* fp = fopen("/dev/null", "w+");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (fileno(fp) != STDIN_FILENO) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _exit(EXIT_FAILURE);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _exit(EXIT_FAILURE);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _exit(EXIT_FAILURE);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; execl(svnlook, svnlook, "pg", repopath, propname, path, NULL);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("exit failure\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _exit(EXIT_FAILURE);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (pid &amp;lt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; status = -1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (waitpid(pid, &amp;amp;status, 0) != pid) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; status = -1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return status;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;&amp;nbsp;* Checks to see if any of the directories from the stream have the specified property set.&lt;br /&gt;&amp;nbsp;*/&lt;br /&gt;int check_svn_dirs(FILE* fdirschanged, char *svnlook,char *repopath, char *revnum, char *propname, int showallpaths) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char line[1024];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int rv = 0;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; while (fgets(line, sizeof line, fdirschanged)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; for (unsigned int i = 0; i &amp;lt; sizeof line; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (line[i] == '\n') {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; line[i] = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; for (int j = i; j &amp;gt; 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (line[j] == '/') {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; line[j] = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (0 == svnlook_is_prop_set(svnlook, repopath, propname, line)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("%s/\n", line);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (!showallpaths) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rv = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; line[0] = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (0 == svnlook_is_prop_set(svnlook, repopath, propname, line)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("/\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (!showallpaths) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rv = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } // else it's a really long path and I'm refusing to check it!&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return rv;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; FILE *fpipe;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char *svnlook = "/usr/bin/svnlook";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char *repopath = "..";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char *revnum = NULL;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; char *propname = NULL;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int showallpaths = 0;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int index = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int help = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; while (index &amp;lt; argc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (strcmp("--svn-look", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (index &amp;lt; argc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; svnlook = argv[index];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (strcmp("-r", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (index &amp;lt; argc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; revnum = argv[index];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (strcmp("--repo", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (index &amp;lt; argc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; repopath = argv[index];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (strcmp("--property", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (index &amp;lt; argc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; propname = argv[index];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (strcmp("--show-all-paths", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; showallpaths = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (strcmp("--help", argv[index]) == 0 || strcmp("-h", argv[index]) == 0 || strcmp("-?", argv[index]) == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; help = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (help || svnlook == NULL || repopath == NULL || revnum == NULL || propname == NULL) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("Syntax: %s options\n", argv[0]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("Options:\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("&amp;nbsp;&amp;nbsp;&amp;nbsp; --svn-look PATH&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Specify an alternative svnlook binary location (default /usr/bin/svnlook)\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("&amp;nbsp;&amp;nbsp;&amp;nbsp; --repo PATH&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Specify the repository to work against (required)\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("&amp;nbsp;&amp;nbsp;&amp;nbsp; -r REVNUM&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Specify the revision to process (required)\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("&amp;nbsp;&amp;nbsp;&amp;nbsp; -property NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Specify the property to check (required)\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("&amp;nbsp;&amp;nbsp;&amp;nbsp; --show-all-paths&amp;nbsp;&amp;nbsp;&amp;nbsp; Shows all the changed paths with the property rather than just the first (optional)\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return 2;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int rv = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; fpipe = svnlook_dirs_changed(svnlook, repopath, revnum);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (fpipe) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rv = check_svn_dirs(fpipe, svnlook, repopath, revnum, propname, showallpaths);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pclose(fpipe);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return rv;&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;Now I should warn you that it's a wee while since I wrote C/C++, so apologies if the above is not perfect... it works for me.&lt;br /&gt;&lt;br /&gt;Next I use a post-commit.sh script to check for my property:&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: courier new,monospace; font-size: x-small;margin-left: 40px;"&gt;&lt;pre&gt;#!/bin/bash&lt;br /&gt;#&lt;br /&gt;# Licensed to the Apache Software Foundation (ASF) under one&lt;br /&gt;# or more contributor license agreements.  See the NOTICE file&lt;br /&gt;# distributed with this work for additional information&lt;br /&gt;# regarding copyright ownership.  The ASF licenses this file&lt;br /&gt;# to you under the Apache License, Version 2.0 (the&lt;br /&gt;# "License"); you may not use this file except in compliance&lt;br /&gt;# with the License.  You may obtain a copy of the License at&lt;br /&gt;#&lt;br /&gt;#   &lt;a bitly="BITLY_PROCESSED" href="http://www.apache.org/licenses/LICENSE-2.0"&gt;http://www.apache.org/licenses/LICENSE-2.0&lt;/a&gt;&lt;br /&gt;#&lt;br /&gt;# Unless required by applicable law or agreed to in writing,&lt;br /&gt;# software distributed under the License is distributed on an&lt;br /&gt;# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY&lt;br /&gt;# KIND, either express or implied.  See the License for the&lt;br /&gt;# specific language governing permissions and limitations&lt;br /&gt;# under the License.&lt;br /&gt;#&lt;br /&gt;&lt;br /&gt;path=$(/usr/bin/svnlook props --repo "$1" -r $2 --property reviewboard:autoreview)&lt;br /&gt;if [ $? == 1 ]; then&lt;br /&gt;&amp;nbsp; if [ $(svnlook changed "$1" -r $2 | sed -e "/^_.*/d;" | wc -l) -eq 0 ] ; then &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # Commit does not contain changed files, only changed properties&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; exit 0&lt;br /&gt;&amp;nbsp; fi&lt;br /&gt;&amp;nbsp; people=$(for p in $(/usr/bin/svnlook props --repo "$1" -r $2 --property reviewboard:autoreview --show-all-paths); \&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; do /usr/bin/svnlook pg "$1" reviewboard:autoreview "$p"; echo ""; done \&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | sed -e 's/[, \t]/\n/g;' | sort -u | tr '\n' ',' | sed -e "s/,$//;")&lt;br /&gt;&amp;nbsp; post-review --server=&lt;i style="color: red; font-family: arial,helvetica,sans-serif;"&gt;...&lt;/i&gt; --username=&lt;i style="color: red; font-family: arial,helvetica,sans-serif;"&gt;...&lt;/i&gt; --password=&lt;i style="color: red; font-family: arial,helvetica,sans-serif;"&gt;...&lt;/i&gt; --submit-as=$(/usr/bin/svnlook author "$1" "$2") \&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; --repository-url=&lt;i style="color: red; font-family: arial,helvetica,sans-serif;"&gt;...&lt;/i&gt; --revision-range=$(($2-1)):$2 "--description=$(/usr/bin/svnlook log "$1" -r "$2")" \&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "--summary=Commit r$2" "--target-people=$people" --publish&lt;br /&gt;fi&lt;br /&gt;exit 0&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;And now, as if by magic, all I need to do is set the property reviewboard:autoreview to the list of reviewboard usernames to be reviewers and then any time there is a commit on the folder with the property set, a review is automagically scheduled.&lt;br /&gt;&lt;br /&gt;Lovely...&lt;br /&gt;&lt;br /&gt;Now all I need to do is tone down how often reviewboard sends emails!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-7534786279840886346?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/7534786279840886346/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=7534786279840886346' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7534786279840886346'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7534786279840886346'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2010/02/review-board-and-subversion-hooks.html' title='Review Board and Subversion Hooks'/><author><name>Stephen Connolly</name><uri>http://www.blogger.com/profile/00818066207956539255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/-lLREKFiI3Ms/Tcp6lCOEYhI/AAAAAAAAABI/Wa4jMaiMgww/s220/me-120x120-cropped.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-8454145409424106744</id><published>2008-11-28T09:51:00.007Z</published><updated>2010-03-24T12:26:29.282Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Subversion'/><title type='text'>HowTo: CentOS 5, Apache 2.2, Subversion 1.5 with ActiveDirectory Authentication</title><content type='html'>Here's my step by step:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Install CentOS 5.2&lt;/li&gt;&lt;li&gt;Configure Network and Proxies as needed&lt;br /&gt;I usually create a login script: /etc/profile.d/login.sh as follows:&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;&lt;pre&gt;function set_proxies() {&lt;br /&gt; local s&lt;br /&gt; PROXY_ADDR="http://proxy.example.com:8000/"&lt;br /&gt; for s in HTTP HTTPS FTP GOPHER NEWSPOST NEWSREPLY\&lt;br /&gt;          NEWS NNTP SNEWSPOST SNEWSREPLY SNEWS\&lt;br /&gt;          WAIS FINGER CSO; do&lt;br /&gt;     export ${s}_PROXY=${PROXY_ADDR}&lt;br /&gt; done&lt;br /&gt;&lt;span style="font-size:85%;"&gt;    for s in http https ftp; do&lt;br /&gt;     export ${s}_proxy=${PROXY_ADDR}&lt;br /&gt; done&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:85%;"&gt;}&lt;br /&gt;&lt;br /&gt;set_proxies&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;li&gt;I installed from the DVD, so &lt;span style=";font-family:courier new;font-size:85%;"  &gt;yum update&lt;/span&gt; to ensure everything is up-to-date.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Get my mod_authn_sasl rpm:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;wget http://www.one-dash.com/blog/mod_authn_sasl-1.0.2-3.i386.rpm&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Install it (this should pull down the):&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;yum --nogpgcheck localinstall mod_authn_sasl-1.0.2-3.i386.rpm&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Get my sasl-magic-config script:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;wget http://www.one-dash.com/blog/system-saslauthd-active-directory-config.sh&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;dos2unix system-saslauthd-active-directory-config.sh&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Run the script:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;sh system-saslauthd-active-directory-config.sh example.com&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Change the security level:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;system-config-securitylevel-tui&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Set SELinux to Permissive, Customize and enable WWW and Secure WWW on the firewall&lt;/li&gt;&lt;li&gt;Create the sasl2 configuration for apache:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;echo "pwcheck_method:saslauthd" &gt; /usr/lib/sasl2/apache-httpd.conf&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Now we need to install Subversion 1.5.2:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;wget http://summersoft.fay.ar.us/pub/subversion/1.5.2/rhel-5/i386/subversion-1.5.2-1.i386.rpm&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;wget http://summersoft.fay.ar.us/pub/subversion/1.5.2/rhel-5/i386/neon-0.27.2-1.i386.rpm&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;wget http://summersoft.fay.ar.us/pub/subversion/1.5.2/rhel-5/i386/mod_dav_svn-1.5.2-1.i386.rpm&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;yum install perl-URI&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;rpm -i neon-0.27.2-1.i386.rpm &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;rpm -i subversion-1.5.2-1.i386.rpm&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;rpm -i mod_dav_svn-1.5.2-1.i386.rpm&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Next modify the apache conf file for our subversion repositories: /etc/httpd/conf.d/subversion.conf&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;# Needed to do Subversion Apache server.&lt;br /&gt;LoadModule dav_svn_module     modules/mod_dav_svn.so&lt;br /&gt;&lt;br /&gt;# Only needed if you decide to do "per-directory" access control.&lt;br /&gt;#LoadModule authz_svn_module   modules/mod_authz_svn.so&lt;br /&gt;&lt;br /&gt;&amp;lt;Location /svn&amp;gt;&lt;br /&gt; DAV svn&lt;br /&gt; SVNParentPath /var/www/svn&lt;br /&gt;&lt;br /&gt; &amp;lt;IfModule mod_authn_sasl.c&amp;gt;&lt;br /&gt;&lt;br /&gt;    # Limit write permission to list of valid users.&lt;br /&gt;    &amp;lt;LimitExcept GET PROPFIND OPTIONS REPORT&amp;gt;&lt;br /&gt;       # Require SSL connection for password protection.&lt;br /&gt;       # SSLRequireSSL&lt;br /&gt;       AuthType Basic&lt;br /&gt;       AuthName "EXAMPLE"&lt;br /&gt;       AuthBasicProvider sasl&lt;br /&gt;       AuthSaslPwcheckMethod saslauthd auxprop&lt;br /&gt;       AuthSaslAppname apache-httpd&lt;br /&gt;       AuthSaslRealm example&lt;br /&gt;       Require valid-user&lt;br /&gt;    &amp;lt;/LimitExcept&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;/IfModule&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;&lt;span style="font-family:Georgia,serif;"&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:Georgia,serif;"&gt;Next, create the subversion projects root and restart Apache:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;mkdir /var/www/svn&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;chown -R apache.apache /var/www/svn&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;service httpd restart&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:Georgia,serif;"&gt;At this point you can now create a test repository:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;svnadmin create /var/www/svn/test&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;chown -R apache.apache /var/www/svn/test&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:Georgia,serif;"&gt;Let's test if subversion is working:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;svn info http://localhost/svn/test&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;svn mkdir --username myWindowsUsername --message "a test commit" http://localhost/svn/test/trunk&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:Georgia,serif;"&gt;At this point we should have subversion up and running.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-8454145409424106744?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/8454145409424106744/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=8454145409424106744' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8454145409424106744'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8454145409424106744'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/11/howto-centos-5-apache-22-subversion-15.html' title='HowTo: CentOS 5, Apache 2.2, Subversion 1.5 with ActiveDirectory Authentication'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6294895647598693858</id><published>2008-11-27T14:02:00.008Z</published><updated>2010-03-24T12:26:29.283Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Subversion'/><title type='text'>Apache 2.2 Authentication with Active Directory via Cyrus SASL</title><content type='html'>Here is my version of the holy grail, i.e.&lt;br /&gt;&lt;blockquote&gt;Authentication for Apache HTTPD against Active Directory.&lt;/blockquote&gt;This is not the only way to skin this cat, for example you could also use &lt;a href="http://www.jejik.com/articles/2007/06/apache_and_subversion_authentication_with_microsoft_active_directory/"&gt;Sander Marechal's technique&lt;/a&gt; (which uses &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html"&gt;mod_authnz_ldap&lt;/a&gt;). However, my problem with Sander's technique is that you need to have an account in Active Directory which you will use to bind to the LDAP server.  That means that the accound password has to be stored in a plain-text file on the Apache server, and if the password expires everything breaks until you go fix the password.&lt;br /&gt;&lt;br /&gt;I want as near to zero maintenance as possible, running on CentOS 5.2 with minimal custom work - so that I don't have to maintain it when it does break.&lt;br /&gt;&lt;br /&gt;I have previously configured Kerberos to authenicate against Active Directory, so my first attempt was with &lt;a href="http://modauthkerb.sourceforge.net/"&gt;mod_auth_kerb&lt;/a&gt;.  That worked... but very slowly... trying to an empty access Subversion repository took over 2 minutes.  The problem being that successful authentication was not being cached.&lt;br /&gt;&lt;br /&gt;Then I tried using &lt;a href="http://perl.apache.org/"&gt;mod_perl&lt;/a&gt; and the &lt;a href="http://search.cpan.org/%7Echansen/Authen-Simple-LDAP-0.2/lib/Authen/Simple/ActiveDirectory.pm"&gt;Authen::Simple::ActiveDirectory&lt;/a&gt; reasoning that I could always hack caching once in perl... but getting that lot installed on a CentOS 5.2 from RPMs was an exercise in tears.&lt;br /&gt;&lt;br /&gt;So, anyway, I found out that &lt;a href="http://asg.web.cmu.edu/sasl/sasl-library.html"&gt;Cyrus SASL&lt;/a&gt; supports two modes of LDAP authentication:&lt;br /&gt;&lt;quote&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;The bind method uses the LDAP bind facility to verify the password.  The bind method is not available when ldap_use_sasl is turned on.  In that case saslauthd will use fastbind.&lt;br /&gt;&lt;br /&gt;'bind' is the default auth method. When ldap_use_sasl is enabled, 'fastbind' is the default.&lt;br /&gt;&lt;br /&gt;The custom method uses userPassword attribute to verify the password. Suppored hashes: crypt, md5, smd5, sha and ssha.  Cleartext is supported as well.&lt;br /&gt;&lt;br /&gt;The fastbind method (when 'ldap_use_sasl: no') does away with the search and an extra anonymous bind in auth_bind, but makes two assumptions:&lt;br /&gt;  1. Expanding the ldap_filter expression gives the user's&lt;br /&gt; fully-qualified DN&lt;br /&gt;  2. There is no cost to staying bound as a named user&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;/quote&gt;So bind is pretty much the same technique as that used by &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html"&gt;mod_authnz_ldap&lt;/a&gt;, and (as ActiveDirectory does not support - at my company at least - anonymous bind) is ruled out of the running.&lt;br /&gt;&lt;br /&gt;"fastbind" sounds exactly like what I want... ok and saslauthd can be configured to cache authentication, so none of the performance hit of &lt;a href="http://modauthkerb.sourceforge.net/"&gt;mod_auth_kerb&lt;/a&gt;.  All we need is a way to tie this into Apache.&lt;br /&gt;&lt;br /&gt;So, enter &lt;a href="http://mod-authn-sasl.sourceforge.net/"&gt;mod_authn_sasl&lt;/a&gt; which does just that.&lt;br /&gt;&lt;br /&gt;First, we need to create an RPM, so I did a quick&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;sudo yum install httpd-devel rpmbuild mock&lt;br /&gt;sudo&lt;br /&gt;echo "%_topdir %(echo $HOME)/rpmbuild" &gt; ~/.rpmmacros&lt;br /&gt;mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;I added my account to the &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;mock&lt;/span&gt;&lt;/span&gt; group, logged out and in again for the group membership to be recognised and I was ready to go.  This was my first experience playing with &lt;a href="http://fedoraproject.org/wiki/Projects/Mock"&gt;mock&lt;/a&gt;, but it was lovely, especially as CentOS has its own version.&lt;br /&gt;&lt;br /&gt;I pre-primed my mock environments for i386 and x86_64 as I need RPMs for both of these:&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;mock -r centos-5-i386 init&lt;br /&gt;mock -r centos-5-x86_64 init&lt;/span&gt;&lt;/blockquote&gt;Then I downloaded &lt;a href="http://superb-east.dl.sourceforge.net/sourceforge/mod-authn-sasl/mod_authn_sasl-1.0.2.tar.bz2"&gt;mod_authn_sasl-1.0.2.tar.bz2&lt;/a&gt; and started writing my spec file.&lt;br /&gt;&lt;br /&gt;You can download the RPC spec: &lt;a href="http://www.one-dash.com/blog/mod_authn_sasl.spec"&gt;mod_authn_sasl.spec&lt;/a&gt; and the apache config file: &lt;a href="http://www.one-dash.com/blog/mod_authn_sasl.conf"&gt;mod_authn_sasl.conf&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I put &lt;a href="http://superb-east.dl.sourceforge.net/sourceforge/mod-authn-sasl/mod_authn_sasl-1.0.2.tar.bz2"&gt;mod_authn_sasl-1.0.2.tar.bz2&lt;/a&gt; and &lt;a href="http://www.one-dash.com/blog/mod_authn_sasl.conf"&gt;mod_authn_sasl.conf&lt;/a&gt; in the &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;~/rpmbuild/SOURCES &lt;/span&gt;&lt;/span&gt;and then running&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;rpmbuild -ba mod_authn_sasl.spec&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;produced &lt;a href="http://www.one-dash.com/blog/mod_authn_sasl-1.0.2-4.src.rpm"&gt;my source RPM&lt;/a&gt;, then I used mock to create the two binary RPMs that I needed:&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;mock -r centos-5-i386 ~/rpmbuild/SRPMS/mod_authn_sasl-1.0.2-4.src.rpm&lt;br /&gt;mock -r centos-5-x86_64 ~/rpmbuild/SRPMS/mod_authn_sasl-1.0.2-4.src.rpm&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;The rpms built from the mock environments end up in &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;/var/lib/mock/centos-5-i386/result &lt;/span&gt;&lt;/span&gt; and &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;/var/lib/mock/centos-5-x86_64/result&lt;/span&gt;&lt;/span&gt;. If you want to be lazy and trust my binaries here they are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.one-dash.com/blog/mod_authn_sasl-1.0.2-4.i386.rpm"&gt;mod_authn_sasl-1.0.2-4.i386.rpm&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.one-dash.com/blog/mod_authn_sasl-1.0.2-4.x86_64.rpm"&gt;mod_authn_sasl-1.0.2-4.x86_64.rpm&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;Now all we need to do is configure everything.&lt;br /&gt;&lt;br /&gt;I've written &lt;a href="http://www.one-dash.com/blog/system-saslauthd-active-directory-config.sh"&gt;this shell script&lt;/a&gt; to simplify configuring saslauthd for most good deployments of Active Directory, i.e. where there are SRV records for the domain in your DNS server.  You need to run it as root.  It looks up the SRV records for the domain name you provide, and creates &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;/etc/saslauthd.conf&lt;/span&gt;&lt;/span&gt; from them, assuming that the Active Directory domain name is the first word of your DNS name converted to uppercase.  So for example, if your Active Directory is set up correctly, and you can login to windows as either &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;EXAMPLE\joebloggs&lt;/span&gt;&lt;/span&gt; or &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;jbloggs@example.foo.com&lt;/span&gt;&lt;/span&gt; then you would run&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;sh system-saslauthd-active-directory-config.sh example.foo.com&lt;/span&gt;&lt;/blockquote&gt;And that &lt;span style="font-style: italic;"&gt;should&lt;/span&gt; setup saslauthd for you.  You can test this using the &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;testsaslauthd &lt;/span&gt;&lt;/span&gt;program.&lt;br /&gt;&lt;br /&gt;Then all you need to do is secure the appropriate locations in apache, e.g. by adding the following to your VirtualHost configuration:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;location&gt;&lt;location&gt;&amp;lt;Location /private&amp;gt;&lt;br /&gt;AuthSaslPwcheckMethod saslauthd&lt;br /&gt;AuthSaslAppname httpd&lt;br /&gt;AuthSaslRealm example&lt;br /&gt;AuthType basic&lt;br /&gt;AuthBasicProvider sasl&lt;br /&gt;AuthBasicAuthoritative On&lt;br /&gt;AuthName "sasl@example.com"&lt;br /&gt;require valid-user&lt;br /&gt;&lt;/location&gt;&amp;lt;/Location&amp;gt;&lt;br /&gt;&lt;/location&gt;&lt;/pre&gt;&lt;/blockquote&gt;And define the SASL application provider for the &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;AuthSaslAppname&lt;/span&gt;&lt;/span&gt; you specified, e.g. for the above configuration you need to create &lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;/usr/lib/sasl2/httpd.conf&lt;/span&gt;&lt;/span&gt; with the contents:&lt;br /&gt;&lt;blockquote  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;pwcheck_method:saslauthd&lt;/span&gt;&lt;/blockquote&gt;That's it.  You should be done.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Updated Friday 28th November 2008:&lt;/span&gt; I noticed that the spec file I had provided did not have the correct Requires and BuildRequires. I have fixed this and now I have posted updated rpms (these are 1.0.2-4) with the correct requires to help with a minimal install&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6294895647598693858?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6294895647598693858/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6294895647598693858' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6294895647598693858'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6294895647598693858'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/11/apache-22-authentication-with-active.html' title='Apache 2.2 Authentication with Active Directory via Cyrus SASL'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6116720682127819803</id><published>2008-10-21T08:39:00.002+01:00</published><updated>2010-03-24T12:28:20.441Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><category scheme='http://www.blogger.com/atom/ns#' term='Acer Aspire One'/><title type='text'>Acer Aspire One, Huawei E169G and Three IR</title><content type='html'>One of my co-workers has asked me to post this up.  It's rough and ready, so make of it what you want.&lt;br /&gt;&lt;br /&gt;First off, Acer recently pushed an update for better performance with the Huawei USB modems... I'm assuming that you have this update... check if the file /etc/udev/rules.d/10-Huawei-Datacard.rules &lt;br /&gt; exists.&lt;br /&gt;&lt;br /&gt;If that file exists then when you plug in a E169g it will be correctly autodetected without requiring poking about with usbmodeswitch... it will bind the three serial ports of the E169G to /dev/HuaweiMobile-0, /dev/HuaweiMobile-1 and /dev/HuaweiMobile-2.&lt;br /&gt;&lt;br /&gt;OK, so assuming you see these device nodes after plugging in the E169G, the next problem is getting wvdial to connect.  Here's the wvdial.conf file I use&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[Dialer Defaults]&lt;br /&gt;Init2 = ATZ&lt;br /&gt;Init3 = ATH&lt;br /&gt;Init4 = ATQ0 V1 E1 S0=0 &amp;C1 &amp;D2 +FCLASS=0&lt;br /&gt;Stupid Mode = 1&lt;br /&gt;Modem Type = USB Modem&lt;br /&gt;ISDN = 0&lt;br /&gt;Phone = *99\#&lt;br /&gt;Modem = /dev/HuaweiMobile-0&lt;br /&gt;Username = username&lt;br /&gt;Password = password&lt;br /&gt;Dial Command = ATDT&lt;br /&gt;Baud = 460800&lt;br /&gt;Init5 = AT+CGDCONT=1,"IP","3ireland.ie"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By the way, the username is actually "username", and the password is actually "password".&lt;br /&gt;&lt;br /&gt;That should be enough to get any self-respecting linux freak 90% of the way there.  There was some stuff I had to tweak to get DHCP to populate resolve.conf from the pppd connection... and I added the following udev rule as 70-huawei-e169g-dial.rules&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SUBSYSTEM=="usb" SYSFS{idProduct}=="1001",SYSFS{idVendor}=="12d1",RUN+="/usr/sbin/e169g_dial"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And, /usr/sbin/e169g_dial is just&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;sleep 5&lt;br /&gt;/usr/bin/wvdial 2&gt;&amp;1 &gt; /var/log/wvdial &amp;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You might be able to tune down from 5 seconds if you can be bothered... but you need at least some delay&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6116720682127819803?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6116720682127819803/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6116720682127819803' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6116720682127819803'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6116720682127819803'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/10/acer-aspire-one-huawei-e169g-and-three.html' title='Acer Aspire One, Huawei E169G and Three IR'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-3353990012973679401</id><published>2008-06-21T10:25:00.010+01:00</published><updated>2011-05-30T14:56:15.201+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plugin (Part 7 - Putting it all together)</title><content type='html'>&lt;p&gt;Life gets in the way... but we're back with our final installment! So where to start, let's start with a publisher for freestyle builds, then we'll add a publisher for Maven 2 builds... These will both require some reports to display results, and then finally we'll need the plugin entry point.  But before we get into all that, perhaps I should briefly explain structured form submission support&lt;/p&gt;&lt;h2&gt;DataBoundConstructors&lt;/h2&gt;&lt;p&gt;Hudson uses &lt;a href="https://stapler.dev.java.net"&gt;Stapler&lt;/a&gt; as it's web framework. One of the things that Stapler provides is support for constructing objects from a JSON data model.  Basically, if you have a class with a public constructor annotated with &lt;code&gt;@DataBoundConstructor&lt;/code&gt;, Stapler will bind fields from a JSON object by matching the field name to the constructor parameter name. If a parameter also has a &lt;code&gt;@DataBoundConstructor&lt;/code&gt;, then Stapler will recurse to construct this child object from the child JSON object.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; The only hole in this (at the moment) is if you want to inject a variable class, i.e. it does not support the case where there are three &lt;code&gt;ChildImpl&lt;/code&gt; classes all implementing &lt;code&gt;Child&lt;/code&gt;, and all with &lt;code&gt;@DataBoundConstructor&lt;/code&gt; and &lt;code&gt;Parent&lt;/code&gt;'s constructor has a parameter which takes &lt;code&gt;Child&lt;/code&gt;... However, plans are afoot to fix this!&lt;/p&gt;&lt;h2&gt;JavaNCSSPublisher&lt;/h2&gt;&lt;p&gt;Publishers in Hudson must have a &lt;code&gt;Descriptor&lt;/code&gt;, this will be registered with Hudson and allows Hudson to create Publisher instances which have the details for the project they are publishing. Descriptors are normally implemented as an inner class called &lt;code&gt;DescriptorImpl&lt;/code&gt; and there is normally a static field of the publisher &lt;code&gt;DESCRIPTOR&lt;/code&gt; that holds the &lt;code&gt;Descriptor&lt;/code&gt; singleton. 99.995% of the time, you will want your publisher to have a &lt;code&gt;@DataBoundConstructor&lt;/code&gt;, so without further delay, here is the publisher:&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.maven.MavenModule;&lt;br /&gt;import hudson.maven.MavenModuleSet;&lt;br /&gt;import hudson.model.AbstractProject;&lt;br /&gt;import hudson.model.Action; &lt;br /&gt;import hudson.model.Descriptor;&lt;br /&gt;import hudson.plugins.helpers.AbstractPublisherImpl;&lt;br /&gt;import hudson.plugins.helpers.Ghostwriter;&lt;br /&gt;import hudson.tasks.BuildStepDescriptor;&lt;br /&gt;import hudson.tasks.Publisher;&lt;br /&gt;import net.sf.json.JSONObject;&lt;br /&gt;import org.kohsuke.stapler.DataBoundConstructor;&lt;br /&gt;import org.kohsuke.stapler.StaplerRequest;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSPublisher extends AbstractPublisherImpl {&lt;br /&gt;&lt;br /&gt;    private String reportFilenamePattern;&lt;br /&gt;&lt;br /&gt;    @DataBoundConstructor&lt;br /&gt;    public JavaNCSSPublisher(String reportFilenamePattern) {&lt;br /&gt;        reportFilenamePattern.getClass();&lt;br /&gt;        this.reportFilenamePattern = reportFilenamePattern;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getReportFilenamePattern() {&lt;br /&gt;        return reportFilenamePattern;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean needsToRunAfterFinalized() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();&lt;br /&gt;&lt;br /&gt;    public Descriptor&amp;lt;Publisher&amp;gt; getDescriptor() {&lt;br /&gt;        return DESCRIPTOR;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Action getProjectAction(AbstractProject&amp;lt;?, ?&amp;gt; project) {&lt;br /&gt;        return new JavaNCSSProjectIndividualReport(project);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected Ghostwriter newGhostwriter() {&lt;br /&gt;        return new JavaNCSSGhostwriter(reportFilenamePattern);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static final class DescriptorImpl extends BuildStepDescriptor&amp;lt;Publisher&amp;gt; {&lt;br /&gt;&lt;br /&gt;        private DescriptorImpl() {&lt;br /&gt;            super(JavaNCSSPublisher.class);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public String getDisplayName() {&lt;br /&gt;            return "Publish " + PluginImpl.DISPLAY_NAME;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public boolean isApplicable(Class&amp;lt;? extends AbstractProject&amp;gt; aClass) {&lt;br /&gt;            return !MavenModuleSet.class.isAssignableFrom(aClass)&lt;br /&gt;                    &amp;amp;&amp;amp; !MavenModule.class.isAssignableFrom(aClass);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;By inheriting from &lt;code&gt;AbstractPublisherImpl&lt;/code&gt; we get a lot of the work done for us, all we really need to do is provide a &lt;code&gt;Ghostwriter&lt;/code&gt; and the project level report (&lt;code&gt;JavaNCSSProjectIndividualReport&lt;/code&gt; which we will see later&lt;/p&gt;&lt;p&gt;We need &lt;code&gt;hudson/plugins/javancss/JavaNCSSPublisher/config.jelly&lt;/code&gt; to allow the user to specify the report file name pattern... not much to this, so here it is:&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health"&amp;gt;&lt;br /&gt;    &amp;lt;f:entry title="JavaNCSS xml report pattern"&lt;br /&gt;             description="&lt;br /&gt;             This is a file name pattern that can be used to locate the JavaNCSS xml report files&lt;br /&gt;             (for example with Maven2 use &amp;amp;lt;b&amp;amp;gt;**/target/javancss-raw-report.xml&amp;amp;lt;/b&amp;amp;gt;).&lt;br /&gt;           The path is relative to &amp;amp;lt;a href='ws/'&amp;amp;gt;the module root&amp;amp;lt;/a&amp;amp;gt; unless&lt;br /&gt;           you are using Subversion as SCM and have configured multiple modules, in which case it is&lt;br /&gt;           relative to the workspace root.&amp;amp;lt;br/&amp;amp;gt;&lt;br /&gt;           JavaNCSS must be configured to generate XML reports for this plugin to function.&lt;br /&gt;           "&amp;gt;&lt;br /&gt;        &amp;lt;f:textbox name="javancss.reportFilenamePattern" value="${instance.reportFilenamePattern}"/&amp;gt;&lt;br /&gt;    &amp;lt;/f:entry&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h2&gt;JavaNCSSMavenPublisher&lt;/h2&gt;&lt;p&gt;This is fairly similar to the freestyle publisher, except that we do not need the user to configure everything for us, as we can grab some of the stuff from the &lt;code&gt;pom.xml&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We could, if necessary, tweak the &lt;code&gt;pom.xml&lt;/code&gt; to ensure that the report we are looking for is generated... an example of this is the cobertura maven plugin which does not generate an XML report by default. A Hudson plugin can modify the cobertura plugin's configuration before it executes in order to ensure that the xml report is generated. &lt;b&gt;Note:&lt;/b&gt; some people regard this kind of thing as evil, as the pom.xml is no longer behaving the same as when run from the command line.&lt;/p&gt;&lt;p&gt;Ok, so here is the Maven publisher...&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.maven.*;&lt;br /&gt;import hudson.model.Action;&lt;br /&gt;import hudson.plugins.helpers.AbstractMavenReporterImpl;&lt;br /&gt;import hudson.plugins.helpers.Ghostwriter;&lt;br /&gt;import net.sf.json.JSONObject;&lt;br /&gt;import org.apache.maven.project.MavenProject;&lt;br /&gt;import org.codehaus.plexus.component.configurator.ComponentConfigurationException;&lt;br /&gt;import org.kohsuke.stapler.DataBoundConstructor;&lt;br /&gt;import org.kohsuke.stapler.StaplerRequest;&lt;br /&gt;&lt;br /&gt;import java.io.File;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSMavenPublisher extends AbstractMavenReporterImpl {&lt;br /&gt;&lt;br /&gt;    @DataBoundConstructor&lt;br /&gt;    public JavaNCSSMavenPublisher() {&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static final String PLUGIN_GROUP_ID = "org.codehaus.mojo";&lt;br /&gt;    private static final String PLUGIN_ARTIFACT_ID = "javancss-maven-plugin";&lt;br /&gt;    private static final String PLUGIN_EXECUTE_GOAL = "report";&lt;br /&gt;&lt;br /&gt;    protected boolean isExecutingMojo(MojoInfo mojo) {&lt;br /&gt;        return mojo.pluginName.matches(PLUGIN_GROUP_ID, PLUGIN_ARTIFACT_ID)&lt;br /&gt;                &amp;amp;&amp;amp; PLUGIN_EXECUTE_GOAL.equals(mojo.getGoal());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected Ghostwriter newGhostwriter(MavenProject pom, MojoInfo mojo) {&lt;br /&gt;        // get the name of the xml report &lt;br /&gt;&lt;br /&gt;        String tempFileName;&lt;br /&gt;        try {&lt;br /&gt;            tempFileName = mojo.getConfigurationValue("tempFileName", String.class);&lt;br /&gt;        } catch (ComponentConfigurationException e) {&lt;br /&gt;            tempFileName = null;&lt;br /&gt;        }&lt;br /&gt;        if (tempFileName == null) {&lt;br /&gt;            // the name was not overridden in the pom, so use the default&lt;br /&gt;            tempFileName = "javancss-raw-report.xml";&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // get the xml output directory&lt;br /&gt;&lt;br /&gt;        File baseDir = pom.getBasedir().getAbsoluteFile();&lt;br /&gt;        File xmlOutputDirectory;&lt;br /&gt;        try {&lt;br /&gt;            xmlOutputDirectory = mojo.getConfigurationValue("xmlOutputDirector", File.class);&lt;br /&gt;        } catch (ComponentConfigurationException e) {&lt;br /&gt;            xmlOutputDirectory = null;&lt;br /&gt;        }&lt;br /&gt;        if (xmlOutputDirectory == null) {&lt;br /&gt;            // the directory was not overridden in the pom, so use the default&lt;br /&gt;            xmlOutputDirectory = new File(pom.getBuild().getDirectory());&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        String searchPath;&lt;br /&gt;        String targetPath = makeDirEndWithFileSeparator(fixFilePathSeparator(xmlOutputDirectory.getAbsolutePath()));&lt;br /&gt;        String baseDirPath = makeDirEndWithFileSeparator(fixFilePathSeparator(baseDir.getAbsolutePath()));&lt;br /&gt;&lt;br /&gt;        if (targetPath.startsWith(baseDirPath)) {&lt;br /&gt;            searchPath = targetPath.substring(baseDirPath.length()) + tempFileName;&lt;br /&gt;        } else {&lt;br /&gt;            // we have no clue where this is, so default to anywhere&lt;br /&gt;            searchPath = "**/" + tempFileName;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return new JavaNCSSGhostwriter(searchPath, targets);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private String makeDirEndWithFileSeparator(String baseDirPath) {&lt;br /&gt;        if (!baseDirPath.endsWith(File.separator)) {&lt;br /&gt;            baseDirPath += File.separator;&lt;br /&gt;        }&lt;br /&gt;        return baseDirPath;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private String fixFilePathSeparator(String path) {&lt;br /&gt;        return path.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Action getProjectAction(MavenModule module) {&lt;br /&gt;        for (MavenBuild build : module.getBuilds()) {&lt;br /&gt;            if (build.getAction(JavaNCSSBuildIndividualReport.class) != null) {&lt;br /&gt;                return new JavaNCSSProjectIndividualReport(module);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * {@inheritDoc}&lt;br /&gt;     */&lt;br /&gt;    public MavenReporterDescriptor getDescriptor() {&lt;br /&gt;        return DESCRIPTOR;  //To change body of implemented methods use File | Settings | File Templates.&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static final class DescriptorImpl extends MavenReporterDescriptor {&lt;br /&gt;&lt;br /&gt;        /**&lt;br /&gt;         * Do not instantiate DescriptorImpl.&lt;br /&gt;         */&lt;br /&gt;        private DescriptorImpl() {&lt;br /&gt;            super(JavaNCSSMavenPublisher.class);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        /**&lt;br /&gt;         * {@inheritDoc}&lt;br /&gt;         */&lt;br /&gt;        public String getDisplayName() {&lt;br /&gt;            return "Publish " + PluginImpl.DISPLAY_NAME;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public MavenReporter newInstance(StaplerRequest req, JSONObject formData) throws FormException {&lt;br /&gt;            return req.bindJSON(JavaNCSSMavenPublisher.class, formData);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The only complexity is in the &lt;code&gt;newGhostwriter&lt;/code&gt; method, where we have to find out what the configuration for the maven plugin is in order to find the xml report.&lt;/p&gt;&lt;p&gt;We will need a &lt;code&gt;hudson/plugins/javancss/JavaNCSSMavenPublisher/config.jelly&lt;/code&gt; file for this publisher... not much to this one as we get what we need from the &lt;code&gt;pom.xml&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h2&gt;The reports&lt;/h2&gt;We have a total of four reports to generate:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Individual build report - used for freestyle and maven 2 modules&lt;/li&gt;&lt;li&gt;Individual project report - used for freestyle and maven 2 modules&lt;/li&gt;&lt;li&gt;Aggregated build report - used for maven 2 projects&lt;/li&gt;&lt;li&gt;Aggregated project report - used for maven 2 projects&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To keep to our DRY principals, we'll use some abstract classes to pull together the common code. First, &lt;code&gt;AbstractBuildReport&lt;/code&gt; which will form the basis of our build reports:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.plugins.helpers.AbstractBuildAction;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;import org.kohsuke.stapler.StaplerRequest;&lt;br /&gt;import org.kohsuke.stapler.StaplerResponse;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractBuildReport&amp;lt;T extends AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt; extends AbstractBuildAction&amp;lt;T&amp;gt; {&lt;br /&gt;    private final Collection&amp;lt;Statistic&amp;gt; results;&lt;br /&gt;    private final Statistic totals;&lt;br /&gt;&lt;br /&gt;    public AbstractBuildReport(Collection&amp;lt;Statistic&amp;gt; results) {&lt;br /&gt;        this.results = results;&lt;br /&gt;        this.totals = Statistic.total(results);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Collection&amp;lt;Statistic&amp;gt; getResults() {&lt;br /&gt;        return results;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Statistic getTotals() {&lt;br /&gt;        return totals;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getSummary() {&lt;br /&gt;        AbstractBuild&amp;lt;?, ?&amp;gt; prevBuild = getBuild().getPreviousBuild();&lt;br /&gt;        while (prevBuild != null &amp;amp;&amp;amp; prevBuild.getAction(getClass()) == null) {&lt;br /&gt;            prevBuild = prevBuild.getPreviousBuild();&lt;br /&gt;        }&lt;br /&gt;        if (prevBuild == null) {&lt;br /&gt;            return totals.toSummary();&lt;br /&gt;        } else {&lt;br /&gt;            AbstractBuildReport action = prevBuild.getAction(getClass());&lt;br /&gt;            return totals.toSummary(action.getTotals());&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getIconFileName() {&lt;br /&gt;        return PluginImpl.ICON_FILE_NAME;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getDisplayName() {&lt;br /&gt;        return PluginImpl.DISPLAY_NAME;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getUrlName() {&lt;br /&gt;        return PluginImpl.URL;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isGraphActive() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Similarly, we have &lt;code&gt;AbstractProjectReport&lt;/code&gt; which will be used for project reports:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.Collections;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.AbstractProject;&lt;br /&gt;import hudson.model.ProminentProjectAction;&lt;br /&gt;import hudson.plugins.helpers.AbstractProjectAction;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;import org.kohsuke.stapler.StaplerRequest;&lt;br /&gt;import org.kohsuke.stapler.StaplerResponse;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractProjectReport&amp;lt;T extends AbstractProject&amp;lt;?, ?&amp;gt;&amp;gt; &lt;br /&gt;        extends AbstractProjectAction&amp;lt;T&amp;gt;&lt;br /&gt;        implements ProminentProjectAction {&lt;br /&gt;&lt;br /&gt;    public AbstractProjectReport(T project) {&lt;br /&gt;        super(project);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getIconFileName() {&lt;br /&gt;        for (AbstractBuild&amp;lt;?, ?&amp;gt; build = getProject().getLastBuild(); &lt;br /&gt;                build != null; build = build.getPreviousBuild()) {&lt;br /&gt;&lt;br /&gt;            final AbstractBuildReport action = build.getAction(getBuildActionClass());&lt;br /&gt;            if (action != null) {&lt;br /&gt;                return PluginImpl.ICON_FILE_NAME;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getDisplayName() {&lt;br /&gt;        for (AbstractBuild&amp;lt;?, ?&amp;gt; build = getProject().getLastBuild(); &lt;br /&gt;                build != null; build = build.getPreviousBuild()) {&lt;br /&gt;            final AbstractBuildReport action = build.getAction(getBuildActionClass());&lt;br /&gt;            if (action != null) {&lt;br /&gt;                return PluginImpl.DISPLAY_NAME;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getUrlName() {&lt;br /&gt;        for (AbstractBuild&amp;lt;?, ?&amp;gt; build = getProject().getLastBuild(); build != null; build = build.getPreviousBuild()) {&lt;br /&gt;            final AbstractBuildReport action = build.getAction(getBuildActionClass());&lt;br /&gt;            if (action != null) {&lt;br /&gt;                return PluginImpl.URL;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getSearchUrl() {&lt;br /&gt;        return PluginImpl.URL;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isGraphActive() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Collection&amp;lt;Statistic&amp;gt; getResults() {&lt;br /&gt;        for (AbstractBuild&amp;lt;?, ?&amp;gt; build = getProject().getLastBuild(); &lt;br /&gt;                build != null; build = build.getPreviousBuild()) {&lt;br /&gt;            final AbstractBuildReport action = build.getAction(getBuildActionClass());&lt;br /&gt;            if (action != null) {&lt;br /&gt;                return action.getResults();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return Collections.emptySet();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Statistic getTotals() {&lt;br /&gt;        for (AbstractBuild&amp;lt;?, ?&amp;gt; build = getProject().getLastBuild(); &lt;br /&gt;                build != null; build = build.getPreviousBuild()) {&lt;br /&gt;            final AbstractBuildReport action = build.getAction(getBuildActionClass());&lt;br /&gt;            if (action != null) {&lt;br /&gt;                return action.getTotals();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected abstract Class&amp;lt;? extends AbstractBuildReport&amp;gt; getBuildActionClass();&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now that we have these abstract classes, we can roll out our concrete reports. First the individual build report:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;import hudson.maven.AggregatableAction;&lt;br /&gt;import hudson.maven.MavenAggregatedReport;&lt;br /&gt;import hudson.maven.MavenBuild;&lt;br /&gt;import hudson.maven.MavenModule;&lt;br /&gt;import hudson.maven.MavenModuleSetBuild;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSBuildIndividualReport extends AbstractBuildReport&amp;lt;AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&lt;br /&gt;        implements AggregatableAction {&lt;br /&gt;&lt;br /&gt;    public JavaNCSSBuildIndividualReport(Collection&amp;lt;Statistic&amp;gt; results) {&lt;br /&gt;        super(results);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public synchronized void setBuild(AbstractBuild&amp;lt;?, ?&amp;gt; build) {&lt;br /&gt;        super.setBuild(build);&lt;br /&gt;        if (this.getBuild() != null) {&lt;br /&gt;            for (Statistic r : getResults()) {&lt;br /&gt;                r.setOwner(this.getBuild());&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public MavenAggregatedReport createAggregatedAction(MavenModuleSetBuild build,&lt;br /&gt;            Map&amp;lt;MavenModule, &lt;br /&gt;            List&amp;lt;MavenBuild&amp;gt;&amp;gt; moduleBuilds) {&lt;br /&gt;        return new JavaNCSSBuildAggregatedReport(build, moduleBuilds);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;That was fairly painless... Note that we interfaces for both the freestyle and maven2 project types, this is OK as the freestyle projects will ignore the Maven2 stuff and vice-versa while the common code is shared by both.  Next we need the aggregated build report:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.maven.*;&lt;br /&gt;import hudson.model.Action;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSBuildAggregatedReport &lt;br /&gt;        extends AbstractBuildReport&amp;lt;MavenModuleSetBuild&amp;gt; &lt;br /&gt;        implements MavenAggregatedReport {&lt;br /&gt;&lt;br /&gt;    public JavaNCSSBuildAggregatedReport(MavenModuleSetBuild build, &lt;br /&gt;            Map&amp;lt;MavenModule, List&amp;lt;MavenBuild&amp;gt;&amp;gt; moduleBuilds) {&lt;br /&gt;        super(new ArrayList&amp;lt;Statistic&amp;gt;());&lt;br /&gt;        setBuild(build);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public synchronized void update(Map&amp;lt;MavenModule, List&amp;lt;MavenBuild&amp;gt;&amp;gt; moduleBuilds, &lt;br /&gt;            MavenBuild newBuild) {&lt;br /&gt;        JavaNCSSBuildIndividualReport report = &lt;br /&gt;                newBuild.getAction(JavaNCSSBuildIndividualReport.class);&lt;br /&gt;&lt;br /&gt;        if (report != null) {&lt;br /&gt;            Collection&amp;lt;Statistic&amp;gt; u = Statistic.merge(report.getResults(), getResults());&lt;br /&gt;            getResults().clear();&lt;br /&gt;            getResults().addAll(u);&lt;br /&gt;            getTotals().add(report.getTotals());&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Class&amp;lt;? extends AggregatableAction&amp;gt; getIndividualActionType() {&lt;br /&gt;        return JavaNCSSBuildIndividualReport.class;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Action getProjectAction(MavenModuleSet moduleSet) {&lt;br /&gt;        for (MavenModuleSetBuild build : moduleSet.getBuilds()) {&lt;br /&gt;            if (build.getAction(JavaNCSSBuildAggregatedReport.class) != null) {&lt;br /&gt;                return new JavaNCSSProjectAggregatedReport(moduleSet);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This report is only used for the Maven2 project types. The two key methods are &lt;code&gt;update&lt;/code&gt; which is called as each module completes, and &lt;code&gt;getProjectAction&lt;/code&gt; which should return the project level aggregated report &lt;i&gt;if there is a report to show&lt;/i&gt;.  At this point we're ready for the individual project report:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractProject;&lt;br /&gt;import hudson.model.Actionable;&lt;br /&gt;import hudson.model.ProminentProjectAction;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.util.ChartUtil;&lt;br /&gt;import hudson.util.DataSetBuilder;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSProjectIndividualReport &lt;br /&gt;        extends AbstractProjectReport&amp;lt;AbstractProject&amp;lt;?, ?&amp;gt;&amp;gt; &lt;br /&gt;        implements ProminentProjectAction {&lt;br /&gt;&lt;br /&gt;    public JavaNCSSProjectIndividualReport(AbstractProject&amp;lt;?, ?&amp;gt; project) {&lt;br /&gt;        super(project);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected Class&amp;lt;? extends AbstractBuildReport&amp;gt; getBuildActionClass() {&lt;br /&gt;        return JavaNCSSBuildIndividualReport.class;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Don't repeat ourselves comes in handy here as essentially all the work has been done for us!.  The project aggregated report:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.model.Actionable;&lt;br /&gt;import hudson.model.ProminentProjectAction;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.Action;&lt;br /&gt;import hudson.maven.MavenModuleSet;&lt;br /&gt;import hudson.maven.MavenModuleSetBuild;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSProjectAggregatedReport &lt;br /&gt;        extends AbstractProjectReport&amp;lt;MavenModuleSet&amp;gt; &lt;br /&gt;        implements ProminentProjectAction {&lt;br /&gt;&lt;br /&gt;    public JavaNCSSProjectAggregatedReport(MavenModuleSet project) {&lt;br /&gt;        super(project);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected Class&amp;lt;? extends AbstractBuildReport&amp;gt; getBuildActionClass() {&lt;br /&gt;        return JavaNCSSBuildAggregatedReport.class;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Again DRY to the rescue... At this point all that remains is to present the reports from these backing objects... so on with the jelly views. The helper classes and our inheritance makes this easy... all we need is two jelly files: &lt;code&gt;hudson/plugins/javancss/AbstractBuildReport/reportDetail.jelly&lt;/code&gt; and &lt;code&gt;hudson/plugins/javancss/AbstractProjectReport/reportDetail.jelly&lt;/code&gt;.  Here they are:&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;h1&amp;gt;Results&amp;lt;/h1&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;table border="1px" class="pane sortable"&amp;gt;&lt;br /&gt;        &amp;lt;thead&amp;gt;&lt;br /&gt;            &amp;lt;tr&amp;gt;&lt;br /&gt;                &amp;lt;th&amp;gt;Package&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Class count"&amp;gt;Classes&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Function count"&amp;gt;Functions&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Javadoc count"&amp;gt;Javadocs&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Non-commenting Source Statements"&amp;gt;NCSS&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Javadoc line count"&amp;gt;JLC&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Single-line comment line count"&amp;gt;SLCLC&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th title="Multi-line comment line count"&amp;gt;MLCLC&amp;lt;/th&amp;gt;&lt;br /&gt;            &amp;lt;/tr&amp;gt;&lt;br /&gt;        &amp;lt;/thead&amp;gt;&lt;br /&gt;        &amp;lt;tfoot&amp;gt;&lt;br /&gt;            &amp;lt;tr&amp;gt;&lt;br /&gt;                &amp;lt;th align="left"&amp;gt;Totals&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.classes}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.functions}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.javadocs}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.ncss}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.javadocLines}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.singleCommentLines}&amp;lt;/th&amp;gt;&lt;br /&gt;                &amp;lt;th align="right"&amp;gt;${it.totals.multiCommentLines}&amp;lt;/th&amp;gt;&lt;br /&gt;            &amp;lt;/tr&amp;gt;&lt;br /&gt;        &amp;lt;/tfoot&amp;gt;&lt;br /&gt;        &amp;lt;tbody&amp;gt;&lt;br /&gt;            &amp;lt;j:forEach var="r" items="${it.results}"&amp;gt;&lt;br /&gt;                &amp;lt;tr&amp;gt;&lt;br /&gt;                    &amp;lt;th align="left"&amp;gt;${r.name}&amp;lt;/th&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.classes}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.functions}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.javadocs}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.ncss}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.javadocLines}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.singleCommentLines}&amp;lt;/td&amp;gt;&lt;br /&gt;                    &amp;lt;td align="right"&amp;gt;${r.multiCommentLines}&amp;lt;/td&amp;gt;&lt;br /&gt;                &amp;lt;/tr&amp;gt;&lt;br /&gt;            &amp;lt;/j:forEach&amp;gt;&lt;br /&gt;        &amp;lt;/tbody&amp;gt;&lt;br /&gt;    &amp;lt;/table&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Yep, the two files are identical! Other plugins may not be quite so lucky... but in general the project level report should be the same as the report for the latest build&lt;/p&gt;&lt;h2&gt;Making a plugin&lt;/h2&gt;&lt;p&gt;Now we are ready to make our plugin.... for this we need a class that extends &lt;code&gt;hudson.Plugin&lt;/code&gt; and registers our publisher's descriptors with the appropriate lists... here it is:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.Plugin;&lt;br /&gt;import hudson.maven.MavenReporters;&lt;br /&gt;import hudson.tasks.BuildStep;&lt;br /&gt;&lt;br /&gt;public class PluginImpl extends Plugin {&lt;br /&gt;    public void start() throws Exception {&lt;br /&gt;        BuildStep.PUBLISHERS.add(JavaNCSSPublisher.DESCRIPTOR);&lt;br /&gt;        MavenReporters.LIST.add(JavaNCSSMavenPublisher.DESCRIPTOR);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static String DISPLAY_NAME = "Java NCSS Report";&lt;br /&gt;    public static String GRAPH_NAME = "Java NCSS Trend";&lt;br /&gt;    public static String URL = "javancss";&lt;br /&gt;    public static String ICON_FILE_NAME = "graph.gif";&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And that's pretty much it... we should have a working plugin&lt;/p&gt;&lt;h2&gt;Finishing touches&lt;/h2&gt;&lt;p&gt;OK, so the plugin does not have health reports (i.e. the weather icons) and it does not show a trend graph... I think I'm going to need a part 8 :-(&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-3353990012973679401?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/3353990012973679401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=3353990012973679401' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3353990012973679401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3353990012973679401'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/06/writing-hudson-plugin-part-7-putting-it.html' title='Writing a Hudson plugin (Part 7 - Putting it all together)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1012524818897039620</id><published>2008-04-14T22:54:00.002+01:00</published><updated>2011-05-30T14:56:15.202+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>More praise for Hudson</title><content type='html'>&lt;p&gt;Over on the Maven2 Users list a recent poster asked what CI server was best, and Hudson was the only answer.&lt;/p&gt;&lt;p&gt;Then Jason van Zyl posted &lt;a href="http://www.nabble.com/continuous-integration-server-tt16662773s177.html"&gt;this praise for Hudson&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;I know from my vantage point Hudson is the only system I will provide commercial support for at Sonatype because the battle is over. Hudson won by making developers  lives' easier. Kohsuke will go to no end to make things easier for users. &lt;/p&gt;&lt;p&gt;...&lt;/p&gt;&lt;p&gt;At any rate I guarantee you that inside 3 months Hudson will have the best Maven integration of any CI/Build Server there is.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I'm personally biased towards Hudson, but I have to agree that it has won the war of the CI servers. I don't see anything coming close in ease of use or speed of startup.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1012524818897039620?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1012524818897039620/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1012524818897039620' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1012524818897039620'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1012524818897039620'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/04/more-praise-for-hudson.html' title='More praise for Hudson'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4883496773793834556</id><published>2008-04-14T12:13:00.006+01:00</published><updated>2011-05-30T14:56:15.202+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plugin (Part 6 - Parsing the results)</title><content type='html'>&lt;p&gt;In some ways parsing the JavaNCSS results is the least interesting part of developing a Hudson plugin, as once I have implemented the parser, it is available for everyone.  For that reason I will focus more on:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;best practice techniques for parsing results&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;common gotchas&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;designing for extension&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Getting started&lt;/h2&gt;&lt;p&gt;First off, we need to analyse the results file format. In the case of JavaNCSS there are multiple ways that the results file can be generated: from the JavaNCSS program directly, from ANT or from Maven. This leads us onto gotcha #1&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Gotcha #1:&lt;/i&gt;&lt;br/&gt;Never assume that a build tool generates the same format of output when run from the command line, ANT or Maven.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;A case in point for Gotcha #1 is &lt;a href="http://findbugs.sourceforge.net/"&gt;Findbugs&lt;/a&gt; which generates one XML format from the command line and ANT, and generates a different format that appears similar at first glance when run from Maven (&lt;a href= "http://www.nabble.com/Findbugs-maven-plugin-td12062101.html#a12069489"&gt;mail thread&lt;/a&gt;).  In this case it turns out that Maven 1 used the different format output, and it is feared that some people came to depend on this Maven 1 format, so when the plugin for Maven 2 was developed, they kept the Maven 1 format. In any case, the moral is don’t assume, check!&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So we use the sample projects from Part 1 and generate an ANT and a Maven 2 XML report. First off, here is the report from ANT:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;javancss&amp;gt;&lt;br /&gt;&amp;lt;date&amp;gt;2008-04-12&amp;lt;/date&amp;gt;&lt;br /&gt;&amp;lt;time&amp;gt;11:22:30&amp;lt;/time&amp;gt;&lt;br /&gt;&amp;lt;packages&amp;gt;&lt;br /&gt; &amp;lt;package&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;1&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;3&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;10&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;3&amp;lt;/javadocs&amp;gt;&lt;br /&gt;   &amp;lt;javadoc_lines&amp;gt;12&amp;lt;/javadoc_lines&amp;gt;&lt;br /&gt;   &amp;lt;single_comment_lines&amp;gt;0&amp;lt;/single_comment_lines&amp;gt;&lt;br /&gt;   &amp;lt;multi_comment_lines&amp;gt;0&amp;lt;/multi_comment_lines&amp;gt;&lt;br /&gt; &amp;lt;/package&amp;gt;&lt;br /&gt; &amp;lt;package&amp;gt;&lt;br /&gt;    ...&lt;br /&gt; &amp;lt;/package&amp;gt;&lt;br /&gt; ...&lt;br /&gt; &amp;lt;total&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;5&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;8&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;46&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;9&amp;lt;/javadocs&amp;gt;&lt;br /&gt;   &amp;lt;javadoc_lines&amp;gt;37&amp;lt;/javadoc_lines&amp;gt;&lt;br /&gt;   &amp;lt;single_comment_lines&amp;gt;0&amp;lt;/single_comment_lines&amp;gt;&lt;br /&gt;   &amp;lt;multi_comment_lines&amp;gt;0&amp;lt;/multi_comment_lines&amp;gt;&lt;br /&gt; &amp;lt;/total&amp;gt;&lt;br /&gt; &amp;lt;table&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Packages&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Classes&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Functions&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;NCSS&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Javadocs&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;per&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;4.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;5.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;8.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;46.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;9.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Project&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.25&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;2.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;11.50&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;2.25&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Package&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.60&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;9.20&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.80&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Class&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;5.75&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.13&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Function&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt; &amp;lt;/table&amp;gt;&lt;br /&gt;&amp;lt;/packages&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;objects&amp;gt;&lt;br /&gt; &amp;lt;object&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common.Factory&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;7&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;3&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;0&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;3&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/object&amp;gt;&lt;br /&gt; &amp;lt;object&amp;gt;&lt;br /&gt;   ...&lt;br /&gt; &amp;lt;/object&amp;gt;&lt;br /&gt; ...&lt;br /&gt; &amp;lt;averages&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;6.60&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;1.60&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;0.00&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;1.80&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/averages&amp;gt;&lt;br /&gt; &amp;lt;ncss&amp;gt;46.00&amp;lt;/ncss&amp;gt;&lt;br /&gt;&amp;lt;/objects&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;functions&amp;gt;&lt;br /&gt; &amp;lt;function&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common.Factory.Factory()&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;1&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;ccn&amp;gt;1&amp;lt;/ccn&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;1&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/function&amp;gt;&lt;br /&gt; &amp;lt;function&amp;gt;&lt;br /&gt;   ...&lt;br /&gt; &amp;lt;/function&amp;gt;&lt;br /&gt; ...&lt;br /&gt; &amp;lt;ncss&amp;gt;46.00&amp;lt;/ncss&amp;gt;&lt;br /&gt;&amp;lt;/functions&amp;gt;&lt;br /&gt;&amp;lt;/javancss&amp;gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;OK, first off, for those following the tutorial exactly, I have cheated a little. I added some more source files into the project to make sure that I have multiple classes is different packages.  You can see the source code I built from &lt;a href="https://hudson.dev.java.net/svn/hudson/trunk/hudson/plugins/javancss/work/jobs/simple-ant/workspace"&gt;here&lt;/a&gt;. Additionally, I have trimmed the output somewhat to highlight the interesting bits, removing the duplicate entries.&lt;/p&gt;&lt;p&gt;From this report file we can see a basic XML structure:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;The root element is &lt;code&gt;&amp;lt;javancss&amp;gt;&lt;/code&gt; and has child elements: &lt;code&gt;&amp;lt;date&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;packages&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;objects&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;date&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; elements are the timestamp when the report was generated with the date in &lt;code&gt;YYYY-MM-DD&lt;/code&gt; format and the time in &lt;code&gt;HH:MM:SS&lt;/code&gt; format&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;packages&amp;gt;&lt;/code&gt; element has child elements: &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;total&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;. There are multiple &lt;code&gt;&amp;lt;package&amp;gt&lt;/code&gt;; elements, but only one &lt;code&gt;&amp;lt;total&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; element.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt; elements have child elements: &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;classes&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;javadocs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;javadoc_lines&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;single_comment_lines&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;multi_comment_lines&amp;gt;&lt;/code&gt;. The &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; element contains the name of the package as a &lt;code&gt;String&lt;/code&gt; and the other elements contain totals as &lt;code&gt;Integer&lt;/code&gt;s.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;total&amp;gt;&lt;/code&gt; element has child elements: &lt;code&gt;&amp;lt;classes&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;javadocs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;javadoc_lines&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;single_comment_lines&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;multi_comment_lines&amp;gt;&lt;/code&gt;.  These elements are the sum of all the corresponding &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt; children inside the &lt;code&gt;&amp;lt;packages&amp;gt;&lt;/code&gt; parent&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; element seems to be a HTML table.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;objects&amp;gt;&lt;/code&gt; element has child elements: &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;averages&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt;. There are multiple &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; elements, the &lt;code&gt;&amp;lt;averages&amp;gt;&lt;/code&gt; element contains the average results for all the &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; elements and the &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt; element providing some form of total or average.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt; element has child elements: &lt;code&gt;&amp;lt;function&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt;. Again there are multiple &lt;code&gt;&amp;lt;function&amp;gt;&lt;/code&gt; elements with the &lt;code&gt;&amp;lt;ncss&amp;gt;&lt;/code&gt; element providing some form of total or average (interestingly the result appears to be the same as from &lt;code&gt;&amp;lt;objects&amp;gt;&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now, let’s take a look at what Maven 2 gives us:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;javancss&amp;gt;&lt;br /&gt;&amp;lt;date&amp;gt;2008-04-12&amp;lt;/date&amp;gt;&lt;br /&gt;&amp;lt;time&amp;gt;11:43:06&amp;lt;/time&amp;gt;&lt;br /&gt;&amp;lt;packages&amp;gt;&lt;br /&gt; &amp;lt;package&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;1&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;3&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;10&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;3&amp;lt;/javadocs&amp;gt;&lt;br /&gt;   &amp;lt;javadoc_lines&amp;gt;12&amp;lt;/javadoc_lines&amp;gt;&lt;br /&gt;   &amp;lt;single_comment_lines&amp;gt;0&amp;lt;/single_comment_lines&amp;gt;&lt;br /&gt;   &amp;lt;multi_comment_lines&amp;gt;0&amp;lt;/multi_comment_lines&amp;gt;&lt;br /&gt; &amp;lt;/package&amp;gt;&lt;br /&gt; &amp;lt;package&amp;gt;&lt;br /&gt;   ...&lt;br /&gt; &amp;lt;/package&amp;gt;&lt;br /&gt; ...&lt;br /&gt; &amp;lt;total&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;5&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;8&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;46&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;10&amp;lt;/javadocs&amp;gt;&lt;br /&gt;   &amp;lt;javadoc_lines&amp;gt;42&amp;lt;/javadoc_lines&amp;gt;&lt;br /&gt;   &amp;lt;single_comment_lines&amp;gt;3&amp;lt;/single_comment_lines&amp;gt;&lt;br /&gt;   &amp;lt;multi_comment_lines&amp;gt;3&amp;lt;/multi_comment_lines&amp;gt;&lt;br /&gt; &amp;lt;/total&amp;gt;&lt;br /&gt; &amp;lt;table&amp;gt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Packages&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Classes&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Functions&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;NCSS&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Java&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;4.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;5.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;8.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;46.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;10.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;P&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.25&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;2.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;11.50&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;2.50&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Packag&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.60&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;9.20&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;2.00&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Class&amp;lt;/td&amp;gt;&amp;lt;&lt;br /&gt;   &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;5.75&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;1.25&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Function&amp;lt;/td&amp;gt;&amp;lt;/&lt;br /&gt; &amp;lt;/table&amp;gt;&lt;br /&gt;&amp;lt;/packages&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;objects&amp;gt;&lt;br /&gt; &amp;lt;object&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common.api.Namer&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;2&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;1&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;0&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;1&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/object&amp;gt;&lt;br /&gt; &amp;lt;object&amp;gt;&lt;br /&gt;   ...&lt;br /&gt; &amp;lt;/object&amp;gt;&lt;br /&gt; ...&lt;br /&gt; &amp;lt;averages&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;6.60&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;functions&amp;gt;1.60&amp;lt;/functions&amp;gt;&lt;br /&gt;   &amp;lt;classes&amp;gt;0.00&amp;lt;/classes&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;2.00&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/averages&amp;gt;&lt;br /&gt; &amp;lt;ncss&amp;gt;46.00&amp;lt;/ncss&amp;gt;&lt;br /&gt;&amp;lt;/objects&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;functions&amp;gt;&lt;br /&gt; &amp;lt;function&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;com.onedash.common.api.Namer.newName()&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;ncss&amp;gt;1&amp;lt;/ncss&amp;gt;&lt;br /&gt;   &amp;lt;ccn&amp;gt;1&amp;lt;/ccn&amp;gt;&lt;br /&gt;   &amp;lt;javadocs&amp;gt;0&amp;lt;/javadocs&amp;gt;&lt;br /&gt; &amp;lt;/function&amp;gt;&lt;br /&gt; &amp;lt;function&amp;gt;&lt;br /&gt;   ...&lt;br /&gt; &amp;lt;/function&amp;gt;&lt;br /&gt; &amp;lt;ncss&amp;gt;46.00&amp;lt;/ncss&amp;gt;&lt;br /&gt;&amp;lt;/functions&amp;gt;&lt;br /&gt;&amp;lt;/javancss&amp;gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Thankfully, this is the same format as for ANT. You will also be relieved to know that this is the format generated by the JavaNCSS program directly. Thus we only have to write one parser, and we do not have to detect what format we are parsing. But before I forget:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Best Practice #1:&lt;/i&gt;&lt;br /&gt;When there are multiple formats of a report generated by different tools, make sure that your Hudson plugin can detect the different formats and can handle them appropriately (by either delegating to a different parser implementation, or by handling the differences on the fly).&lt;/b&gt;&lt;/p&gt;&lt;p&gt;One of the goals of Hudson is to minimise configuration. So when a plugin can detect an configuration option automatically, it should detect it automatically (possibly providing an “Advanced” option button to let users override the detection if Hudson gets it wrong)&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Start small&lt;/h2&gt;&lt;p&gt;Looking at the JavaNCSS output, I see that there is a lot of information... and I only have one more Part left in this series! So I am not going to parse everything.  I am sure that in the future I will extend the Hudson plugin to parse all of the file, but for now I am just going to concentrate on the &amp;lt;pacakages&amp;gt; element. This gives users something useful and it’s better than nothing.&lt;/p&gt;&lt;p&gt;But what happens when I do get around to parsing the &lt;code&gt;&amp;lt;objects&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt; elements? People may have lots of old builds and they will want to see the trends of the &lt;code&gt;&amp;lt;objects&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;functions&amp;gt;&lt;/code&gt; results. I have two choices:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Tell them “Sorry, out of luck”&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Save the results with the build, and then the newer parser can extract the results when people want the trend.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Choosing between these two options can be difficult. My preference is to go with option two, as long as the results are not a really big file.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Best Practice #2:&lt;/i&gt;&lt;br/&gt;If you are not parsing everything in the results file, and the results file is not too big, and it can be parsed without reference to the source code, copy the results file to Hudson so that future versions of your plugin can read the information you are not currently parsing.&lt;/b&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Don’t over parse&lt;/h2&gt;&lt;p&gt;The results that we parse are going to be placed into an &lt;code&gt;Action&lt;/code&gt; object.  This &lt;code&gt;Action&lt;/code&gt; object will be serialized. When Hudson starts up, it reads all the results of all the builds.  If we place too much information in our &lt;code&gt;Action&lt;/code&gt; object, this can have a detrimental effect on Hudson’s performance.  When users have 50+ projects each with a couple of hundred builds, they will thank you for keeping your &lt;/code&gt;Action&lt;/code&gt; objects small.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Gotcha #2:&lt;/i&gt;&lt;br/&gt;Don’t store too much in your Action objects.&lt;/b&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Don’t under parse&lt;/h2&gt;&lt;p&gt;OK, so I have just given out about storing too much in your Action objects. There is a second problem... not storing enough! Most reporting plugins try to present a trend graph to show progress over a number of builds.  If we don’t store the information required to generate this trend graph inside our Action objects, then displaying the trend graph will require parsing all the result files for all builds of a project. This can have a detrimental effect on Hudson’s performance. When users have projects with a couple of hundred builds, they will thank you for keeping the information to generate the main trend graph inside your Action objects.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Gotcha #3:&lt;/i&gt;&lt;br/&gt;Store the information for generating the Project level trend graph in your Action objects.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;A case in point for Gotach #3 is the cobertura plugin, which at the time of writing, does not store the information for the main trend graph in the Action object.  I fully intend to fix this situation once I have finished this series!&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;How to parse&lt;/h2&gt;&lt;p&gt;Most of the result files that you will encounter are XML based.  We are writing our plugins in Java, so that gives us a range of parsers to choose from, e.g.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;SAX&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;DOM&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;StAX&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Roll your own&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;i&gt;Etc.&lt;/i&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Given that report files can end up very big for very big projects, we need to be careful how we parse the results:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Gotcha #4:&lt;/i&gt;&lt;br/&gt;Don’t parse XML results using DOM, as this will require reading the entire report file into memory.&lt;/b&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I am going to stick my neck out and make a recommendation:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Best Practice #3:&lt;/i&gt;&lt;br/&gt;Use an XML pull parser to parse XML report files.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;They are generally faster, use less memory, and are better suited to a “hit-and-run” style of result extraction.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Be able to aggregate parsing results&lt;/h2&gt;&lt;p&gt;You may think that there will only ever be one result file that you need to parse. Maven 2 usually throws a spanner into that model, and everyone has their own ANT build script, so:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;b&gt;&lt;i&gt;Gotcha #5:&lt;/i&gt;&lt;br/&gt;Don’t assume you only have to parse one report file for each project.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;This gotcha arrives from the code coverage plugins (emma, clover, cobertura).  Initially, you would think that people are only interested in one code coverage result, i.e. the coverage for the project... so they will only have one result file that we need to parse, right? Wrong! Some tools/build scripts generate a report for each module but only generate a summary report in non-conforming HTML. Some tools / build scripts generate a report for unit tests and integration tests separately. It’s a mess, and don’t get me started on using different tools for different test types...&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;The parsing engine&lt;/h2&gt;&lt;p&gt;Ok, so here is the parsing engine:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;package hudson.plugins.javancss.parser;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.util.IOException2;&lt;br /&gt;import org.xmlpull.v1.XmlPullParser;&lt;br /&gt;import org.xmlpull.v1.XmlPullParserException;&lt;br /&gt;import org.xmlpull.v1.XmlPullParserFactory;&lt;br /&gt;&lt;br /&gt;import java.io.*;&lt;br /&gt;import java.util.*;&lt;br /&gt;&lt;br /&gt;public class Statistic implements Serializable {&lt;br /&gt;&lt;br /&gt; private AbstractBuild&amp;lt;?, ?&amp;gt; owner;&lt;br /&gt; private String name;&lt;br /&gt; private long classes;&lt;br /&gt; private long functions;&lt;br /&gt; private long ncss;&lt;br /&gt; private long javadocs;&lt;br /&gt; private long javadocLines;&lt;br /&gt; private long singleCommentLines;&lt;br /&gt; private long multiCommentLines;&lt;br /&gt;&lt;br /&gt; public static Collection&amp;lt;Statistic&amp;gt; parse(File inFile)&lt;br /&gt;         throws IOException, XmlPullParserException {&lt;br /&gt;     Collection&amp;lt;Statistic&amp;gt; results = new ArrayList&amp;lt;Statistic&amp;gt;();&lt;br /&gt;     FileInputStream fis = null;&lt;br /&gt;     BufferedInputStream bis = null;&lt;br /&gt;     try {&lt;br /&gt;         fis = new FileInputStream(inFile);&lt;br /&gt;         bis = new BufferedInputStream(fis);&lt;br /&gt;         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();&lt;br /&gt;         factory.setNamespaceAware(true);&lt;br /&gt;         factory.setValidating(false);&lt;br /&gt;         XmlPullParser parser = factory.newPullParser();&lt;br /&gt;         parser.setInput(bis, null);&lt;br /&gt;&lt;br /&gt;         // check that the first tag is &amp;lt;javancss&amp;gt;&lt;br /&gt;         expectNextTag(parser, "javancss");&lt;br /&gt;&lt;br /&gt;         // skip until we get to the &amp;lt;packages&amp;gt; tag&lt;br /&gt;         while (parser.getDepth() &amp;gt; 0&lt;br /&gt;                &amp;amp;&amp;amp; (parser.getEventType() != XmlPullParser.START_TAG&lt;br /&gt;                    || !"packages".equals(parser.getName()))) {&lt;br /&gt;             parser.next();&lt;br /&gt;         }&lt;br /&gt;         while (parser.getDepth() &amp;gt; 0&lt;br /&gt;                &amp;amp;&amp;amp; (parser.getEventType() != XmlPullParser.START_TAG&lt;br /&gt;                    || !"package".equals(parser.getName()))) {&lt;br /&gt;             parser.next();&lt;br /&gt;         }&lt;br /&gt;         while (parser.getDepth() &amp;gt;= 2&lt;br /&gt;                &amp;amp;&amp;amp; parser.getEventType() == XmlPullParser.START_TAG&lt;br /&gt;                &amp;amp;&amp;amp; "package".equals(parser.getName())) {&lt;br /&gt;             Map&amp;lt;String, String&amp;gt; data = new HashMap&amp;lt;String, String&amp;gt;();&lt;br /&gt;             String lastTag = null;&lt;br /&gt;             String lastText = null;&lt;br /&gt;             int depth = parser.getDepth();&lt;br /&gt;             while (parser.getDepth() &amp;gt;= depth) {&lt;br /&gt;                 parser.next();&lt;br /&gt;                 switch (parser.getEventType()) {&lt;br /&gt;                     case XmlPullParser.START_TAG:&lt;br /&gt;                         lastTag = parser.getName();&lt;br /&gt;                         break;&lt;br /&gt;                     case XmlPullParser.TEXT:&lt;br /&gt;                         lastText = parser.getText();&lt;br /&gt;                         break;&lt;br /&gt;                     case XmlPullParser.END_TAG:&lt;br /&gt;                         if (parser.getDepth() == 4&lt;br /&gt;                             &amp;amp;&amp;amp; lastTag != null&lt;br /&gt;                             &amp;amp;&amp;amp; lastText != null) {&lt;br /&gt;                             data.put(lastTag, lastText);&lt;br /&gt;                         }&lt;br /&gt;                         lastTag = null;&lt;br /&gt;                         lastText = null;&lt;br /&gt;                         break;&lt;br /&gt;                 }&lt;br /&gt;             }&lt;br /&gt;             if (data.containsKey("name")) {&lt;br /&gt;                 Statistic s = new Statistic(data.get("name"));&lt;br /&gt;                 s.setClasses(Long.valueOf(data.get("classes")));&lt;br /&gt;                 s.setFunctions(Long.valueOf(data.get("functions")));&lt;br /&gt;                 s.setNcss(Long.valueOf(data.get("ncss")));&lt;br /&gt;                 s.setJavadocs(Long.valueOf(data.get("javadocs")));&lt;br /&gt;                 s.setJavadocLines(Long.valueOf(data.get("javadoc_lines")));&lt;br /&gt;                 s.setSingleCommentLines(Long.valueOf(data.get("single_comment_lines")));&lt;br /&gt;                 s.setMultiCommentLines(Long.valueOf(data.get("multi_comment_lines")));&lt;br /&gt;                 results.add(s);&lt;br /&gt;             }&lt;br /&gt;             parser.next();&lt;br /&gt;         }&lt;br /&gt;     } catch (XmlPullParserException e) {&lt;br /&gt;         throw new IOException2(e);&lt;br /&gt;     } finally {&lt;br /&gt;         if (bis != null) {&lt;br /&gt;             bis.close();&lt;br /&gt;         }&lt;br /&gt;         if (fis != null) {&lt;br /&gt;             fis.close();&lt;br /&gt;         }&lt;br /&gt;     }&lt;br /&gt;     return results;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private static void skipTag(XmlPullParser parser)&lt;br /&gt;         throws IOException, XmlPullParserException {&lt;br /&gt;     parser.next();&lt;br /&gt;     endElement(parser);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private static void expectNextTag(XmlPullParser parser, String tag)&lt;br /&gt;         throws IOException, XmlPullParserException {&lt;br /&gt;     while (true) {&lt;br /&gt;         if (parser.getEventType() != XmlPullParser.START_TAG) {&lt;br /&gt;             parser.next();&lt;br /&gt;             continue;&lt;br /&gt;         }&lt;br /&gt;         if (parser.getName().equals(tag)) {&lt;br /&gt;             return;&lt;br /&gt;         }&lt;br /&gt;         throw new IOException("Expecting tag " + tag);&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private static void endElement(XmlPullParser parser)&lt;br /&gt;         throws IOException, XmlPullParserException {&lt;br /&gt;     int depth = parser.getDepth();&lt;br /&gt;     while (parser.getDepth() &amp;gt;= depth) {&lt;br /&gt;         parser.next();&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public Statistic(String name) {&lt;br /&gt;     this.name = name;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt; // Simple getters and setters for all the private fields&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt; // equals based on all private fields, hashCode based on&lt;br /&gt; // name and owner.&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt; // toString&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Essentially the main work is done in the static parse method. It takes a File and tries to parse it. We get an XML Pull Parser for the stream and ensure that it is neither namespace aware nor validating as the file format does not use namespaces and we will be forgiving on the XML format.&lt;/p&gt;&lt;p&gt;The first tag should be &lt;code&gt;&amp;lt;javancss&amp;gt;&lt;/code&gt; and after that we skip until we get a &lt;code&gt;&amp;lt;pacakages&amp;gt;&lt;/code&gt; tag.  Once we have found the &lt;code&gt;&amp;lt;packages&amp;gt;&lt;/code&gt; tag we skip until we hit the first &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;p&gt;We are reverse engineering the JavaNCSS file format, so we will not make any assumptions about the order of the child elements in the &amp;lt;package&amp;gt; element.  We put all the child elements into a &lt;code&gt;Map&lt;/code&gt; keyed by the element name, and then when we reach the end of the &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt; element we pull out the information we were after from the &lt;code&gt;Map&lt;/code&gt; and put it into a &lt;code&gt;Statistic&lt;/code&gt; object and add that to the collection of results that we will return.&lt;/p&gt;&lt;p&gt;As soon as we hit the end of the &lt;code&gt;&amp;lt;packages&amp;gt;&lt;/code&gt; element, we stop parsing.&lt;/p&gt;&lt;h2&gt;Supporting aggregation&lt;/h2&gt;&lt;p&gt;In order to support aggregation of multiple results, we'll add some utility methods to the &lt;code&gt;Statistic&lt;/code&gt; class, first we need methods that allow us to calculate totals:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;package hudson.plugins.javancss.parser;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;public class Statistic implements Serializable {&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt; public static Statistic total(Collection&amp;lt;Statistic&amp;gt;... results) {&lt;br /&gt;     Collection&amp;lt;Statistic&amp;gt; merged = merge(results);&lt;br /&gt;     Statistic total = new Statistic("");&lt;br /&gt;     for (Statistic individual : merged) {&lt;br /&gt;         total.add(individual);&lt;br /&gt;     }&lt;br /&gt;     return total;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void add(Statistic r) {&lt;br /&gt;     classes += r.classes;&lt;br /&gt;     functions += r.functions;&lt;br /&gt;     ncss += r.ncss;&lt;br /&gt;     javadocs += r.javadocs;&lt;br /&gt;     javadocLines += r.javadocLines;&lt;br /&gt;     singleCommentLines += r.singleCommentLines;&lt;br /&gt;     multiCommentLines += r.multiCommentLines;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;total&lt;/code&gt; method just calculates the total of all the statistics in a collection of statistics.  We will also need to be able to merge different result sets.  This should aggregate totals for each package separately and return a collection with one total statistic for each package:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;package hudson.plugins.javancss.parser;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;public class Statistic implements Serializable {&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt; public static Collection&amp;lt;Statistic&amp;gt; merge(&lt;br /&gt;         Collection&amp;lt;Statistic&amp;gt;... results) {&lt;br /&gt;     if (results.length == 0) {&lt;br /&gt;         return Collections.emptySet();&lt;br /&gt;     } else if (results.length == 1) {&lt;br /&gt;         return results[0];&lt;br /&gt;     } else {&lt;br /&gt;         Map&amp;lt;String, Statistic&amp;gt; merged =&lt;br /&gt;                 new HashMap&amp;lt;String, Statistic&amp;gt;();&lt;br /&gt;         for (Collection&amp;lt;Statistic&amp;gt; result : results) {&lt;br /&gt;             for (Statistic individual : result) {&lt;br /&gt;                 if (!merged.containsKey(individual.name)) {&lt;br /&gt;                     merged.put(individual.name,&lt;br /&gt;                             new Statistic(individual.name));&lt;br /&gt;                 }&lt;br /&gt;                 merged.get(individual.name).add(individual);&lt;br /&gt;             }&lt;br /&gt;         }&lt;br /&gt;         return merged.values();&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;That is pretty much it for the parser engine.&lt;/p&gt;&lt;h2&gt;The Ghostwriter&lt;/h2&gt;&lt;p&gt;Now we need to hook the engine into our publisher. We will need to configure the UI elements and the &lt;code&gt;Action&lt;/code&gt;s... all tasks for the final part, but for now, we'll just hook it up.  We want to run the parsing on the slave side so we implement &lt;code&gt;Ghostwriter.SlaveGhostwriter&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;p&gt;package hudson.plugins.javancss;&lt;br /&gt;&lt;br /&gt;import hudson.FilePath;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.plugins.helpers.BuildProxy;&lt;br /&gt;import hudson.plugins.helpers.Ghostwriter;&lt;br /&gt;import hudson.plugins.javancss.parser.Statistic;&lt;br /&gt;import org.xmlpull.v1.XmlPullParserException;&lt;br /&gt;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.HashSet;&lt;br /&gt;import java.util.Set;&lt;br /&gt;&lt;br /&gt;public class JavaNCSSGhostwriter&lt;br /&gt;     implements Ghostwriter,&lt;br /&gt;     Ghostwriter.SlaveGhostwriter {&lt;br /&gt;&lt;br /&gt; private final String reportFilenamePattern;&lt;br /&gt;&lt;br /&gt; public JavaNCSSGhostwriter(String reportFilenamePattern) {&lt;br /&gt;     this.reportFilenamePattern = reportFilenamePattern;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public boolean performFromSlave(&lt;br /&gt;         BuildProxy build,&lt;br /&gt;         BuildListener listener)&lt;br /&gt;         throws InterruptedException, IOException {&lt;br /&gt;     FilePath[] paths = build.getExecutionRootDir()&lt;br /&gt;             .list(reportFilenamePattern);&lt;br /&gt;     Collection&amp;lt;Statistic&amp;gt; results = null;&lt;br /&gt;     Set&amp;lt;String&amp;gt; parsedFiles = new HashSet&amp;lt;String&amp;gt;();&lt;br /&gt;     for (FilePath path : paths) {&lt;br /&gt;         final String pathStr = path.getRemote();&lt;br /&gt;         if (!parsedFiles.contains(pathStr)) {&lt;br /&gt;             parsedFiles.add(pathStr);&lt;br /&gt;             try {&lt;br /&gt;                 Collection&amp;lt;Statistic&amp;gt; result =&lt;br /&gt;                         Statistic.parse(new File(pathStr));&lt;br /&gt;                 if (results == null) {&lt;br /&gt;                     results = result;&lt;br /&gt;                 } else {&lt;br /&gt;                     results = Statistic.merge(results, result);&lt;br /&gt;                 }&lt;br /&gt;&lt;br /&gt;                 // TODO copy the parsed file to the master&lt;br /&gt;&lt;br /&gt;             } catch (XmlPullParserException e) {&lt;br /&gt;                 e.printStackTrace(listener.getLogger());&lt;br /&gt;             }&lt;br /&gt;         }&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     // TODO add the results into an Action an attach it to the&lt;br /&gt;     // build.&lt;br /&gt;&lt;br /&gt;     return true;&lt;br /&gt; }&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Basically, we search the supplied wildcard-path for report files and merge all the results together into a collection of results.  In the final part of this series we will create our Action to hold the results and wire everything together.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4883496773793834556?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4883496773793834556/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4883496773793834556' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4883496773793834556'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4883496773793834556'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/04/writing-hudson-plugin-part-6-parsing.html' title='Writing a Hudson plugin (Part 6 - Parsing the results)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-7976261940228031492</id><published>2008-03-26T22:33:00.005Z</published><updated>2011-05-30T14:56:15.202+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plugin (Part 5½ - Typos corrected)</title><content type='html'>&lt;p&gt;Ok, life has got in the way and I have not been able to update this series as quickly as I originally intended.  In any case, there were a number of bugs in Hudson that I discovered and are now fixed, and there was a good deal of tidy-up needed in order for me to figure out what to do for parts 6 and 7. The good news is that I am getting closer to finishing writing these final two parts.  The bad news is that there are a number of corrections to the previous posts (I have put some corrections in-line for parts 4 and 5).  This post aims to ensure that, for parts 6 and 7, everyone is able to follow from the same code!&lt;/p&gt;&lt;h4&gt;pom.xml&lt;/h4&gt;&lt;p&gt;This needs to be updated to reference a Hudson version of at least 1.200 in order to have the bugs I identified fixed.&lt;/p&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/AbstractBuildAction.java&lt;/h4&gt;&lt;p&gt;AFAIK I escaped all the HTML entities and this file is the same as constructed from the previous posts.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.HealthReportingAction;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractBuildAction&amp;lt;BUILD extends AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&lt;br /&gt;       implements HealthReportingAction {&lt;br /&gt;&lt;br /&gt;   private BUILD build = null;&lt;br /&gt;&lt;br /&gt;   protected AbstractBuildAction() {&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public synchronized BUILD getBuild() {&lt;br /&gt;       return build;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public synchronized void setBuild(BUILD build) {&lt;br /&gt;       if (this.build == null &amp;amp;&amp;amp; this.build != build) {&lt;br /&gt;           this.build = build;&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean isFloatingBoxActive() {&lt;br /&gt;       return false;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean isGraphActive() {&lt;br /&gt;       return false;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public String getGraphName() {&lt;br /&gt;       return getDisplayName();&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public String getSummary() {&lt;br /&gt;       return "";&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/AbstractMavenReporterImpl.java&lt;/h4&gt;&lt;p&gt;AFAIK I escaped all the HTML entities and this file is the same as constructed from the previous posts.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.maven.MavenReporter;&lt;br /&gt;import hudson.maven.MojoInfo;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.model.Result;&lt;br /&gt;import org.apache.maven.project.MavenProject;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractMavenReporterImpl extends MavenReporter {&lt;br /&gt;&lt;br /&gt;   protected MojoExecutionReportingMode getExecutionMode() {&lt;br /&gt;       return MojoExecutionReportingMode.ONLY_REPORT_ON_SUCCESS;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean postExecute(MavenBuildProxy build,&lt;br /&gt;                              MavenProject pom,&lt;br /&gt;                              MojoInfo mojo,&lt;br /&gt;                              BuildListener listener,&lt;br /&gt;                              Throwable error)&lt;br /&gt;           throws InterruptedException, IOException {&lt;br /&gt;       if (!isExecutingMojo(mojo)) {&lt;br /&gt;           // not a mojo who's result we are interested in&lt;br /&gt;           return true;&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       final Boolean okToContinue = getExecutionMode().isOkToContinue(this, &lt;br /&gt;               build, listener, error);&lt;br /&gt;       if (okToContinue != null) {&lt;br /&gt;           return okToContinue;&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       build.registerAsProjectAction(this);&lt;br /&gt;&lt;br /&gt;       return BuildProxy.doPerform(newGhostwriter(pom, mojo), build, pom, listener);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   protected abstract boolean isExecutingMojo(MojoInfo mojo);&lt;br /&gt;&lt;br /&gt;   protected abstract Ghostwriter newGhostwriter(MavenProject pom, MojoInfo mojo);&lt;br /&gt;&lt;br /&gt;   public boolean preExecute(MavenBuildProxy build,&lt;br /&gt;                             MavenProject pom,&lt;br /&gt;                             MojoInfo mojo,&lt;br /&gt;                             BuildListener listener)&lt;br /&gt;           throws InterruptedException, IOException {&lt;br /&gt;       return !isAutoconfMojo(mojo) || autoconfPom(build, pom, mojo, listener);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   protected boolean autoconfPom(MavenBuildProxy build,&lt;br /&gt;                                 MavenProject pom,&lt;br /&gt;                                 MojoInfo mojo,&lt;br /&gt;                                 BuildListener listener) {&lt;br /&gt;       return true;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   protected boolean isAutoconfMojo(MojoInfo mojo) {&lt;br /&gt;       return false;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   protected enum MojoExecutionReportingMode {&lt;br /&gt;       ONLY_REPORT_ON_SUCCESS {&lt;br /&gt;           Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                  MavenBuildProxy build,&lt;br /&gt;                                  BuildListener listener,&lt;br /&gt;                                  Throwable error) {&lt;br /&gt;               return error == null ? null : Boolean.TRUE;&lt;br /&gt;           }&lt;br /&gt;       },&lt;br /&gt;       ALWAYS_REPORT_STABLE {&lt;br /&gt;           Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                  MavenBuildProxy build,&lt;br /&gt;                                  BuildListener listener,&lt;br /&gt;                                  Throwable error) {&lt;br /&gt;               return null;&lt;br /&gt;           }},&lt;br /&gt;       REPORT_UNSTABLE_ON_ERROR {&lt;br /&gt;           Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                  MavenBuildProxy build,&lt;br /&gt;                                  BuildListener listener,&lt;br /&gt;                                  Throwable error) {&lt;br /&gt;               if (error != null) {&lt;br /&gt;                   listener.getLogger().println("[HUDSON] "&lt;br /&gt;                           + reporter.getDescriptor().getDisplayName()&lt;br /&gt;                           + " setting build to UNSTABLE");&lt;br /&gt;                   build.setResult(Result.UNSTABLE);&lt;br /&gt;               }&lt;br /&gt;               return null;&lt;br /&gt;           }&lt;br /&gt;       };&lt;br /&gt;&lt;br /&gt;       abstract Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                       MavenBuildProxy build,&lt;br /&gt;                                       BuildListener listener,&lt;br /&gt;                                       Throwable error);&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/AbstractProjectAction.java&lt;/h4&gt;&lt;p&gt;AFAIK I escaped all the HTML entities and this file is the same as constructed from the previous posts.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractProject;&lt;br /&gt;import hudson.model.Actionable;&lt;br /&gt;&lt;br /&gt;abstract public class AbstractProjectAction&amp;lt;PROJECT extends AbstractProject&amp;lt;?, ?&amp;gt;&amp;gt; &lt;br /&gt;        extends Actionable {&lt;br /&gt;   private final PROJECT project;&lt;br /&gt;&lt;br /&gt;   protected AbstractProjectAction(PROJECT project) {&lt;br /&gt;       this.project = project;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public PROJECT getProject() {&lt;br /&gt;       return project;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean isFloatingBoxActive() {&lt;br /&gt;       return true;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean isGraphActive() {&lt;br /&gt;       return false;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public String getGraphName() {&lt;br /&gt;       return getDisplayName();&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/AbstractPublisherImpl.java&lt;/h4&gt;&lt;p&gt;Some of the HTML entities were not properly escaped.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.tasks.Publisher;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.Launcher;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractPublisherImpl extends Publisher {&lt;br /&gt;&lt;br /&gt;   protected abstract Ghostwriter newGhostwriter();&lt;br /&gt;&lt;br /&gt;   public boolean perform(AbstractBuild&amp;lt;?, ?&amp;gt; build, Launcher launcher, &lt;br /&gt;           final BuildListener listener)&lt;br /&gt;           throws InterruptedException, IOException {&lt;br /&gt;       return BuildProxy.doPerform(newGhostwriter(), build, listener);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean prebuild(AbstractBuild&amp;lt;?, ?&amp;gt; build, BuildListener listener) {&lt;br /&gt;       return true;&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/BuildProxy.java&lt;/h4&gt;&lt;p&gt;Again, some of the HTML entities were not properly escaped.  Additionally, this is the file that has had the most updates during this tutorial, so here it is in full:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.FilePath;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.util.IOException2;&lt;br /&gt;import hudson.model.Action;&lt;br /&gt;import hudson.model.Result;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;&lt;br /&gt;import java.util.Calendar;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;&lt;br /&gt;import org.apache.maven.project.MavenProject;&lt;br /&gt;&lt;br /&gt;public final class BuildProxy implements Serializable {&lt;br /&gt;   private final FilePath artifactsDir;&lt;br /&gt;   private final FilePath projectRootDir;&lt;br /&gt;   private final FilePath buildRootDir;&lt;br /&gt;   private final FilePath executionRootDir;&lt;br /&gt;   private final Calendar timestamp;&lt;br /&gt;   private final List&amp;lt;AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&amp;gt; actions =&lt;br /&gt;           new ArrayList&amp;lt;AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&amp;gt;();&lt;br /&gt;   private Result result = null;&lt;br /&gt;   private boolean continueBuild = true;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   public static boolean doPerform(Ghostwriter ghostwriter,&lt;br /&gt;                                   AbstractBuild&amp;lt;?, ?&amp;gt; build,&lt;br /&gt;                                   BuildListener listener)&lt;br /&gt;           throws IOException, InterruptedException {&lt;br /&gt;&lt;br /&gt;       // first, do we need to do anything on the slave&lt;br /&gt;&lt;br /&gt;       if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;&lt;br /&gt;           // construct the BuildProxy instance that we will use&lt;br /&gt;&lt;br /&gt;           BuildProxy buildProxy = new BuildProxy(&lt;br /&gt;                   new FilePath(build.getArtifactsDir()),&lt;br /&gt;                   new FilePath(build.getProject().getRootDir()),&lt;br /&gt;                   new FilePath(build.getRootDir()),&lt;br /&gt;                   build.getProject().getModuleRoot(),&lt;br /&gt;                   build.getTimestamp());&lt;br /&gt;&lt;br /&gt;           BuildProxyCallableHelper callableHelper = new BuildProxyCallableHelper(&lt;br /&gt;                   buildProxy, ghostwriter, listener);&lt;br /&gt;&lt;br /&gt;           try {&lt;br /&gt;               buildProxy = buildProxy.getExecutionRootDir().act(callableHelper);&lt;br /&gt;&lt;br /&gt;               buildProxy.updateBuild(build);&lt;br /&gt;&lt;br /&gt;               // terminate the build if necessary&lt;br /&gt;               if (!buildProxy.isContinueBuild()) {&lt;br /&gt;                   return false;&lt;br /&gt;               }&lt;br /&gt;           } catch (Exception e) {&lt;br /&gt;               throw unwrapException(e, listener);&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       // finally, on to the master&lt;br /&gt;&lt;br /&gt;       final Ghostwriter.MasterGhostwriter masterGhostwriter = &lt;br /&gt;               Ghostwriter.MasterGhostwriter.class.cast(ghostwriter);&lt;br /&gt;&lt;br /&gt;       return masterGhostwriter == null&lt;br /&gt;               || masterGhostwriter.performFromMaster(build, &lt;br /&gt;                       build.getProject().getModuleRoot(), listener);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   private static RuntimeException unwrapException(Exception e,&lt;br /&gt;                                                   BuildListener listener)&lt;br /&gt;           throws IOException, InterruptedException {&lt;br /&gt;       if (e.getCause() instanceof IOException) {&lt;br /&gt;           throw new IOException2(e.getCause().getMessage(), e);&lt;br /&gt;       }&lt;br /&gt;       if (e.getCause() instanceof InterruptedException) {&lt;br /&gt;           e.getCause().printStackTrace(listener.getLogger());&lt;br /&gt;           throw new InterruptedException(e.getCause().getMessage());&lt;br /&gt;       }&lt;br /&gt;       if (e.getCause() instanceof RuntimeException) {&lt;br /&gt;           throw new RuntimeException(e.getCause());&lt;br /&gt;       }&lt;br /&gt;       // How on earth do we get this far down the branch&lt;br /&gt;       e.printStackTrace(listener.getLogger());&lt;br /&gt;       throw new RuntimeException("Unexpected exception", e);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public void updateBuild(AbstractBuild&amp;lt;?, ?&amp;gt; build) {&lt;br /&gt;       for (AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt; action : actions) {&lt;br /&gt;           if (!build.getActions().contains(action)) {&lt;br /&gt;               action.setBuild(build);&lt;br /&gt;               build.getActions().add(action);&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       if (result != null &amp;amp;&amp;amp; result.isWorseThan(build.getResult())) {&lt;br /&gt;           build.setResult(result);&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public static boolean doPerform(Ghostwriter ghostwriter,&lt;br /&gt;                                   MavenBuildProxy mavenBuildProxy,&lt;br /&gt;                                   MavenProject pom,&lt;br /&gt;                                   final BuildListener listener)&lt;br /&gt;           throws InterruptedException, IOException {&lt;br /&gt;&lt;br /&gt;       // first, construct the BuildProxy instance that we will use&lt;br /&gt;&lt;br /&gt;       BuildProxy buildProxy = new BuildProxy(&lt;br /&gt;               mavenBuildProxy.getArtifactsDir(),&lt;br /&gt;               mavenBuildProxy.getProjectRootDir(),&lt;br /&gt;               mavenBuildProxy.getRootDir(),&lt;br /&gt;               new FilePath(pom.getBasedir()),&lt;br /&gt;               mavenBuildProxy.getTimestamp());&lt;br /&gt;&lt;br /&gt;       // do we need to do anything on the slave&lt;br /&gt;&lt;br /&gt;       if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;           final Ghostwriter.SlaveGhostwriter slaveGhostwriter = &lt;br /&gt;                   (Ghostwriter.SlaveGhostwriter) ghostwriter;&lt;br /&gt;&lt;br /&gt;           // terminate the build if necessary&lt;br /&gt;           if (!slaveGhostwriter.performFromSlave(buildProxy, listener)) {&lt;br /&gt;               return false;&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       // finally, on to the master&lt;br /&gt;&lt;br /&gt;       try {&lt;br /&gt;           return mavenBuildProxy.execute(new BuildProxyCallableHelper(&lt;br /&gt;                   buildProxy, ghostwriter, listener));&lt;br /&gt;       } catch (Exception e) {&lt;br /&gt;           throw unwrapException(e, listener);&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   private BuildProxy(FilePath artifactsDir,&lt;br /&gt;                      FilePath projectRootDir,&lt;br /&gt;                      FilePath buildRootDir,&lt;br /&gt;                      FilePath executionRootDir,&lt;br /&gt;                      Calendar timestamp) {&lt;br /&gt;       this.artifactsDir = artifactsDir;&lt;br /&gt;       this.projectRootDir = projectRootDir;&lt;br /&gt;       this.buildRootDir = buildRootDir;&lt;br /&gt;       this.executionRootDir = executionRootDir;&lt;br /&gt;       this.timestamp = timestamp;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public List&amp;lt;AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&amp;gt; getActions() {&lt;br /&gt;       return actions;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public FilePath getArtifactsDir() {&lt;br /&gt;       return artifactsDir;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public FilePath getBuildRootDir() {&lt;br /&gt;       return buildRootDir;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public FilePath getExecutionRootDir() {&lt;br /&gt;       return executionRootDir;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public FilePath getProjectRootDir() {&lt;br /&gt;       return projectRootDir;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public Calendar getTimestamp() {&lt;br /&gt;       return timestamp;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public Result getResult() {&lt;br /&gt;       return result;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public void setResult(Result result) {&lt;br /&gt;       this.result = result;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public boolean isContinueBuild() {&lt;br /&gt;       return continueBuild;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public void setContinueBuild(boolean continueBuild) {&lt;br /&gt;       this.continueBuild = continueBuild;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/BuildProxyCallableHelper.java&lt;/h4&gt;&lt;p&gt;Lost some HTML entities (again!)&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.remoting.Callable;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.maven.MavenBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;class BuildProxyCallableHelper implements Callable&amp;lt;BuildProxy, Exception&amp;gt;,&lt;br /&gt;       MavenBuildProxy.BuildCallable&amp;lt;Boolean, Exception&amp;gt; {&lt;br /&gt;&lt;br /&gt;   private final BuildProxy buildProxy;&lt;br /&gt;   private final Ghostwriter ghostwriter;&lt;br /&gt;   private final BuildListener listener;&lt;br /&gt;&lt;br /&gt;   BuildProxyCallableHelper(BuildProxy buildProxy,&lt;br /&gt;                            Ghostwriter ghostwriter,&lt;br /&gt;                            BuildListener listener) {&lt;br /&gt;       this.buildProxy = buildProxy;&lt;br /&gt;       this.ghostwriter = ghostwriter;&lt;br /&gt;       this.listener = listener;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public Boolean call(MavenBuild mavenBuild) throws Exception {&lt;br /&gt;       buildProxy.updateBuild(mavenBuild);&lt;br /&gt;       if (ghostwriter instanceof Ghostwriter.MasterGhostwriter) {&lt;br /&gt;           final Ghostwriter.MasterGhostwriter masterBuildStep =&lt;br /&gt;                   (Ghostwriter.MasterGhostwriter) ghostwriter;&lt;br /&gt;           return masterBuildStep.performFromMaster(mavenBuild, &lt;br /&gt;                   buildProxy.getExecutionRootDir(), listener);&lt;br /&gt;       }&lt;br /&gt;       return true;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public BuildProxy call() throws Exception {&lt;br /&gt;       if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;           final Ghostwriter.SlaveGhostwriter slaveBuildStep =&lt;br /&gt;                   (Ghostwriter.SlaveGhostwriter) ghostwriter;&lt;br /&gt;           try {&lt;br /&gt;               buildProxy.setContinueBuild(&lt;br /&gt;                       slaveBuildStep.performFromSlave(buildProxy, listener));&lt;br /&gt;               return buildProxy;&lt;br /&gt;           } catch (IOException e) {&lt;br /&gt;               throw new Exception(e);&lt;br /&gt;           } catch (InterruptedException e) {&lt;br /&gt;               throw new Exception(e);&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;       return buildProxy;&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/java/hudson/plugins/helpers/Ghostwriter.java&lt;/h4&gt;&lt;p&gt;Java Generics are a real gotcha for HTML entities&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.FilePath;&lt;br /&gt;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public interface Ghostwriter extends Serializable {&lt;br /&gt;   public static interface SlaveGhostwriter extends Ghostwriter {&lt;br /&gt;       boolean performFromSlave(BuildProxy build, BuildListener listener) &lt;br /&gt;               throws InterruptedException, IOException;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public static interface MasterGhostwriter extends Ghostwriter {&lt;br /&gt;       boolean performFromMaster(AbstractBuild&amp;lt;?, ?&amp;gt; build, &lt;br /&gt;               FilePath executionRoot, BuildListener listener) &lt;br /&gt;               throws InterruptedException, IOException;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractBuildAction/enlargedGraph.jelly&lt;/h4&gt;&lt;p&gt;I left a "&lt;code&gt;css&lt;/code&gt;" attribute in the &lt;code&gt;&amp;lt;l:layout&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;   &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags"&amp;gt;&lt;br /&gt;       &amp;lt;st:include it="${it.build}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;           &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;               &amp;lt;h1&amp;gt;${it.graphName}&amp;lt;/h1&amp;gt;&lt;br /&gt;               &amp;lt;st:include page="largeGraph.jelly"/&amp;gt;&lt;br /&gt;           &amp;lt;/j:if&amp;gt;&lt;br /&gt;       &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;   &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractBuildAction/floatingBox.jelly&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;j:if&amp;gt;&lt;/code&gt; should be based on the &lt;code&gt;from&lt;/code&gt; variable and not &lt;code&gt;it&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;   &amp;lt;j:if test="${from.graphActive}"&amp;gt;&lt;br /&gt;       &amp;lt;div style="width:500px;"&amp;gt;&lt;br /&gt;           &amp;lt;div class="test-trend-caption"&amp;gt;&lt;br /&gt;               ${from.graphName}&lt;br /&gt;           &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;           &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;&lt;br /&gt;           &amp;lt;div style="text-align:right"&amp;gt;&lt;br /&gt;               &amp;lt;a href="enlargedGraph"&amp;gt;enlarge&amp;lt;/a&amp;gt;&lt;br /&gt;           &amp;lt;/div&amp;gt;&lt;br /&gt;       &amp;lt;/div&amp;gt;&lt;br /&gt;   &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractBuildAction/index.jelly&lt;/h4&gt;&lt;p&gt;I left a "&lt;code&gt;css&lt;/code&gt;" attribute in the &lt;code&gt;&amp;lt;l:layout&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;   &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags"&amp;gt;&lt;br /&gt;       &amp;lt;st:include it="${it.build}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;           &amp;lt;h1&amp;gt;${it.displayName}&amp;lt;/h1&amp;gt;&lt;br /&gt;           &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;               &amp;lt;h4&amp;gt;${it.graphName}&amp;lt;/h4&amp;gt;&lt;br /&gt;               &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;           &amp;lt;/j:if&amp;gt;&lt;br /&gt;           &amp;lt;st:include page="reportDetail.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;   &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractBuildAction/largeGraph.jelly, src/main/resources/hudson/plugins/helpers/AbstractBuildAction/normalGraph.jelly, and src/main/resources/hudson/plugins/helpers/AbstractBuildAction/reportDetail.jelly&lt;/h4&gt;&lt;p&gt;These are all the same content, and are basically empty placeholders to be overrided in classes that extend AbstractBuildAction. I do not think there are any corrections.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractBuildAction/summary.jelly&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;j:if&amp;gt;&lt;/code&gt; had an extra &lt;code&gt;}&lt;/code&gt; in the expression&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt"&amp;gt;&lt;br /&gt;   &amp;lt;t:summary icon="${it.iconFileName}"&amp;gt;&lt;br /&gt;       &amp;lt;a href="${it.urlName}"&amp;gt;${it.displayName}&amp;lt;/a&amp;gt;&lt;br /&gt;       ${it.summary}&lt;br /&gt;   &amp;lt;/t:summary&amp;gt;&lt;br /&gt;   &amp;lt;j:if test="${it.floatingBoxActive}"&amp;gt;&lt;br /&gt;       &amp;lt;div style="float:right"&amp;gt;&lt;br /&gt;           &amp;lt;st:include page="floatingBox.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;/div&amp;gt;&lt;br /&gt;   &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractProjectAction/enlargedGraph.jelly&lt;/h4&gt;&lt;p&gt;I left a "&lt;code&gt;css&lt;/code&gt;" attribute in the &lt;code&gt;&amp;lt;l:layout&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;   &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags"&amp;gt;&lt;br /&gt;       &amp;lt;st:include it="${it.project}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;           &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;               &amp;lt;h1&amp;gt;${it.graphName}&amp;lt;/h1&amp;gt;&lt;br /&gt;               &amp;lt;st:include page="largeGraph.jelly"/&amp;gt;&lt;br /&gt;           &amp;lt;/j:if&amp;gt;&lt;br /&gt;       &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;   &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractProjectAction/floatingBox.jelly&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;j:if&amp;gt;&lt;/code&gt; should be based on the &lt;code&gt;from&lt;/code&gt; variable and not &lt;code&gt;it&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:local="local"&amp;gt;&lt;br /&gt;   &amp;lt;j:if test="${from.graphActive}"&amp;gt;&lt;br /&gt;       &amp;lt;div style="width:500px;"&amp;gt;&lt;br /&gt;           &amp;lt;div class="test-trend-caption"&amp;gt;&lt;br /&gt;               ${from.graphName}&lt;br /&gt;           &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;           &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;&lt;br /&gt;           &amp;lt;div style="text-align:right"&amp;gt;&lt;br /&gt;               &amp;lt;a href="enlargedGraph"&amp;gt;enlarge&amp;lt;/a&amp;gt;&lt;br /&gt;           &amp;lt;/div&amp;gt;&lt;br /&gt;       &amp;lt;/div&amp;gt;&lt;br /&gt;   &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractProjectAction/index.jelly&lt;/h4&gt;&lt;p&gt;I left a "&lt;code&gt;css&lt;/code&gt;" attribute in the &lt;code&gt;&amp;lt;l:layout&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;   &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags"&amp;gt;&lt;br /&gt;       &amp;lt;st:include it="${it.project}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;           &amp;lt;h1&amp;gt;${it.displayName}&amp;lt;/h1&amp;gt;&lt;br /&gt;           &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;               &amp;lt;h4&amp;gt;${it.graphName}&amp;lt;/h4&amp;gt;&lt;br /&gt;               &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;           &amp;lt;/j:if&amp;gt;&lt;br /&gt;           &amp;lt;st:include page="reportDetail.jelly"/&amp;gt;&lt;br /&gt;       &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;   &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;h4&gt;src/main/resources/hudson/plugins/helpers/AbstractProjectAction/largeGraph.jelly, src/main/resources/hudson/plugins/helpers/AbstractProjectAction/normalGraph.jelly, and src/main/resources/hudson/plugins/helpers/AbstractProjectAction/reportDetail.jelly&lt;/h4&gt;&lt;p&gt;These are all the same content, and are basically empty placeholders to be overrided in classes that extend AbstractProjectAction. I do not think there are any corrections.&lt;/p&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;        xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-7976261940228031492?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/7976261940228031492/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=7976261940228031492' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7976261940228031492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7976261940228031492'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/03/writing-hudson-plugin-part-5-typos.html' title='Writing a Hudson plugin (Part 5½ - Typos corrected)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-482296725811034499</id><published>2008-02-20T19:12:00.010Z</published><updated>2011-05-30T14:56:15.203+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plug-in (Part 5 – Reporting)</title><content type='html'>&lt;p&gt;Until now we have been generating classes that collect the results we want to display. We have not hooked into Hudson's methods of displaying reports. There are essentially four places that we could want to generate reports:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In each individual build (we'll call this a Single Build Report),&lt;/li&gt;&lt;li&gt;In the project (we'll call this a Single Project Report),&lt;/li&gt;&lt;li&gt;In the summary of all the Maven 2 module builds associated with a multi-module Maven 2 build (we'll call this an Aggregate Build Report), and finally&lt;/li&gt;&lt;li&gt;In the Maven 2 project itself (we'll call this an Aggregate Project Report).&lt;/ul&gt;&lt;p&gt;To make matters more confusing, each of these reports usually to implement different classes and need to implement different interfaces:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Project Reports usually inherit from Actionable&lt;/li&gt;&lt;li&gt;Build Reports usually inherit from HealthReportingAction&lt;/li&gt;&lt;li&gt;Single Build Reports in Maven 2 projects need to implement AggregatableAction&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;What we need is to put some common framework around these reports to ensure that we are not repeating ourselves all the time. In general, each of these four reports will be doing mostly the same things.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The Build Reports will be displaying the results for a specific build&lt;/li&gt;&lt;li&gt;The Project Reports will be displaying the results for the latest build (and possibly a trend chart if that makes sense)&lt;/li&gt;&lt;li&gt;The Aggregate Reports will be displaying the aggregate of all the Single Reports&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Sound's like a job for multiple inheritance! Fortune would have it that Java does not support multiple inheritance, so we will have to use some form of composition to get the same effect, and generics can help reduce the repetition too. But enough! On to the solution.&lt;/p&gt;&lt;h2&gt;AbstractBuildAction&lt;/h2&gt;&lt;p&gt;We'll start with a parent class for all our Build Reports. We'll make this class generic, parameterised by the Build class that it operates on, so that we can use the same core code for all the Build Reports:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.HealthReportingAction;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractBuildAction&amp;lt;BUILD extends AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt;&lt;br /&gt;        implements HealthReportingAction {&lt;br /&gt;&lt;br /&gt;    private BUILD build = null;&lt;br /&gt;&lt;br /&gt;    protected AbstractBuildAction() {&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public synchronized BUILD getBuild() {&lt;br /&gt;        return build;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public synchronized void setBuild(BUILD build) {&lt;br /&gt;        if (this.build == null &amp;amp;&amp;amp; this.build != build) {&lt;br /&gt;            this.build = build;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;We store a reference to the build in a local variable and provide a getter for the build. Ideally we would like to the build variable to be final and initialize it in the constructor. However, Hudson's remoting interface will get in the way of this for any actions created on the slave.  The solution is to use a setter to set the build. Additionally, we have some logic that makes this setter write-once. This is just a safety net to stop us from accidentally confusing Hudson. Strictly speaking, if you are careful the setter can be justa a simple setter and not write once.&lt;/p&gt;&lt;p&gt;&lt;i&gt;[Correction]&lt;/i&gt; We also need to modify &lt;code&gt;BuildProxy&lt;/code&gt; so that it calls our setter for us for actions added from slave side executions:&lt;/p&gt;&lt;pre&gt;package hudson.plugsin.helpers;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;public final class BuildProxy implements Serializable {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    private final List&amp;lt;&lt;b&gt;AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?,?&amp;gt;&amp;gt;&lt;/b&gt;&amp;gt; actions = &lt;br /&gt;            new ArrayList&amp;lt;&lt;b&gt;AbstractBuildAction&amp;lt;?,?&amp;gt;&amp;gt;&lt;/b&gt;&amp;gt;();&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public void updateBuild(AbstractBuild&amp;lt;,?&amp;gt; build) {&lt;br /&gt;        &lt;b&gt;for (AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?,?&amp;gt;&amp;gt; action: actions) {&lt;br /&gt;            if (!build.getActions().contains(action)) {&lt;br /&gt;                action.setBuild(build);&lt;br /&gt;                build.getActions().add(action);&lt;br /&gt;            }&lt;br /&gt;        }&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;        if (result != null &amp;amp;&amp;amp; result.isWorseThan(build.getResult())) {&lt;br /&gt;            build.setResult(result);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public List&amp;lt;&lt;b&gt;AbstractBuildAction&amp;lt;AbstractBuild&amp;lt;?,?&amp;gt;&amp;gt;&lt;/b&gt;&amp;gt; getActions() {&lt;br /&gt;        return actions;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;&lt;i&gt;[/Correction]&lt;/i&gt;&lt;/p&gt;&lt;p&gt;In addition to these simple getters and setters, we want to provide a framework for displaying the report detail.  Each different type of report has different ways of fitting into the Hudson UI. We are going to attempt to standardise these by using wrapper &lt;code&gt;.jelly&lt;/code&gt; files to call a standard set which implementing classes can override. Jelly files are stored as resources in the hudson plugin, so with the Maven2 project structure for a Hudson plugin, the java source is  &lt;code&gt;/src/main/java/hudson/plugins/helpers/AbstractBuildAction.java&lt;/code&gt; and the jelly files for this java class are in &lt;code&gt;/src/main/resources/hudson/plugins/helpers/AbstractBuildAction/&lt;/code&gt;. The basic things that all build reports want to do are as follows:&lt;ul&gt;&lt;li&gt;A main report detail page (e.g. package level summary of the number of lines of code)&lt;/li&gt;&lt;li&gt;A graph of some sort, with the option to enlarge it. (e.g. trend graph of the number of lines of code)&lt;/li&gt;&lt;li&gt;A simple summary of the report for the main page (i.e. "17,345 lines of code (+1,534)")&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We will implement this functionality with some properties of the AbstractBuildAction, and some default &lt;code&gt;.jelly&lt;/code&gt; files. First the additional properties:&lt;/p&gt;&lt;pre&gt;...&lt;br /&gt;&lt;br /&gt;public abstract class AbstractBuildAction&amp;lt;BUILD extends AbstractBuild&amp;lt;?, ?&amp;gt;&amp;gt; implements HealthReportingAction {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public boolean isFloatingBoxActive() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isGraphActive() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getGraphName() {&lt;br /&gt;        return getDisplayName();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getSummary() {&lt;br /&gt;        return "";&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;These four properties will be used to control how the action appears. &lt;code&gt;isFloatingBoxActive&lt;/code&gt; allows us to enable the floating box on the build summary page. &lt;code&gt;isGraphActive&lt;/code&gt; allows us to activate the graph inside the floating box. &lt;code&gt;getGraphName&lt;/code&gt; should return the title that will be displayed above the graph. And finally, &lt;code&gt;getSummary&lt;/code&gt; will control the summary text to display beside the build report icon on the build summary page.&lt;/p&gt;&lt;p&gt;Next we have some empty default place-holder jelly files.  These will be the jelly files that we can override in our actual reports:&lt;/p&gt;&lt;h3&gt;reportDetail.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;normalGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;largeGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;reportDetail.jelly&lt;/code&gt; page will be used for the main report detail page, the &lt;code&gt;normalGraph.jelly&lt;/code&gt; page will be used for the floating trend graph, while the &lt;ocde&gt;largeGraph.jelly&lt;/code&gt; page will be used for the enlarged graph. To hook these pages into the Hudson framework, we need the following jelly pages:&lt;/p&gt;&lt;h3&gt;index.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"&amp;gt;&lt;br /&gt;        &amp;lt;st:include it="${it.build}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;            &amp;lt;h1&amp;gt;${it.displayName}&amp;lt;/h1&amp;gt;&lt;br /&gt;            &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;                &amp;lt;h2&amp;gt;${it.graphName}&amp;lt;/h2&amp;gt;&lt;br /&gt;                &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;            &amp;lt;/j:if&amp;gt;&lt;br /&gt;            &amp;lt;st:include page="reportDetail.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;    &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This page will also include the trend graph if it is active&lt;/p&gt;&lt;h3&gt;floatingBox.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;        &amp;lt;div style="width:500px;"&amp;gt;&lt;br /&gt;            &amp;lt;div class="test-trend-caption"&amp;gt;&lt;br /&gt;                ${from.graphName}&lt;br /&gt;            &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;div style="text-align:right"&amp;gt;&lt;br /&gt;                &amp;lt;a href="enlargedGraph"&amp;gt;enlarge&amp;lt;/a&amp;gt;&lt;br /&gt;            &amp;lt;/div&amp;gt;&lt;br /&gt;        &amp;lt;/div&amp;gt;&lt;br /&gt;    &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;summary.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt"&amp;gt;&lt;br /&gt;    &amp;lt;t:summary icon="${it.iconFileName}"&amp;gt;&lt;br /&gt;        &amp;lt;a href="${it.urlName}"&amp;gt;${it.displayName}&amp;lt;/a&amp;gt;&lt;br /&gt;        ${it.summary}&lt;br /&gt;    &amp;lt;/t:summary&amp;gt;&lt;br /&gt;    &amp;lt;j:if test="${it.floatingBoxVisible}}"&amp;gt;&lt;br /&gt;        &amp;lt;div style="float:right"&amp;gt;&lt;br /&gt;            &amp;lt;st:include page="floatingBox.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;/div&amp;gt;&lt;br /&gt;    &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;p&gt;Note that the build main page does not support floating boxes, so we have to hack it in via the &lt;code&gt;summary.jelly&lt;/code&gt; page&lt;/p&gt;&lt;h3&gt;enlargedGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"&amp;gt;&lt;br /&gt;        &amp;lt;st:include it="${it.build}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;            &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;                &amp;lt;h1&amp;gt;${it.graphName}&amp;lt;/h1&amp;gt;&lt;br /&gt;                &amp;lt;st:include page="largeGraph.jelly"/&amp;gt;&lt;br /&gt;            &amp;lt;/j:if&amp;gt;&lt;br /&gt;        &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;    &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;AbstractProjectAction&lt;/h2&gt;&lt;p&gt;This is similar to AbstractBuildAction, however, we don't have to deal with the write-once setter problem.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.AbstractProject;&lt;br /&gt;import hudson.model.Actionable;&lt;br /&gt;&lt;br /&gt;abstract public class AbstractProjectAction&amp;lt;PROJECT extends AbstractProject&amp;lt;?, ?&amp;gt;&amp;gt; extends Actionable {&lt;br /&gt;    private final PROJECT project;&lt;br /&gt;&lt;br /&gt;    protected AbstractProjectAction(PROJECT project) {&lt;br /&gt;        this.project = project;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public PROJECT getProject() {&lt;br /&gt;        return project;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isFloatingBoxActive() {&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isGraphActive() {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getGraphName() {&lt;br /&gt;        return getDisplayName();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Again we have a set of jelly files, we'll repeat the same placeholders:&lt;/p&gt;&lt;h3&gt;reportDetail.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;normalGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;largeGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The other link in pages are similar, only changing from a reference for &lt;code&gt;${it.build}&lt;/code&gt; to &lt;code&gt;${it.project}&lt;/code&gt;&lt;/p&gt;&lt;h3&gt;index.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"&amp;gt;&lt;br /&gt;        &amp;lt;st:include it="${it.project}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;            &amp;lt;h1&amp;gt;${it.displayName}&amp;lt;/h1&amp;gt;&lt;br /&gt;            &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;                &amp;lt;h2&amp;gt;${it.graphName}&amp;lt;/h2&amp;gt;&lt;br /&gt;                &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;            &amp;lt;/j:if&amp;gt;&lt;br /&gt;            &amp;lt;st:include page="reportDetail.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;    &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;floatingBox.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:local="local"&amp;gt;&lt;br /&gt;    &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;        &amp;lt;div style="width:500px;"&amp;gt;&lt;br /&gt;            &amp;lt;div class="test-trend-caption"&amp;gt;&lt;br /&gt;                ${from.graphName}&lt;br /&gt;            &amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;st:include page="normalGraph.jelly"/&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;div style="text-align:right"&amp;gt;&lt;br /&gt;                &amp;lt;a href="enlargedGraph"&amp;gt;enlarge&amp;lt;/a&amp;gt;&lt;br /&gt;            &amp;lt;/div&amp;gt;&lt;br /&gt;        &amp;lt;/div&amp;gt;&lt;br /&gt;    &amp;lt;/j:if&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;/pre&gt;&lt;p&gt;The project page automatically includes the floating box, so  we don;t require the summary hack&lt;/p&gt;&lt;h3&gt;enlargedGraph.jelly&lt;/h3&gt;&lt;pre&gt;&amp;lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"&lt;br /&gt;         xmlns:t="/lib/hudson" xmlns:f="/lib/form"&amp;gt;&lt;br /&gt;    &amp;lt;l:layout xmlns:plugin="/hudson/plugins/javancss/tags" css="/plugin/javancss/css/style.css"&amp;gt;&lt;br /&gt;        &amp;lt;st:include it="${it.project}" page="sidepanel.jelly"/&amp;gt;&lt;br /&gt;        &amp;lt;l:main-panel&amp;gt;&lt;br /&gt;            &amp;lt;j:if test="${it.graphActive}"&amp;gt;&lt;br /&gt;                &amp;lt;h1&amp;gt;${it.graphName}&amp;lt;/h1&amp;gt;&lt;br /&gt;                &amp;lt;st:include page="largeGraph.jelly"/&amp;gt;&lt;br /&gt;            &amp;lt;/j:if&amp;gt;&lt;br /&gt;        &amp;lt;/l:main-panel&amp;gt;&lt;br /&gt;    &amp;lt;/l:layout&amp;gt;&lt;br /&gt;&amp;lt;/j:jelly&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Summary&lt;/h2&gt;&lt;p&gt;That's it for part 5, next time we will implement the javancss parser and get the results for a build. Part 7 will finish off the plugin with the reports based on these two base classes.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-482296725811034499?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/482296725811034499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=482296725811034499' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/482296725811034499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/482296725811034499'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/02/writing-hudson-plug-in-part-5-reporting.html' title='Writing a Hudson plug-in (Part 5 – Reporting)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4388534984242853537</id><published>2008-02-11T22:12:00.001Z</published><updated>2011-05-30T14:56:15.203+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plug-in (Part 4 – Abstract Publishers and MavenReporters)</title><content type='html'>&lt;p&gt;This is the fourth installment, which covers the two abstract classes that call the &lt;code&gt;Ghostwriter&lt;/code&gt; for us. When we implement our plugin we will extend from these two classes.  These classes perform the work of calling the Ghostwriter at the appropriate points in the build, the concrete classes that we will develop in Part 5, however have the responsibility of creating a configured Ghostwriter and providing it to the superclass for execution.&lt;/p&gt;&lt;h2&gt;AbstractPublisherImpl&lt;/h2&gt;&lt;p&gt;In a way, this is the simplest of the two classes:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.tasks.Publisher;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.Launcher;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractPublisherImpl extends Publisher {&lt;br /&gt;&lt;br /&gt;    protected abstract Ghostwriter newGhostwriter();&lt;br /&gt;&lt;br /&gt;    public boolean perform(AbstractBuild&lt;?, ?&gt; build, &lt;br /&gt;                           Launcher launcher, &lt;br /&gt;                           final BuildListener listener)&lt;br /&gt;            throws InterruptedException, IOException {&lt;br /&gt;        return BuildProxy.doPerform(newGhostwriter(), build, listener);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean prebuild(AbstractBuild&lt;?, ?&gt; build, &lt;br /&gt;                            BuildListener listener) {&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;Fairly simple, we just ask the BuildProxy to invoke the new &lt;code&gt;Ghostwriter&lt;/code&gt; created by the concrete class.&lt;/p&gt;&lt;h2&gt;AbstractMavenReporterImpl&lt;/h2&gt;&lt;p&gt;This class needs to do a bit more work. First I'll present the abstract methods and protected methods that can be overrided by implementing classes:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.maven.MavenReporter;&lt;br /&gt;import hudson.maven.MojoInfo;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.model.Result;&lt;br /&gt;import hudson.plugins.helpers.BuildProxy;&lt;br /&gt;import hudson.plugins.javancss.PluginImpl;&lt;br /&gt;import org.apache.maven.project.MavenProject;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public abstract class AbstractMavenReporterImpl extends MavenReporter {&lt;br /&gt;&lt;br /&gt;    protected abstract boolean isExecutingMojo(MojoInfo mojo);&lt;br /&gt;&lt;br /&gt;    protected abstract Ghostwriter newGhostwriter(MavenProject pom, &lt;br /&gt;                                                  MojoInfo mojo);&lt;br /&gt;&lt;br /&gt;    protected boolean autoconfPom(MavenBuildProxy build,&lt;br /&gt;                                  MavenProject pom,&lt;br /&gt;                                  MojoInfo mojo,&lt;br /&gt;                                  BuildListener listener) {&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected boolean isAutoconfMojo(MojoInfo mojo) {&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Maven project execution consists of invocations of various golas of the different mojos that are bound to the Maven lifecycle. Hudson inserts hooks that allow us to intercept mojos before they execute and after they have executed. Intercepting prior to execution can be useful to ensure that the plugin has been configured with the options we require (e.g. XML output enabled). Post execution is usually when we want to invoke our &lt;code&gt;Ghostwriter&lt;/code&gt;. If the implementing class wants to be able to tweak the mojo configuration prior to execution it needs to override &lt;code&gt;isAutoconfMojo&lt;/code&gt; in order to identify the mojo executions that need to be tweaked, and override &lt;code&gt;autoconfPom&lt;/code&gt; to do the actual configuration.&lt;/p&gt;&lt;p&gt;The implementing class needs to provide:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;a &lt;code&gt;isExecutingMojo&lt;/code&gt; method to identify execution of the mojo that this publisher reports on.&lt;/li&gt;&lt;li&gt;a &lt;code&gt;newGhostwriter&lt;/code&gt; method that constructs the &lt;code&gt;Ghostwriter&lt;/code&gt; which will do the work for us. The new &lt;code&gt;Ghostwriter&lt;/code&gt; can either be configured manually on the build page, and/or can be configured based on information in the pom.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The final thing that we need to decide is when to execute the publisher:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;public abstract class AbstractMavenReporterImpl extends MavenReporter {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt; &lt;br /&gt;    protected MojoExecutionReportingMode getExecutionMode() {&lt;br /&gt;        return MojoExecutionReportingMode.ONLY_REPORT_ON_SUCCESS;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    protected enum MojoExecutionReportingMode {&lt;br /&gt;        ONLY_REPORT_ON_SUCCESS {&lt;br /&gt;            Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                   MavenBuildProxy build,&lt;br /&gt;                                   BuildListener listener,&lt;br /&gt;                                   Throwable error) {&lt;br /&gt;                return error == null ? null : Boolean.TRUE;&lt;br /&gt;            }&lt;br /&gt;        },&lt;br /&gt;        ALWAYS_REPORT_STABLE {&lt;br /&gt;            Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                   MavenBuildProxy build,&lt;br /&gt;                                   BuildListener listener,&lt;br /&gt;                                   Throwable error) {&lt;br /&gt;                return null;&lt;br /&gt;            }},&lt;br /&gt;        REPORT_UNSTABLE_ON_ERROR {&lt;br /&gt;            Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                   MavenBuildProxy build,&lt;br /&gt;                                   BuildListener listener,&lt;br /&gt;                                   Throwable error) {&lt;br /&gt;                if (error != null) {&lt;br /&gt;                    listener.getLogger().println("[HUDSON] "&lt;br /&gt;                            + reporter.getDescriptor().getDisplayName()&lt;br /&gt;                            + " setting build to UNSTABLE");&lt;br /&gt;                    build.setResult(Result.UNSTABLE);&lt;br /&gt;                }&lt;br /&gt;                return null;&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;        abstract Boolean isOkToContinue(MavenReporter reporter,&lt;br /&gt;                                        MavenBuildProxy build,&lt;br /&gt;                                        BuildListener listener,&lt;br /&gt;                                        Throwable error);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We define three different execution modes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;ONLY_REPORT_ON_SUCCESS - this ensures that we only run the publisher if the mojo executed without error.&lt;/li&gt;&lt;li&gt;ALWAYS_REPORT_STABLE - runs the publisher even if the mojo had an execution error, never marks the build as failed or unstable (note that Maven will most likely mark the build as failed or unstable)&lt;/li&gt;&lt;li&gt;REPORT_UNSTABLE_ON_ERROR - runs the publisher even if the mojo had an execution error. In the event of a mojo execution error the build will be marked UNSTABLE&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All that's left is are the methods to tie these all together:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;public abstract class AbstractMavenReporterImpl extends MavenReporter {&lt;br /&gt;&lt;br /&gt;    ....&lt;br /&gt;&lt;br /&gt;    public boolean preExecute(MavenBuildProxy build,&lt;br /&gt;                              MavenProject pom,&lt;br /&gt;                              MojoInfo mojo,&lt;br /&gt;                              BuildListener listener)&lt;br /&gt;            throws InterruptedException, IOException {&lt;br /&gt;        return !isAutoconfMojo(mojo) &lt;br /&gt;                || autoconfPom(build, pom, mojo, listener);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean postExecute(MavenBuildProxy build,&lt;br /&gt;                               MavenProject pom,&lt;br /&gt;                               MojoInfo mojo,&lt;br /&gt;                               BuildListener listener,&lt;br /&gt;                               Throwable error)&lt;br /&gt;            throws InterruptedException, IOException {&lt;br /&gt;        if (!isExecutingMojo(mojo)) {&lt;br /&gt;            // not a mojo who's result we are interested in&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        final Boolean okToContinue = getExecutionMode()&lt;br /&gt;                .isOkToContinue(this, build, listener, error);&lt;br /&gt;        if (okToContinue != null) {&lt;br /&gt;            return okToContinue;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        build.registerAsProjectAction(this);&lt;br /&gt;&lt;br /&gt;        return BuildProxy.doPerform(newGhostwriter(pom, mojo), &lt;br /&gt;                                    build, &lt;br /&gt;                                    pom, &lt;br /&gt;                                    listener);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;preExecute&lt;/code&gt; method checks if this is an mojo execution that we want to tweak, executing the tweaks if necessary. The &lt;code&gt;postExecute&lt;/code&gt; method checks if this the the mojo we want to report on. Then it checks if there were execution errors, following the execution mode reported by &lt;code&gt;getExecutionMode&lt;/code&gt;. We register this &lt;code&gt;MavenReporter&lt;/code&gt; as a project action (thus signalling Hudson that it should call the &lt;code&gt;getProjectAction()&lt;/code&gt; method of our reporter. We will have this method return &lt;code&gt;null&lt;/code&gt; if we actually don't want a project action. This is safer than trying to be smart and only calling register if we want a project action!). Finally we invoke the &lt;code&gt;Ghostwriter&lt;/code&gt;!&lt;/p&gt;&lt;h2&gt;Summary&lt;/h2&gt;&lt;p&gt;Well that's it for Part 4. In the next installment, I will introduce a DRY set of classes for the reports that the publisher will generate&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4388534984242853537?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4388534984242853537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4388534984242853537' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4388534984242853537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4388534984242853537'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/02/writing-hudson-plug-in-part-4-abstract.html' title='Writing a Hudson plug-in (Part 4 – Abstract Publishers and MavenReporters)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-2521647890085234955</id><published>2008-01-31T22:44:00.001Z</published><updated>2011-05-30T14:56:15.203+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plug-in (Part 3 – Subcontracting for Publisher and MavenReporter)</title><content type='html'>&lt;p&gt;In this, the third installment, I will introduce some helper classes that we can use to  let Publisher and MavenReporter subcontract the job of creating the build reports. Firstly, we have the Ghostwriter interface.  An implementation of a Ghostwriter is the work-horse that writes the build report. Secondly, we have the BuildProxy class.  This class is a more general MavenBuildProxy that works for both Maven builds and non-Maven builds. In part 4 we will tie these helpers into the Hudson plugin framework with an AbstractMavenReporterImpl and AbstractPublisherImpl from which we can extend our plugin's MavenReporter and Publisher.&lt;/p&gt;&lt;p&gt;The classes presented here and in part 4 should be generic enough that most plugins can be implemented on the back of them, hence my reason for making them this generic.&lt;/p&gt;&lt;h2&gt;Ghostwriter&lt;/h2&gt;&lt;p&gt;A class or interface should be named after its function. PublisherDelegate, RealReporter, etc, just did not seem snappy enough, so I've picked Ghostwriter, as I think it encapsulates the idea of actually doing the writing without the consumer knowing who did the work!&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.FilePath;&lt;br /&gt;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;public interface Ghostwriter extends Serializable {&lt;br /&gt;    public static interface SlaveGhostwriter &lt;br /&gt;            extends Ghostwriter {&lt;br /&gt;        boolean performFromSlave(BuildProxy build, &lt;br /&gt;                                 BuildListener listener) &lt;br /&gt;                throws InterruptedException, IOException;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static interface MasterGhostwriter &lt;br /&gt;            extends Ghostwriter {&lt;br /&gt;        boolean performFromMaster(AbstractBuild&lt;?, ?&gt; build, &lt;br /&gt;                                  FilePath executionRoot, &lt;br /&gt;                                  BuildListener listener) &lt;br /&gt;                throws InterruptedException, IOException;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;This interface is really a marker interface for a class that implements at least one of &lt;code&gt;SlaveGhostwriter&lt;/code&gt; and &lt;code&gt;MasterGhostwriter&lt;/code&gt;.  The idea between these two child interfaces is that they encapsulate execution on the slave or the master.&lt;/p&gt;&lt;p&gt;Let's look at &lt;code&gt;Ghostwriter.MasterGhostwriter&lt;/code&gt; the method for performing our plugin's action receives three parameters:&lt;ul&gt;&lt;li&gt;The build that this plugin is being executed on;&lt;/li&gt;&lt;li&gt;The file path location of the execution of the build (Note: this is a &lt;code&gt;FilePath&lt;/code&gt; as the execution of the build is most likely remote from the master); and&lt;/li&gt;&lt;li&gt;The build listener where to allow us to add information to the build log&lt;/li&gt;&lt;/ul&gt;These are mostly the same parameters as &lt;code&gt;hudson.tasks.BuildStep&lt;/code&gt; provides in its perform method, however we have substituted a file path for a launcher. The logic here is that with the Maven project type, the root of the maven module needs to be passed back to the plugin, so we will need this file path information.  With the Free-style project type, the root easily and unambiguously available from the &lt;code&gt;build&lt;/code&gt; parameter. The exceptions that we declare are needed as this method could be being called from the slave via the remoting support built into Hudson.&lt;/p&gt;&lt;p&gt;If we now turn to &lt;code&gt;Ghostwriter.SlaveGhostwriter&lt;/code&gt;. In this case, the perform method takes two parameters&lt;ul&gt;&lt;li&gt;The build proxy, modelled after MavenBuildProxy, which contains the essential information required for our plugin; and &lt;/li&gt;&lt;li&gt;The build listener.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;The idea with both of these interfaces is that our plugin implementation does some work figuring out what our Ghostwriter is supposed to do.  If we are in a Maven project, we can determine some of the information from the pom.xml and the rest from Hudson. If we are in any other type of project, we determine all the information from Hudson. We capture that configuration in a Ghostwriter instance, and we send the Ghostwriter off to do our work. The Ghostwriter then goes off to the slave or the master as necessary and does it's job. We have separated our concerns as we have:&lt;ul&gt;&lt;li&gt;A publisher for determining the configuration for a freestyle project;&lt;/li&gt;&lt;li&gt;A maven reported for determine the configuration for a Maven2 project; and&lt;/li&gt;&lt;li&gt;A ghostwriter for getting the job done&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;Now it is time to look at our next helper class&lt;/p&gt;&lt;h2&gt;BuildProxy&lt;/h2&gt;&lt;p&gt;This is our workhorse class, it is designed to hide the different execution paths to calling the &lt;code&gt;Ghostwriter&lt;/code&gt; and ensuring that there are no differences (as far as the &lt;code&gt;Ghostwriter&lt;/code&gt; is concerned) between the Free-style project type and the Maven2 project type. As I said earlier, it is modelled on &lt;code&gt;MavenBuildProxy&lt;/code&gt;, so we have getters for the important directories on the Master that are needed on the Slave.  For example, if we need to copy a file to the artifacts directory of the master, the build directory of the master, or the project directory of the master.&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.FilePath;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.util.IOException2;&lt;br /&gt;import hudson.model.Action;&lt;br /&gt;import hudson.model.Result;&lt;br /&gt;import hudson.model.AbstractBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;&lt;br /&gt;import java.util.Calendar;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import org.apache.maven.project.MavenProject;&lt;br /&gt;&lt;br /&gt;public final class BuildProxy implements Serializable {&lt;br /&gt;    private final FilePath artifactsDir;&lt;br /&gt;    private final FilePath projectRootDir;&lt;br /&gt;    private final FilePath buildRootDir;&lt;br /&gt;    private final FilePath executionRootDir;&lt;br /&gt;    private final Calendar timestamp;&lt;br /&gt;&lt;br /&gt;    private BuildProxy(FilePath artifactsDir,&lt;br /&gt;                       FilePath projectRootDir,&lt;br /&gt;                       FilePath buildRootDir,&lt;br /&gt;                       FilePath executionRootDir,&lt;br /&gt;                       Calendar timestamp) {&lt;br /&gt;        this.artifactsDir = artifactsDir;&lt;br /&gt;        this.projectRootDir = projectRootDir;&lt;br /&gt;        this.buildRootDir = buildRootDir;&lt;br /&gt;        this.executionRootDir = executionRootDir;&lt;br /&gt;        this.timestamp = timestamp;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public FilePath getArtifactsDir() {&lt;br /&gt;        return artifactsDir;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public FilePath getBuildRootDir() {&lt;br /&gt;        return buildRootDir;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public FilePath getExecutionRootDir() {&lt;br /&gt;        return executionRootDir;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public FilePath getProjectRootDir() {&lt;br /&gt;        return projectRootDir;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Calendar getTimestamp() {&lt;br /&gt;        return timestamp;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;There are additional methods, but I want to outline the key function of &lt;code&gt;BuildProxy&lt;/code&gt; before introducing them. Also, the constructor is &lt;code&gt;private&lt;/code&gt; because we will use these additional methods it instantiate the BuildProxy for us.&lt;/p&gt;&lt;p&gt;In my experience, the primary information that a plugin needs to know (when executing on the slave) are the five pieces of information that are stored in &lt;code&gt;BuildProxy&lt;/code&gt;&lt;ul&gt;&lt;li&gt;The artifacts directory - the directory on the master where artifacts for this project are to be stored;&lt;/li&gt;&lt;li&gt;The build root directory - the directory on the master where build reports for this build are to be stored;&lt;/li&gt;&lt;li&gt;The project root directory - the directory on the master where project reports are to be stored;&lt;/li&gt;&lt;li&gt;The timestamp when the build started - for example to identify new artifacts produced during the build; and finally&lt;/li&gt;&lt;li&gt;The execution root directory - the directory either on the slave or the master from which the build was performed&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;Now, when our plugin is executing on the slave, it will need to pass back information to the real build. For example, if our plugin completes successfully, we will want to attach &lt;code&gt;Action&lt;/code&gt; objects to the &lt;code&gt;AbstractBuild&lt;/code&gt; in order to allow the user to see the results of our plugin on the build pages in Hudson. If there were problems when executing our plugin, we may want to fail the build, mark it as unstable, or stop any other plugins from executing. To handle these cases, we need to add some mutable properties to our &lt;code&gt;BuildProxy&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;...&lt;br /&gt;&lt;br /&gt;public final class BuildProxy implements Serializable {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    private final List&lt;Action&gt; actions = new ArrayList&lt;Action&gt;();&lt;br /&gt;    private Result result = null;&lt;br /&gt;    private boolean continueBuild = true;&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public List&lt;Action&gt; getActions() {&lt;br /&gt;        return actions;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Result getResult() {&lt;br /&gt;        return result;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setResult(Result result) {&lt;br /&gt;        this.result = result;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean isContinueBuild() {&lt;br /&gt;        return continueBuild;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setContinueBuild(boolean continueBuild) {&lt;br /&gt;        this.continueBuild = continueBuild;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;So now our &lt;code&gt;Ghostwriter.SlaveGhostwriter&lt;/code&gt; has the ability to:&lt;ul&gt;&lt;li&gt;Add actions to the build;&lt;/li&gt;&lt;li&gt;Modify the result of the build; and&lt;/li&gt;&lt;li&gt;Flag that the build should finish as early as possible.&lt;/li&gt;&lt;/ul&gt;At this point we are ready to introduce the static methods that will invoke the &lt;code&gt;Ghostwriter&lt;/code&gt; for our &lt;code&gt;Publisher&lt;/code&gt; or &lt;code&gt;MavenReporter&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;First off, we have a &lt;code&gt;doPerform&lt;/code&gt; method designed to be called from a &lt;code&gt;Publisher&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;...&lt;br /&gt;&lt;br /&gt;public final class BuildProxy {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public static boolean doPerform(Ghostwriter ghostwriter,&lt;br /&gt;                                    AbstractBuild&lt;?, ?&gt; build,&lt;br /&gt;                                    BuildListener listener)&lt;br /&gt;            throws IOException, InterruptedException {&lt;br /&gt;&lt;br /&gt;        // first, do we need to do anything on the slave&lt;br /&gt;&lt;br /&gt;        if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;&lt;br /&gt;            // construct the BuildProxy instance that we will use&lt;br /&gt;&lt;br /&gt;            BuildProxy buildProxy = new BuildProxy(&lt;br /&gt;                    new FilePath(build.getArtifactsDir()),&lt;br /&gt;                    new FilePath(build.getProject().getRootDir()),&lt;br /&gt;                    new FilePath(build.getRootDir()),&lt;br /&gt;                    build.getProject().getModuleRoot(),&lt;br /&gt;                    build.getTimestamp());&lt;br /&gt;&lt;br /&gt;            BuildProxyCallableHelper callableHelper = &lt;br /&gt;                new BuildProxyCallableHelper(buildProxy, ghostwriter, listener);&lt;br /&gt;&lt;br /&gt;            try {&lt;br /&gt;                buildProxy = buildProxy.getExecutionRootDir().act(callableHelper);&lt;br /&gt;&lt;br /&gt;                buildProxy.updateBuild(build);&lt;br /&gt;&lt;br /&gt;                // terminate the build if necessary&lt;br /&gt;                if (!buildProxy.isContinueBuild()) {&lt;br /&gt;                    return false;&lt;br /&gt;                }&lt;br /&gt;            } catch (Exception e) {&lt;br /&gt;                throw unwrapException(e, listener);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // finally, on to the master&lt;br /&gt;&lt;br /&gt;        final Ghostwriter.MasterGhostwriter masterGhostwriter = &lt;br /&gt;            Ghostwriter.MasterGhostwriter.class.cast(ghostwriter);&lt;br /&gt;&lt;br /&gt;        return masterGhostwriter == null&lt;br /&gt;                || masterGhostwriter.performFromMaster(build, &lt;br /&gt;                        build.getProject().getModuleRoot(), &lt;br /&gt;                        listener);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static RuntimeException unwrapException(Exception e,&lt;br /&gt;                                                    BuildListener listener)&lt;br /&gt;            throws IOException, InterruptedException {&lt;br /&gt;        if (e.getCause() instanceof IOException) {&lt;br /&gt;            throw new IOException2(e.getCause().getMessage(), e);&lt;br /&gt;        }&lt;br /&gt;        if (e.getCause() instanceof InterruptedException) {&lt;br /&gt;            e.getCause().printStackTrace(listener.getLogger());&lt;br /&gt;            throw new InterruptedException(e.getCause().getMessage());&lt;br /&gt;        }&lt;br /&gt;        if (e.getCause() instanceof RuntimeException) {&lt;br /&gt;            throw new RuntimeException(e.getCause());&lt;br /&gt;        }&lt;br /&gt;        // How on earth do we get this far down the branch&lt;br /&gt;        e.printStackTrace(listener.getLogger());&lt;br /&gt;        throw new RuntimeException("Unexpected exception", e);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void updateBuild(AbstractBuild&lt;?, ?&gt; build) {&lt;br /&gt;        // update the actions&lt;br /&gt;        build.getActions().addAll(actions);&lt;br /&gt;&lt;br /&gt;        // update the result&lt;br /&gt;        if (result != null &amp;&amp; result.isWorseThan(build.getResult())) {&lt;br /&gt;            build.setResult(result);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;The method is initially called from the master. It takes a &lt;code&gt;Ghostwriter&lt;/code&gt;, a build and a build listener.  First off it checks if the &lt;code&gt;Ghostwriter&lt;/code&gt; wants to execute some tasks on the slave, i.e. if it implements &lt;code&gt;Ghostwriter.SlaveGhostwriter&lt;/code&gt;, sending the &lt;code&gt;Ghostwriter&lt;/code&gt; over to the slave with a &lt;code&gt;BuildProxy&lt;/code&gt; and updating the real build when the &lt;code&gt;BuildProxy&lt;/code&gt; is returned. Finally, if the &lt;code&gt;Ghostwriter&lt;/code&gt; wants to execute some tasks on the master, i.e. if it implements &lt;code&gt;Ghostwriter.MasterGhostwriter&lt;/code&gt; we pass control over to the &lt;code&gt;Ghostwriter&lt;/code&gt; again. &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; slave execution is &lt;strong&gt;pass-by-value&lt;/strong&gt;.&lt;/em&gt;. Thus, if the &lt;code&gt;Ghostwriter&lt;/code&gt; is executed on both the slave and the master, changes made to the &lt;code&gt;Ghostwriter&lt;/code&gt; on the slave will not be picked up when executing on the master.&lt;/p&gt;&lt;p&gt;Now, for the method that will be called from a &lt;code&gt;MavenReporter&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;...&lt;br /&gt;&lt;br /&gt;public final class BuildProxy implements Serializable {&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    public static boolean doPerform(Ghostwriter ghostwriter,&lt;br /&gt;                                    MavenBuildProxy mavenBuildProxy,&lt;br /&gt;                                    MavenProject pom,&lt;br /&gt;                                    final BuildListener listener)&lt;br /&gt;            throws InterruptedException, IOException {&lt;br /&gt;&lt;br /&gt;        // first, construct the BuildProxy instance that we will use&lt;br /&gt;&lt;br /&gt;        BuildProxy buildProxy = new BuildProxy(&lt;br /&gt;                mavenBuildProxy.getArtifactsDir(),&lt;br /&gt;                mavenBuildProxy.getProjectRootDir(),&lt;br /&gt;                mavenBuildProxy.getRootDir(),&lt;br /&gt;                new FilePath(pom.getBasedir()),&lt;br /&gt;                mavenBuildProxy.getTimestamp());&lt;br /&gt;&lt;br /&gt;        // do we need to do anything on the slave&lt;br /&gt;&lt;br /&gt;        if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;            final Ghostwriter.SlaveGhostwriter slaveGhostwriter = &lt;br /&gt;                (Ghostwriter.SlaveGhostwriter) ghostwriter;&lt;br /&gt;&lt;br /&gt;            // terminate the build if necessary&lt;br /&gt;            if (!slaveGhostwriter.performFromSlave(buildProxy, listener)) {&lt;br /&gt;                return false;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // finally, on to the master&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            return mavenBuildProxy.execute(&lt;br /&gt;                new BuildProxyCallableHelper(buildProxy, &lt;br /&gt;                                             ghostwriter, &lt;br /&gt;                                             listener));&lt;br /&gt;        } catch (Exception e) {&lt;br /&gt;            throw unwrapException(e, listener);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;This method is initially called from the slave. It takes the &lt;code&gt;Ghostwriter&lt;/code&gt;, the &lt;code&gt;MavenBuildProxy&lt;/code&gt;, the pom and the build listener. We start off by creating the &lt;code&gt;BuildProxy&lt;/code&gt; from the &lt;code&gt;MavenBuildProxy&lt;/code&gt; and the pom, then we invoke the &lt;code&gt;Ghostwriter&lt;/code&gt; if necessary (i.e. if it executes on the slave). Finally, we send the &lt;code&gt;BuildProxy&lt;/code&gt; and the &lt;code&gt;Ghostwriter&lt;/code&gt; over to the master in order to add any required build actions and, if necessary execute on the master.&lt;/p&gt;&lt;p&gt;Both of these two static helper methods make use of a &lt;code&gt;Callable&lt;/code&gt; that is used by Hudson's remoting support to send tasks between the master and the slave:&lt;/p&gt;&lt;pre&gt;package hudson.plugins.helpers;&lt;br /&gt;&lt;br /&gt;import hudson.remoting.Callable;&lt;br /&gt;import hudson.maven.MavenBuildProxy;&lt;br /&gt;import hudson.maven.MavenBuild;&lt;br /&gt;import hudson.model.BuildListener;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;class BuildProxyCallableHelper implements Callable&lt;BuildProxy, Exception&gt;,&lt;br /&gt;        MavenBuildProxy.BuildCallable&lt;Boolean, Exception&gt; {&lt;br /&gt;&lt;br /&gt;    private final BuildProxy buildProxy;&lt;br /&gt;    private final Ghostwriter ghostwriter;&lt;br /&gt;    private final BuildListener listener;&lt;br /&gt;&lt;br /&gt;    BuildProxyCallableHelper(BuildProxy buildProxy,&lt;br /&gt;                             Ghostwriter ghostwriter,&lt;br /&gt;                             BuildListener listener) {&lt;br /&gt;        this.buildProxy = buildProxy;&lt;br /&gt;        this.ghostwriter = ghostwriter;&lt;br /&gt;        this.listener = listener;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Boolean call(MavenBuild mavenBuild) throws Exception {&lt;br /&gt;        buildProxy.updateBuild(mavenBuild);&lt;br /&gt;        if (ghostwriter instanceof Ghostwriter.MasterGhostwriter) {&lt;br /&gt;            final Ghostwriter.MasterGhostwriter masterBuildStep =&lt;br /&gt;                    (Ghostwriter.MasterGhostwriter) ghostwriter;&lt;br /&gt;            return masterBuildStep.performFromMaster(&lt;br /&gt;                    mavenBuild, &lt;br /&gt;                    buildProxy.getExecutionRootDir(), &lt;br /&gt;                    listener);&lt;br /&gt;        }&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public BuildProxy call() throws Exception {&lt;br /&gt;        if (ghostwriter instanceof Ghostwriter.SlaveGhostwriter) {&lt;br /&gt;            final Ghostwriter.SlaveGhostwriter slaveBuildStep =&lt;br /&gt;                    (Ghostwriter.SlaveGhostwriter) ghostwriter;&lt;br /&gt;            try {&lt;br /&gt;                buildProxy.setContinueBuild(&lt;br /&gt;                        slaveBuildStep.performFromSlave(buildProxy, listener));&lt;br /&gt;                return buildProxy;&lt;br /&gt;            } catch (IOException e) {&lt;br /&gt;                throw new Exception(e);&lt;br /&gt;            } catch (InterruptedException e) {&lt;br /&gt;                throw new Exception(e);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return buildProxy;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;Ok, that's enough for now.  The full source code (including javadoc comments) for these classes is available in Hudson's CVS server underneath the /hudson/hudson/plugins/javancss/ directory.  The next part of this tutorial will demonstrate how to use these methods by introducing an &lt;code&gt;AbstractMavenReporterImpl&lt;/code&gt; and an &lt;code&gt;AbstractPublisherImpl&lt;/code&gt; from which we can inherit our publisher and maven reporter.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-2521647890085234955?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/2521647890085234955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=2521647890085234955' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2521647890085234955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2521647890085234955'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/01/writing-hudson-plug-in-part-3.html' title='Writing a Hudson plug-in (Part 3 – Subcontracting for Publisher and MavenReporter)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1114641844849767509</id><published>2008-01-22T22:53:00.000Z</published><updated>2011-05-30T14:56:15.204+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plug-in (Part 2 – Understanding m2 and freestyle projects)</title><content type='html'>&lt;p&gt;Before we proceed with developing the plugin, there is an important concept to understand: How the different project types invoke their plugins.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Currently, one of the biggest source of confusion for end users of Hudson is that most of the plugins do not work with the "m2 project type".  This is because there are essentially two completely different implementations of plugins.&lt;/p&gt;&lt;h3&gt;"Normal" Plugins&lt;/h3&gt;&lt;p&gt;"Normal" plugins work with all project types except the "m2 project type".  Typically they will extend &lt;code&gt;Publisher&lt;/code&gt;. The model of execution is that the Publisher is invoked on the Hudson Master and can invoke actions remotely on the slave.&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_9vm8dyukeD0/R5Z1syeIp3I/AAAAAAAAAAk/WizyCCwXl04/s1600-h/untitled-freestyle.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_9vm8dyukeD0/R5Z1syeIp3I/AAAAAAAAAAk/WizyCCwXl04/s320/untitled-freestyle.gif" alt="" id="BLOGGER_PHOTO_ID_5158439835522344818" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The plugin author must be mindfull that they do not overload the master with excessive work, either by minimising the amount of work to be performed, or by sending work to the slave for execution. Best performance will be achieved if the heavy lifting is performed on the slave and then finally the results are sent to the master.  Multiple trips back and forth between the master and the slave will not be performant.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;"Maven" Plugins&lt;/h3&gt;&lt;p&gt;"Maven" plugins only work with the "m2 project type".  They must extend &lt;code&gt;MavenReporter&lt;/code&gt;. Contrary to the "Normal" plugins, they are executed on the Slave.  This is because they are invoked as call-backs from the Maven Embedded that is running on the slave. If the MavenReporter is able to invoke actions remotely on the Master.&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_9vm8dyukeD0/R5Z3nyeIp4I/AAAAAAAAAAs/ksUpF_K7QKw/s1600-h/untitled-m2.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_9vm8dyukeD0/R5Z3nyeIp4I/AAAAAAAAAAs/ksUpF_K7QKw/s320/untitled-m2.gif" alt="" id="BLOGGER_PHOTO_ID_5158441948646254466" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The plugin author does not have to be as mindful about overloading the master, as by default, they are executing on the slave, however, the side effect is that the plugin does not have full access to all the Hudson objects, and must usually send work to the master, e.g. to add an Action to a build, it is necessary to serialize the Action, send it to the master along with a Callable that then takes the deserialized Action and adds it to the real Build. (Note: Adding an action is such a common task that BuildProxy provides convenience methods to do this for us)&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Don't Repeat Yourself&lt;/h3&gt;&lt;p&gt;The result of this split is that it can be difficult to keep the DRY principles when developing Hudson plugins. Looking at what all plugins do, though, there is a standard pattern for most Publishing plugins:&lt;ul&gt;&lt;li&gt;Determine where the information to be published is to be found&lt;/li&gt;&lt;li&gt;Process/extract the information on the slave&lt;/li&gt;&lt;li&gt;Send the persistent result of the build to the master&lt;/li&gt;&lt;li&gt;Finalize processing of the information on the master&lt;/li&gt;&lt;/ul&gt;Strictly speaking only one of the last two steps is required, however, in most cases there is a trade-off between tasks that are more performant when executed on the slave and tasks that &lt;em&gt;must&lt;/em&gt; be performed on the master as either the required information is not available on the slave or the activity will affect the object instances on the master as an artifact of the serialization call-by-value that occurs on the slave.&lt;/p&gt;&lt;p&gt;Thus to keep the DRY principles we need to:&lt;ul&gt;&lt;li&gt;Publisher or MavenReporter constructs a Configuration object.&lt;/li&gt;&lt;li&gt;If the Configuration requires tasks to execute on the slave, ensure that it is sent to the slave and execute those tasks on the slave.&lt;/li&gt;&lt;li&gt;If the Configuration requires tasks to execute on the master, send it (if necessary back) to the master and execute those tasks on the master.&lt;/li&gt;&lt;/ul&gt;There are other issues with respect to Actions, i.e. Actions for a "m2 project type" must extend a specific subclass.  However, this is not a problem, as there is nothing to stop us just having all the actions extend this type, the non-"m2 project type" usage will just ignore that it's extending the subclass.&lt;/p&gt;&lt;h3&gt;What's next&lt;/h3&gt;&lt;p&gt;In part 3, I will present a couple of wrapper classes that we can use to ensure that the DRY principle gives us plugin execution in one place. Part 4 will tie these wrapper classes into the Hudson plugin framework, giving us a MavenReporter and a Publisher that both use common code to deliver the plugin functionality. Part 5 will add aggregated reporting for m2 project. Part 6 will add simple trend graphs.  Part 7 will close out by actually parsing the JavaNCSS files and implementing our plugin!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1114641844849767509?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1114641844849767509/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1114641844849767509' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1114641844849767509'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1114641844849767509'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/01/writing-hudson-plug-in-part-2.html' title='Writing a Hudson plug-in (Part 2 – Understanding m2 and freestyle projects)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_9vm8dyukeD0/R5Z1syeIp3I/AAAAAAAAAAk/WizyCCwXl04/s72-c/untitled-freestyle.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-3190852113949723580</id><published>2008-01-13T23:24:00.000Z</published><updated>2011-05-30T14:56:15.204+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Writing a Hudson plug-in (Part 1 – Preparation)</title><content type='html'>&lt;h1&gt;Writing a Hudson plug-in (Part 1 – Preparation)&lt;/h1&gt;&lt;p&gt;This is the first part of a tutorial on writing a Hudson plug-in. The aim of this tutorial is to develop a Publisher for JavaNCSS. This publisher will work with both Hudson Freestyle projects and Hudson Maven2 projects. The publisher will also include a trend graph.&lt;/p&gt;&lt;p&gt;Before we can start plug-in development, we need to have a set of working projects to throw at our plugin. Ideally these projects should be simple and quick to build, but the most essential thing is that they build and run the tool that we want to publish the results of! We need a minimum of three projects:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Ant-based project&lt;/li&gt;&lt;li&gt;Maven2-based single module project&lt;/li&gt;&lt;li&gt;Maven2-based multiple module project&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So we will develop these three basic projects, get them running the JavaNCSS tool, and then ensure they build in Hudson.&lt;/p&gt;&lt;h2&gt;The Ant-based project&lt;/h2&gt;&lt;p&gt;We’ll start with the Ant based project. First we’ll need a directory structure:&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   src/&lt;br /&gt;       main/&lt;br /&gt;       test/&lt;br /&gt;   lib/&lt;br /&gt;   tools/&lt;br /&gt;       javancss/&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Next we need to download the library dependencies to the lib folder (for example junit.jar) and the custom ant tasks to the appropriate sub-folder of tools.&lt;/p&gt;&lt;p&gt;You can get JUnit.jar from &lt;a href="http://www.junit.org/"&gt;http://www.junit.org&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The JavaNCSS jars are available at &lt;a href="http://www.kclee.de/clemens/java/javancss/"&gt;http://www.kclee.de/clemens/java/javancss/&lt;/a&gt; (Note this is the .de domain, not the .com domain). JavaNCSS comes as a zip file, so we’ll just extract the three jar files that we need: &lt;code&gt;ccl.jar&lt;/code&gt;, &lt;code&gt;javancss.jar&lt;/code&gt; and &lt;code&gt;jhbasic.jar&lt;/code&gt;&lt;/p&gt;&lt;p&gt;At this point we should have the following&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   src/&lt;br /&gt;       main/&lt;br /&gt;       test/&lt;br /&gt;   lib/&lt;br /&gt;       junit.jar&lt;br /&gt;   tools/&lt;br /&gt;       javancss/&lt;br /&gt;           ccl.jar&lt;br /&gt;           javancss.jar&lt;br /&gt;           jhbasic.jar&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Next we need a build file, and some sample java files to build. Here is a sample build.xml that will do most of what we want:&lt;/p&gt;&lt;pre&gt;&amp;lt;project name="SimpleAntProject" basedir="." default="dist"&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;path id="javancss.ant.task"&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${basedir}/tools/javancss/ccl.jar"/&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${basedir}/tools/javancss/javancss.jar"/&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${basedir}/tools/javancss/jhbasic.jar"&lt;br /&gt;   &amp;lt;/path&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;taskdef name="javancss"&lt;br /&gt;           classname="javancss.JavancssAntTask"&lt;br /&gt;           classpathref="javancss.ant.task"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;property name="src.dir" value="src"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="java.dir" value="${src.dir}/main"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="lib.dir" value="lib"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="docs.dir" value="docs"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="src.junit" value="${src.dir}/test"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="manifest" value="${src.dir}/etc/manifest"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="resource.dir" value="${src.dir}/resources"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.dir" value="build"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="dist.dir" value="dist"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;property name="build.classes" value="${build.dir}/classes"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.lib" value="${build.dir}/lib"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.javadocs" value="${build.dir}/javadocs"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.tests" value="${build.dir}/testcases"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.junit.reports" location="${build.tests}/reports"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="manifest.tmp" value="${build.dir}/optional.manifest"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- the absolute path --&amp;gt;&lt;br /&gt;   &amp;lt;property name="build.tests.value" location="${build.tests}"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;property name="debug" value="true"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="deprecation" value="false"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="optimize" value="true"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="javac.target" value="1.5"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="javac.source" value="1.5"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="junit.fork" value="false"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="junit.filtertrace" value="off"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="junit.summary" value="no"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="test.haltonfailure" value="false"/&amp;gt;&lt;br /&gt;   &amp;lt;property name="junit.forkmode" value="once"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;path id="classpath"&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${lib.dir}"/&amp;gt;&lt;br /&gt;   &amp;lt;/path&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;path id="tests-classpath"&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${build.classes}"/&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${build.tests}"/&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${src.junit}"/&amp;gt;&lt;br /&gt;       &amp;lt;pathelement location="${lib.dir}/junit.jar"/&amp;gt;&lt;br /&gt;       &amp;lt;path refid="classpath"/&amp;gt;&lt;br /&gt;   &amp;lt;/path&amp;gt;&lt;br /&gt;   &amp;lt;!-- turn this path into a string which is passed to the tests --&amp;gt;&lt;br /&gt;   &amp;lt;property name="tests-classpath.value" refid="tests-classpath"/&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="clean" description="Delete all generated files"&amp;gt;&lt;br /&gt;       &amp;lt;delete dir="${build.dir}" failonerror="false"/&amp;gt;&lt;br /&gt;       &amp;lt;delete dir="${dist.dir}" failonerror="false"/&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="compile" description="Compiles ${ant.project.name}"&amp;gt;&lt;br /&gt;       &amp;lt;mkdir dir="${build.classes}"/&amp;gt;&lt;br /&gt;       &amp;lt;javac srcdir="${java.dir}"&lt;br /&gt;               destdir="${build.classes}"&lt;br /&gt;               debug="${debug}"&lt;br /&gt;               target="${javac.target}"&lt;br /&gt;               source="${javac.source}"&lt;br /&gt;               deprecation="${deprecation}"&amp;gt;&lt;br /&gt;           &amp;lt;classpath refid="classpath"&amp;gt;&lt;br /&gt;       &amp;lt;/javac&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="compile-tests" depends="compile"&amp;gt;&lt;br /&gt;       &amp;lt;mkdir dir="${build.tests}"&amp;gt;&amp;lt;/mkdir&amp;gt;&lt;br /&gt;       &amp;lt;javac srcdir="${src.junit}"&lt;br /&gt;               destdir="${build.tests}"&lt;br /&gt;               debug="${debug}"&lt;br /&gt;               target="${javac.target}"&lt;br /&gt;               source="${javac.source}"&lt;br /&gt;               deprecation="${deprecation}"&amp;gt;&lt;br /&gt;           &amp;lt;classpath refid="tests-classpath"/&amp;gt;&lt;br /&gt;       &amp;lt;/javac&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="test" depends="compile-tests"&amp;gt;&lt;br /&gt;       &amp;lt;mkdir dir="${build.junit.reports}"/&amp;gt;&lt;br /&gt;       &amp;lt;junit&amp;gt;&lt;br /&gt;           &amp;lt;classpath refid="tests-classpath"/&amp;gt;&lt;br /&gt;           &amp;lt;formatter type="brief" usefile="false"/&amp;gt;&lt;br /&gt;           &amp;lt;batchtest fork="yes"&lt;br /&gt;                   todir="${build.junit.reports}"&lt;br /&gt;                   haltonerror="true"&lt;br /&gt;                   haltonfailure="true"&amp;gt;&lt;br /&gt;               &amp;lt;fileset dir="${src.junit}"&amp;gt;&amp;lt;/fileset&amp;gt;&lt;br /&gt;               &amp;lt;include name="**/*Test*.java"&amp;gt;&amp;lt;/include&amp;gt;&lt;br /&gt;               &amp;lt;exclude name="**/AllTests.java"&amp;gt;&amp;lt;/exclude&amp;gt;&lt;br /&gt;           &amp;lt;/batchtest&amp;gt;&lt;br /&gt;       &amp;lt;/junit&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="dist" description="Create the distribution" depends="compile, test, javancss"&amp;gt;&lt;br /&gt;       &amp;lt;mkdir dir="${dist.dir}"/&amp;gt;&lt;br /&gt;       &amp;lt;jar destfile="${dist.dir}/${ant.project.name}.jar" basedir="${build.classes}"/&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;target name="javancss"&amp;gt;&lt;br /&gt;       &amp;lt;javancss srcdir="${java.dir}"&lt;br /&gt;               generatereport="true"&lt;br /&gt;               outputfile="${build.dir}/javancss-report.xml"&lt;br /&gt;               format="xml"/&amp;gt;&lt;br /&gt;   &amp;lt;/target&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We can then write some simple java classes and test our build. We’ll start with a simple HelloWorld.java&lt;/p&gt;&lt;pre&gt;package com.onedash.hello;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* A basic hello world application.&lt;br /&gt;*&lt;br /&gt;* @author Stephen Connolly&lt;br /&gt;*/&lt;br /&gt;public class Hello {&lt;br /&gt;   public String sayHello(String name) {&lt;br /&gt;       return "Hello " + name;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   public static void main(String[] args) {&lt;br /&gt;       Hello instance = new Hello();&lt;br /&gt;       if (args.length == 1) {&lt;br /&gt;           System.out.println(instance.sayHello(args[0]));&lt;br /&gt;       } else {&lt;br /&gt;           System.out.println(instance.sayHello("world"));&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;At this point we should have the following project structure:&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   build.xml&lt;br /&gt;   src/&lt;br /&gt;       main/&lt;br /&gt;           com/&lt;br /&gt;               onedash/&lt;br /&gt;                   hello/&lt;br /&gt;                       Hello.java&lt;br /&gt;       test/&lt;br /&gt;   lib/&lt;br /&gt;       junit.jar&lt;br /&gt;   tools/&lt;br /&gt;       javancss/&lt;br /&gt;           ccl.jar&lt;br /&gt;           javancss.jar&lt;br /&gt;           jhbasic.jar&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And running ANT should give output like:&lt;/p&gt;&lt;pre&gt;C:\local\project&gt;ant clean dist&lt;br /&gt;Buildfile: build.xml&lt;br /&gt;&lt;br /&gt;clean:&lt;br /&gt;[delete] Deleting directory C:\local\project\build&lt;br /&gt;[delete] Deleting directory C:\local\project\dist&lt;br /&gt;&lt;br /&gt;compile:&lt;br /&gt; [mkdir] Created dir: C:\local\project\build\classes&lt;br /&gt; [javac] Compiling 5 source files to C:\local\project\build\classes&lt;br /&gt;&lt;br /&gt;compile-tests:&lt;br /&gt; [mkdir] Created dir: C:\local\project\build\testcases&lt;br /&gt; [javac] Compiling 1 source file to C:\local\project\build\testcases&lt;br /&gt;&lt;br /&gt;test:&lt;br /&gt; [mkdir] Created dir: C:\local\project\build\testcases\reports&lt;br /&gt;&lt;br /&gt;javancss:&lt;br /&gt;[javancss] Generating report&lt;br /&gt;&lt;br /&gt;dist:&lt;br /&gt; [mkdir] Created dir: C:\local\project\dist&lt;br /&gt;   [jar] Building jar: C:\local\project\dist\SimpleAntProject.jar&lt;br /&gt;&lt;br /&gt;BUILD SUCCESSFUL&lt;br /&gt;Total time: 3 seconds&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And there should be a javancss-report.xml file in the build directory. That should be enough for an ANT project. Some extra finesse would be to add some more source code so we can examine the structure of the xml report.&lt;/p&gt;&lt;h2&gt;The Maven2 based single module project&lt;/h2&gt;&lt;p&gt;First we’ll need a directory structure:&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   src/&lt;br /&gt;       main/&lt;br /&gt;           java/&lt;br /&gt;       test/&lt;br /&gt;           java/&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Next we need a pom.xml to describe the project for Maven2. Here is a simple pom.xml:&lt;/p&gt;&lt;pre&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0" xsi="http://www.w3.org/2001/XMLSchema-instance"&amp;gt;&lt;br /&gt;       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&amp;gt;&lt;br /&gt;   &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- artifact identification --&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;com.onedash.hudson&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;SimpleM2Project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;SimpleM2Project&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;packaging&amp;gt;jar&amp;lt;/packaging&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- dependency specification --&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;dependencies&amp;gt;&lt;br /&gt;       &amp;lt;dependency&amp;gt;&lt;br /&gt;           &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;           &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;           &amp;lt;version&amp;gt;4.4&amp;lt;/version&amp;gt;&lt;br /&gt;           &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;       &amp;lt;/dependency&amp;gt;&lt;br /&gt;   &amp;lt;/dependencies&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- build process and details specification --&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;build&amp;gt;&lt;br /&gt;       &amp;lt;defaultGoal&amp;gt;package&amp;lt;/defaultGoal&amp;gt;&lt;br /&gt;       &amp;lt;plugins&amp;gt;&lt;br /&gt;           &amp;lt;plugin&amp;gt;&lt;br /&gt;               &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;&lt;br /&gt;               &amp;lt;artifactId&amp;gt;javancss-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;               &amp;lt;executions&amp;gt;&lt;br /&gt;                   &amp;lt;execution&amp;gt;&lt;br /&gt;                       &amp;lt;phase&amp;gt;process-sources&amp;lt;/phase&amp;gt;&lt;br /&gt;                       &amp;lt;goals&amp;gt;&lt;br /&gt;                           &amp;lt;goal&amp;gt;report&amp;lt;/goal&amp;gt;&lt;br /&gt;                       &amp;lt;/goals&amp;gt;&lt;br /&gt;                   &amp;lt;/execution&amp;gt;&lt;br /&gt;               &amp;lt;/executions&amp;gt;&lt;br /&gt;           &amp;lt;/plugin&amp;gt;&lt;br /&gt;       &amp;lt;/plugins&amp;gt;&lt;br /&gt;   &amp;lt;/build&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We can reuse the Hello.java from the Ant project, so at this point we should have the following directory structure:&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   pom.xml&lt;br /&gt;   src/&lt;br /&gt;       main/&lt;br /&gt;           java/&lt;br /&gt;               com/&lt;br /&gt;                   onedash/&lt;br /&gt;                       hello/&lt;br /&gt;                           Hello.java&lt;br /&gt;       test/&lt;br /&gt;           java/&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And running maven should give the following output:&lt;/p&gt;&lt;pre&gt;C:\local\project&gt;mvn clean package&lt;br /&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Building SimpleM2Project&lt;br /&gt;[INFO]    task-segment: [clean, package]&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] [clean:clean]&lt;br /&gt;[INFO] Deleting directory C:\local\project\target&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\test-classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\site&lt;br /&gt;[INFO] [javancss:report {execution: default}]&lt;br /&gt;[WARNING] Unable to locate Source XRef to link to - DISABLED&lt;br /&gt;[INFO] [resources:resources]&lt;br /&gt;[INFO] Using default encoding to copy filtered resources.&lt;br /&gt;[INFO] [compiler:compile]&lt;br /&gt;[INFO] Compiling 1 source files to C:\local\project\target\classes&lt;br /&gt;[INFO] [resources:testResources]&lt;br /&gt;[INFO] Using default encoding to copy filtered resources.&lt;br /&gt;[INFO] [compiler:testCompile]&lt;br /&gt;[INFO] Nothing to compile - all classes are up to date&lt;br /&gt;[INFO] [surefire:test]&lt;br /&gt;[INFO] No tests to run.&lt;br /&gt;[INFO] [jar:jar]&lt;br /&gt;[INFO] Building jar: C:\local\project\target\SimpleM2Project-1.0-SNAPSHOT.jar&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] BUILD SUCCESSFUL&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Total time: 5 seconds&lt;br /&gt;[INFO] Finished at: Sun Jan 13 22:06:20 GMT 2008&lt;br /&gt;[INFO] Final Memory: 5M/10M&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And there should be a javancss-raw-report.xml file in the target directory.&lt;/p&gt;&lt;h2&gt;The Maven2 based multi-module project&lt;/h2&gt;&lt;p&gt;We can generate this project in its simplest form just by adding the simple project as a child of a project with &lt;code&gt;&amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;&lt;/code&gt;. We’ll start off with the following directory structure:&lt;/p&gt;&lt;pre&gt;project/&lt;br /&gt;   SimpleM2Project&lt;br /&gt;       pom.xml&lt;br /&gt;       src/&lt;br /&gt;           main/&lt;br /&gt;               java/&lt;br /&gt;                   com/&lt;br /&gt;                       onedash/&lt;br /&gt;                           hello/&lt;br /&gt;                               Hello.java&lt;br /&gt;           test/&lt;br /&gt;               java/&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Where the SimpleM2Project subtree is a copy of the previous project. All we now need is a pom.xml to nest this project in:&lt;/p&gt;&lt;pre&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0"&lt;br /&gt;       xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;       schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&amp;gt;&lt;br /&gt;   &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- artifact identification --&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;com.onedash.mvn.hudson&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;MultiM2Project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;MultiM2Project&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;!-- child modules --&amp;gt;&lt;br /&gt;   &amp;lt;modules&amp;gt;&lt;br /&gt;       &amp;lt;module&amp;gt;SimpleM2Project&amp;lt;/module&amp;gt;&lt;br /&gt;   &amp;lt;/modules&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And running maven should give the following output:&lt;/p&gt;&lt;pre&gt;C:\local\project&gt;mvn clean package&lt;br /&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO] Reactor build order:&lt;br /&gt;[INFO]   SimpleM2Project&lt;br /&gt;[INFO]   MultiM2Project&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Building SimpleM2Project&lt;br /&gt;[INFO]    task-segment: [clean, package]&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] [clean:clean]&lt;br /&gt;[INFO] Deleting directory C:\local\project\SimpleM2Project\target&lt;br /&gt;[INFO] Deleting directory C:\local\project\SimpleM2Project\target\classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\SimpleM2Project\target\test-classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\SimpleM2Project\target\site&lt;br /&gt;[INFO] [javancss:report {execution: default}]&lt;br /&gt;[WARNING] Unable to locate Source XRef to link to - DISABLED&lt;br /&gt;[INFO] [resources:resources]&lt;br /&gt;[INFO] Using default encoding to copy filtered resources.&lt;br /&gt;[INFO] [compiler:compile]&lt;br /&gt;[INFO] Compiling 1 source files to C:\local\project\SimpleM2Project\target\classes&lt;br /&gt;[INFO] [resources:testResources]&lt;br /&gt;[INFO] Using default encoding to copy filtered resources.&lt;br /&gt;[INFO] [compiler:testCompile]&lt;br /&gt;[INFO] No sources to compile&lt;br /&gt;[INFO] [surefire:test]&lt;br /&gt;[INFO] No tests to run.&lt;br /&gt;[INFO] [jar:jar]&lt;br /&gt;[INFO] Building jar: C:\local\project\SimpleM2Project\target\SimpleM2Project-1.0-SNAPSHOT.jar&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Building MultiM2Project&lt;br /&gt;[INFO]    task-segment: [clean, package]&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] [clean:clean]&lt;br /&gt;[INFO] Deleting directory C:\local\project\target&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\test-classes&lt;br /&gt;[INFO] Deleting directory C:\local\project\target\site&lt;br /&gt;[INFO] [site:attach-descriptor]&lt;br /&gt;[INFO]&lt;br /&gt;[INFO]&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Reactor Summary:&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] SimpleM2Project ....................................... SUCCESS [7.240s]&lt;br /&gt;[INFO] MultiM2Project ........................................ SUCCESS [2.754s]&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] BUILD SUCCESSFUL&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Total time: 10 seconds&lt;br /&gt;[INFO] Finished at: Sun Jan 13 22:16:37 GMT 2008&lt;br /&gt;[INFO] Final Memory: 7M/15M&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;And there should be a javancss-raw-report.xml file in the SimpleM2Project/target directory.&lt;/p&gt;&lt;h2&gt;The Hudson setup&lt;/h2&gt;&lt;p&gt;We’ll start by running mvn hpi:create to create a base to work from:&lt;/p&gt;&lt;pre&gt;C:\local&gt;mvn hpi:create&lt;br /&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO] Searching repository for plugin with prefix: 'hpi'.&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Building Maven Default Project&lt;br /&gt;[INFO]    task-segment: [hpi:create] (aggregator-style)&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;...&lt;pre&gt;[INFO] [hpi:create]&lt;br /&gt;Enter the groupId of your plugin: &lt;b&gt;org.jvnet.hudson.plugins&lt;/b&gt;&lt;br /&gt;[INFO] Defaulting package to group ID: org.jvnet.hudson.plugins&lt;br /&gt;Enter the artifactId of your plugin: &lt;b&gt;javancss&lt;/b&gt;&lt;br /&gt;&lt;/pre&gt;...&lt;pre&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] BUILD SUCCESSFUL&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Total time: 2 minutes 44 seconds&lt;br /&gt;[INFO] Finished at: Sun Jan 13 22:34:58 GMT 2008&lt;br /&gt;[INFO] Final Memory: 8M/15M&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The result should be a sub-directory called javancss with a pom.xml file and a basic Hudson plugin starting point. We’ll edit the pom.xml to update the version of Hudson to the latest (at the time of writing 1.170) and launch a live development instance to set our projects up in.&lt;/p&gt;&lt;p&gt;Open up the pom.xml file, and change the &lt;code&gt;&amp;lt;version&amp;gt;1.153&amp;lt;/version&amp;gt;&lt;/code&gt; tags for the hudson-core and hudson-war dependencies to 1.170.&lt;/p&gt;&lt;p&gt;The dependency section should now look like this:&lt;/p&gt;&lt;pre&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;org.jvnet.hudson.main&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;hudson-core&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;1.170&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;org.jvnet.hudson.main&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;hudson-war&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;type&amp;gt;war&amp;lt;/type&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;1.170&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Next launch a copy of Hudson by running mvn hpi:run&lt;/p&gt;&lt;pre&gt;C:\local\javancss&gt;mvn hpi:run&lt;br /&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO] Searching repository for plugin with prefix: 'hpi'.&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Building Unnamed - org.jvnet.hudson.plugins:javancss:hpi:1.0-SNAPSHOT&lt;br /&gt;[INFO]    task-segment: [hpi:run]&lt;br /&gt;[INFO] ----------------------------------------------------------------------------&lt;br /&gt;[INFO] Preparing hpi:run&lt;br /&gt;&lt;/pre&gt;...&lt;pre&gt;[INFO] Started Jetty Server&lt;br /&gt;[INFO] Starting scanner at interval of 1 seconds.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now fire up a web browser for &lt;a href="http://localhost:8080/"&gt;http://localhost:8080/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_9vm8dyukeD0/R4qgeieIp1I/AAAAAAAAAAU/Jk_zYk-JI-E/s1600-h/hudson1.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_9vm8dyukeD0/R4qgeieIp1I/AAAAAAAAAAU/Jk_zYk-JI-E/s320/hudson1.JPG" alt="" id="BLOGGER_PHOTO_ID_5155109169988675410" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;We want to create five projects: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;Freestyle project for the Simple Ant project&lt;/li&gt;&lt;li&gt;Freestyle project for the Single module Maven2 project&lt;/li&gt;&lt;li&gt;Freestyle project for the Multi-module Maven2 project&lt;/li&gt;&lt;li&gt;Maven2 project for the Single module Maven2 project&lt;/li&gt;&lt;li&gt;Maven2 project for the Multi-module Maven2 project&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_9vm8dyukeD0/R4qgqSeIp2I/AAAAAAAAAAc/G1DhmYHMegU/s1600-h/hudson2.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_9vm8dyukeD0/R4qgqSeIp2I/AAAAAAAAAAc/G1DhmYHMegU/s320/hudson2.JPG" alt="" id="BLOGGER_PHOTO_ID_5155109371852138338" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;For extra finesse, we can add a slave executor, and create a second set of these projects tied to the slave. An final extra project that does nothing other than trigger all the other projects to build is the icing on the cake.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-3190852113949723580?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/3190852113949723580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=3190852113949723580' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3190852113949723580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/3190852113949723580'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2008/01/writing-hudson-plug-in-part-1.html' title='Writing a Hudson plug-in (Part 1 – Preparation)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_9vm8dyukeD0/R4qgeieIp1I/AAAAAAAAAAU/Jk_zYk-JI-E/s72-c/hudson1.JPG' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-2163614723844735848</id><published>2007-12-14T21:25:00.001Z</published><updated>2010-03-24T12:28:20.441Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><title type='text'>JPA, equals() and hashCode()</title><content type='html'>&lt;a class="moz-txt-link-freetext" href="http://burtbeckwith.com/blog/?p=53"&gt;http://burtbeckwith.com/blog/?p=53&lt;/a&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-2163614723844735848?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/2163614723844735848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=2163614723844735848' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2163614723844735848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/2163614723844735848'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/12/jpa-equals-and-hashcode.html' title='JPA, equals() and hashCode()'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-1856119301824110306</id><published>2007-11-21T16:54:00.000Z</published><updated>2010-03-24T12:28:34.718Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><title type='text'>Combining Hibernate and Facelets with Maven, Netbeans and Glassfish</title><content type='html'>&lt;DIV&gt;&lt;FONT face=Arial size=2&gt;&lt;SPAN class=848255316-21112007&gt;Note to self, for  later reading&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;FONT face=Arial size=2&gt;&lt;SPAN  class=848255316-21112007&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;FONT face=Arial size=2&gt;&lt;A  href="http://technology.amis.nl/blog/?p=2610"&gt;http://technology.amis.nl/blog/?p=2610&lt;/A&gt;&lt;/FONT&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;FONT face=Arial size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt; &lt;DIV&gt;&lt;FONT face=Arial size=2&gt;&lt;SPAN class=848255316-21112007&gt;Not sure why  netbeans is essential on this!&amp;nbsp; &lt;/SPAN&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-1856119301824110306?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/1856119301824110306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=1856119301824110306' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1856119301824110306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/1856119301824110306'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/11/combining-hibernate-and-facelets-with.html' title='Combining Hibernate and Facelets with Maven, Netbeans and Glassfish'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-4182435916708425191</id><published>2007-11-14T15:02:00.000Z</published><updated>2011-05-30T14:56:15.205+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Sventon and Hudson</title><content type='html'>&lt;DIV&gt;&lt;SPAN class=765285714-14112007&gt;&lt;FONT face=Arial size=2&gt;This morning I  discovered sventon (&lt;A href="http://www.sventon.org"&gt;http://www.sventon.org&lt;/A&gt;)  a Subversion (&lt;A  href="http://subversion.tigris.org/"&gt;http://subversion.tigris.org/&lt;/A&gt;)  repository browser written in Java. And it's free!&amp;nbsp; Ten minutes later I had  this integrated as a Repository browser in Hudson (&lt;A  href="https://hudson.dev.java.net/"&gt;https://hudson.dev.java.net/&lt;/A&gt;&amp;nbsp;will  be available in build 1.157) and I'm loving it.&amp;nbsp; It's not quite as good as  fisheye... but it does&amp;nbsp;everything I need it to!&amp;nbsp;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=765285714-14112007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=765285714-14112007&gt;&lt;FONT face=Arial size=2&gt;Hudson is  &lt;STRONG&gt;really&lt;/STRONG&gt; starting to have kick-ass features that leave everything  else for dust.&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-4182435916708425191?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/4182435916708425191/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=4182435916708425191' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4182435916708425191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/4182435916708425191'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/11/sventon-and-hudson.html' title='Sventon and Hudson'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6442044352451377678</id><published>2007-11-06T09:09:00.000Z</published><updated>2011-05-30T14:56:15.205+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jenkins'/><title type='text'>Hudson Coverage plugin</title><content type='html'>&lt;DIV&gt;&lt;SPAN class=294120609-06112007&gt;&lt;FONT face=Arial size=2&gt;Note to self, here  are some code coverage tools for .NET that are free to use... and therefore good  candidates for having the Coverage plugin for Hudson support off the  shelf:&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=294120609-06112007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=294120609-06112007&gt;&lt;FONT face=Arial size=2&gt;&lt;A  href="http://sourceforge.net/projects/partcover/"&gt;http://sourceforge.net/projects/partcover/&lt;/A&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=294120609-06112007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=294120609-06112007&gt;&lt;FONT face=Arial size=2&gt;&lt;A  href="http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=881a36c6-6f45-4485-a94e-060130687151"&gt;http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=881a36c6-6f45-4485-a94e-060130687151&lt;/A&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6442044352451377678?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6442044352451377678/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6442044352451377678' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6442044352451377678'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6442044352451377678'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/11/hudson-coverage-plugin.html' title='Hudson Coverage plugin'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-7244787383436153665</id><published>2007-10-08T11:38:00.000+01:00</published><updated>2007-10-08T20:50:28.139+01:00</updated><title type='text'>Stumbled upon</title><content type='html'>A UML renderer written in Java&lt;br /&gt;&lt;br /&gt;&lt;a href="http://snipsnap.org/space/SnipGraph/UML+Example"&gt;http://snipsnap.org/space/SnipGraph/UML+Example&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;A Sequence Diagram renderer written in Java&lt;br /&gt;&lt;a href="http://sdedit.sourceforge.net/"&gt;&lt;br /&gt;http://sdedit.sourceforge.net/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A more complex set of UML digrams from UMLet&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.umlet.com/"&gt;http://www.umlet.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A JavaScript code formatter&lt;br /&gt;&lt;a href="http://code.google.com/p/google-code-prettify/"&gt;&lt;br /&gt;http://code.google.com/p/google-code-prettify/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-7244787383436153665?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/7244787383436153665/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=7244787383436153665' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7244787383436153665'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7244787383436153665'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/10/stumbled-upon.html' title='Stumbled upon'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-8154287516378161867</id><published>2007-09-05T08:42:00.000+01:00</published><updated>2010-03-24T12:28:53.638Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><title type='text'>JAX-WS 2.1 madness</title><content type='html'>&lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial size=2&gt;I am sick of the fun  that is getting JAX-WS 2.1 to work on JVM 1.6.&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial size=2&gt;Oh, copy these four  jars into the endorsed directory and then you can use JAX-WS 2.1... oh but  sometimes it won't work for some unknown reason and then it will work  again.&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial size=2&gt;How you are supposed  to explain this to end users, I don't know.&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial size=2&gt;So next you need a  platform specific installer to put those jars into the correct location, or a  platform specific start script to tell the JVM about my alternate endorsed lib  folder... or do I write a self-extracting jar file that exctracts the libs and  forks a second JVM... no that won't work for people wanting to use my  library..&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial  size=2&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/DIV&gt; &lt;DIV&gt;&lt;SPAN class=358023807-05092007&gt;&lt;FONT face=Arial size=2&gt;We went through all  this pain with SAX and DOM.&amp;nbsp; Did Sun learn  nothing?&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/DIV&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-8154287516378161867?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/8154287516378161867/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=8154287516378161867' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8154287516378161867'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/8154287516378161867'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/09/jax-ws-21-madness.html' title='JAX-WS 2.1 madness'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-7604264870253984956</id><published>2007-08-23T21:45:00.001+01:00</published><updated>2010-03-24T12:27:19.288Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><title type='text'>pom for jsf-comp.sf.net's chartcreator</title><content type='html'>Not perfect, but enough to get you going.&lt;br&gt; &lt;br&gt; To build with this pom:&lt;br&gt; You will need to grab the sources from &lt;a class="moz-txt-link-freetext" href="http://downloads.sourceforge.net/jsf-comp/chartcreator-1.2.0.source.zip"&gt;http://downloads.sourceforge.net/jsf-comp/chartcreator-1.2.0.source.zip&lt;/a&gt; and extract into src/main/java&lt;br&gt; You will need to grab the jar and extract the three files in the META-INF (not MANIFEST.MF) into src/main/resources/META-INF&lt;br&gt; &lt;br&gt; Then you can install away to your hearts content.&lt;br&gt; &lt;br&gt; &amp;lt;?xml version="1.0"?&amp;gt;&lt;br&gt; &amp;lt;project xmlns=&lt;a class="moz-txt-link-rfc2396E" href="http://maven.apache.org/POM/4.0.0"&gt;"http://maven.apache.org/POM/4.0.0"&lt;/a&gt; xmlns:xsi=&lt;a class="moz-txt-link-rfc2396E" href="http://www.w3.org/2001/XMLSchema-instance"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/a&gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; xsi:schemaLocation=&lt;a class="moz-txt-link-rfc2396E" href="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd"&gt;"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&lt;/a&gt;&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;net.sf.jsf-comp&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;chartcreator&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.2.0-mavenized&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;packaging&amp;gt;jar&amp;lt;/packaging&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;name&amp;gt;ChartCreator&amp;lt;/name&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;build&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;plugins&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;source&amp;gt;1.4&amp;lt;/source&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;target&amp;gt;1.4&amp;lt;/target&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/plugins&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/build&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependencies&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.faces&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.2-b19&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.faces&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-impl&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.2-b19&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;com.sun.facelets&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-facelets&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.1.11&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.servlet.jsp&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsp-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;2.1&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.portlet&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;portlet-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;servlet-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;2.5&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;jfree&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jfreechart&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.0.5&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependencies&amp;gt;&lt;br&gt; &amp;lt;/project&amp;gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-7604264870253984956?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/7604264870253984956/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=7604264870253984956' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7604264870253984956'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/7604264870253984956'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/08/pom-for-jsf-compsfnets-chartcreator.html' title='pom for jsf-comp.sf.net&apos;s chartcreator'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-52908193073256603</id><published>2007-08-23T21:12:00.001+01:00</published><updated>2010-03-24T12:27:19.288Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Maven'/><title type='text'>Near minimal Maven 2 pom.xml for Jetty development of Facelets applications using JSF RI 1.2</title><content type='html'>Spent ages trying to get close to this... gave up looking at what others had done, here is my version from scratch:&lt;br&gt; &lt;br&gt; &amp;lt;?xml version="1.0"?&amp;gt;&lt;br&gt; &amp;lt;project xmlns=&lt;a class="moz-txt-link-rfc2396E" href="http://maven.apache.org/POM/4.0.0"&gt;"http://maven.apache.org/POM/4.0.0"&lt;/a&gt; xmlns:xsi=&lt;a class="moz-txt-link-rfc2396E" href="http://www.w3.org/2001/XMLSchema-instance"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/a&gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; xsi:schemaLocation=&lt;a class="moz-txt-link-rfc2396E" href="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd"&gt;"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&lt;/a&gt;&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;....&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;....&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;name&amp;gt;....&amp;lt;/name&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;build&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;plugins&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;source&amp;gt;1.5&amp;lt;/source&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;target&amp;gt;1.5&amp;lt;/target&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;6.1H.5-beta&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;contextPath&amp;gt;/&amp;lt;/contextPath&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scanIntervalSeconds&amp;gt;10&amp;lt;/scanIntervalSeconds&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/configuration&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/plugin&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/plugins&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/build&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependencies&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.faces&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.2-b19&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.faces&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-impl&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.2-b19&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;com.sun.facelets&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jsf-facelets&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.1.11&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;commons-digester&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;commons-digester&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.7&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;commons-beanutils&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;commons-beanutils&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.7.0&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;commons-collections&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;commons-collections&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;3.2&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;jstl&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;1.1.0&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;artifactId&amp;gt;servlet-api&amp;lt;/artifactId&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;version&amp;gt;2.5&amp;lt;/version&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependency&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/dependencies&amp;gt;&lt;br&gt; &amp;lt;/project&amp;gt;&lt;br&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-52908193073256603?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/52908193073256603/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=52908193073256603' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/52908193073256603'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/52908193073256603'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/08/near-minimal-maven-2-pomxml-for-jetty.html' title='Near minimal Maven 2 pom.xml for Jetty development of Facelets applications using JSF RI 1.2'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-6916090266510769332</id><published>2007-07-20T20:15:00.000+01:00</published><updated>2010-03-24T12:28:20.441Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaEE'/><title type='text'>JPA Entity exerciser</title><content type='html'>Working on this to add to EasyGloss.&lt;br /&gt;&lt;br /&gt;There are a number of rules that JPA entities must obey:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;equals and hashCode must only be based on the persistent fields that are @Id annotated.&lt;/li&gt;&lt;li&gt;annotations must be applied to either fields or getters, no mix &amp;amp; match (although future versions of the spec may provide for such)&lt;/li&gt;&lt;li&gt;In general, getters and setters should be simple methods (i.e. no complex processing)&lt;/li&gt;&lt;/ul&gt;I want to have a JPA Entity excerciser that will check these rules for you (and can be included in your unit tests)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-6916090266510769332?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/6916090266510769332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=6916090266510769332' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6916090266510769332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/6916090266510769332'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/07/jpa-entity-exerciser.html' title='JPA Entity exerciser'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-5618319572762552843</id><published>2007-02-26T21:52:00.000Z</published><updated>2010-03-24T12:29:30.538Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Donny Don&apos;t'/><title type='text'>hashCode() pitfalls with HashSet and HashMap</title><content type='html'>&lt;div class="moz-text-html" lang="x-western"&gt;  &lt;/div&gt;            I have been reading a book on C# recently, and it got me thinking about Java's &lt;code&gt;hashCode()&lt;/code&gt; in a little bit more detail than I had before.&lt;br /&gt;&lt;br /&gt;Consider the following Java class.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class Person {&lt;br /&gt;  private String firstName;&lt;br /&gt;  private String surname;&lt;br /&gt;&lt;br /&gt;  public Person(String firstName, String surname) {&lt;br /&gt;    this.firstName = firstName;&lt;br /&gt;    this.surname = surname;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public boolean equals(Object other) {&lt;br /&gt;    /* proper equals checking firstName and surname */&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public int hashCode() {&lt;br /&gt;    int code = surname.hashCode();&lt;br /&gt;    code = 31 * code + firstName.hashCode();&lt;br /&gt;    return code;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public String getFirstName() { /* getter */ }&lt;br /&gt;  public void setFirstName(String firstName) { /* setter */ }&lt;br /&gt;  public String getSurname() { /* getter */ }&lt;br /&gt;  public void setSurname(String surname) { /* setter */ }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What is wrong with the above? Ignore that the hash code may not be well designed given that names are usually A-Z only and the prime factor may not be the most efficient algorithm for calculating hash codes for our data set.&lt;br /&gt;&lt;br /&gt;OK, so I have cheated... the problem is the setters that I glossed over.&lt;br /&gt;&lt;br /&gt;Our hash code is not calculated on the basis of immutable values.&lt;br /&gt;&lt;br /&gt;Let's try using it with a &lt;code&gt;HashSet&lt;person&gt;&lt;/code&gt;...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Person joe = new Person("Joe", "Bloggs");&lt;br /&gt;Set&lt;person&gt; people = new HashSet&lt;person&gt;();&lt;br /&gt;people.add(joe);&lt;br /&gt;&lt;br /&gt;assert people.contains(joe); // this works&lt;br /&gt;assert people.contains(new Person("Joe", "Bloggs")); // this works&lt;br /&gt;&lt;br /&gt;joe.setSurname("Smith");&lt;br /&gt;&lt;br /&gt;assert !people.contains(new Person("Joe", "Bloggs")); // this works&lt;br /&gt;assert people.contains(new Person("Joe", "Smith")); // this fails!!!&lt;br /&gt;assert people.contains(joe); // this fails!!!&lt;br /&gt;&lt;br /&gt;boolean found = false;&lt;br /&gt;for (Person person: people) {&lt;br /&gt;  if (person == joe) {&lt;br /&gt;    found = true;&lt;br /&gt;    break;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;assert found; // this works!!!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So what is going on?&lt;br /&gt;&lt;br /&gt;When we put &lt;code&gt;joe&lt;/code&gt; into the &lt;code&gt;HashSet&lt;/code&gt;, the &lt;code&gt;HashSet&lt;/code&gt; selects a bucket based on the hash code for &lt;code&gt;joe&lt;/code&gt;.  When we ask the &lt;code&gt;HashSet&lt;/code&gt; if it contains a &lt;code&gt;Person&lt;/code&gt;, it computes the hash code of the object that it's looking for and searches only in that bucket.&lt;br /&gt;&lt;br /&gt;As our &lt;code&gt;hashCode()&lt;/code&gt; is based on non-final fields, the hash code can change behind the scenes of our &lt;code&gt;HashSet&lt;/code&gt; (and it won't find out until it has too few buckets and needs some more to optimise searching). Thus completely invalidating the basic assumption of the &lt;code&gt;HashSet&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;A correct implementation for the &lt;code&gt;Person&lt;/code&gt; class would either have the names as &lt;code&gt;final&lt;/code&gt;, or have &lt;code&gt;hashCode&lt;/code&gt; like so&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public int hashCode() {&lt;br /&gt;  return 0; // we have no non-final identity fields to calculate the hashCode from&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-5618319572762552843?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/5618319572762552843/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=5618319572762552843' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/5618319572762552843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/5618319572762552843'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2007/02/hashcode-pitfalls-with-hashset-and.html' title='hashCode() pitfalls with HashSet and HashMap'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-115028780647657505</id><published>2006-06-14T13:23:00.000+01:00</published><updated>2006-06-14T13:59:33.013+01:00</updated><title type='text'>How em.merge actually works</title><content type='html'>&lt;p&gt;Let's have a look at some em.merge related fun.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;From discussions on a number of forums, here is my explanation for what goes on when you call em.merge.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;We will use the following classes as an example:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;@Entity&lt;br /&gt;public class Parent {&lt;br /&gt;    ...&lt;br /&gt;    private List&amp;lt;Child&amp;gt; children;&lt;br /&gt;    ...&lt;br /&gt;    public List&amp;lt;Child&amp;gt; getChildren() { return this.children; }&lt;br /&gt;    public void setChildren(List&amp;lt;Child&amp;gt; children) { this.children = children; }&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;@Entity&lt;br /&gt;public class Child {&lt;br /&gt;    ...&lt;br /&gt;    private Parent parent;&lt;br /&gt;    ...&lt;br /&gt;    public Parent getParent() { return this.parent; }&lt;br /&gt;    public void setParent(Parent parent) { this.parent = parent; }&lt;br /&gt;    ...&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;To aid in understanding, we will assume that our client has a &lt;em&gt;UserTransaction ut&lt;/em&gt;&lt;br /&gt;(this is to allow us to force entity instances to become detached. Entity instances can become&lt;br /&gt;detached without using a UserTransaction, however, we want to show what happens to the entities;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;So our client goes something like:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  Parent parent = null;&lt;br /&gt;  ...&lt;br /&gt;  ut.begin();&lt;br /&gt;  parent = em.find(Parent.class, aParentId);&lt;br /&gt;  ...&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;At this point the client has a reference to a managed instance. We can illustrate this like so:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="http://www.one-dash.com/blog/fig1.png" width="385px" height="495px" alt="Figure 1"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Now at some point we have to commit the transaction and the transaction ends.&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  ...&lt;br /&gt;  ut.commit();&lt;br /&gt;  ...&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;So now parent still points to an instance of the class Parent that still holds the data as&lt;br /&gt;before, however, the instances have lost their connection to the entity manager (as the&lt;br /&gt;connection depended on the transaction.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;We can illustrate this like so:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="http://www.one-dash.com/blog/fig2.png" width="385px" height="495px" alt="Figure 2"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Later on, we want to synchronise the parent object back to the database, so we call em.merge:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  ...&lt;br /&gt;  ut.begin();&lt;br /&gt;  Parent managedParent = em.merge(parent);&lt;br /&gt;  ...&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Now, the transaction is still active, so the instances pointed to by managedParent are still &lt;br /&gt;managed instances, i.e. changes made to fields will be reflected back into the database.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The instances pointed to by parent are still detached instances, i.e. changes made to fields &lt;br /&gt;do not get persisted back to the database. &lt;em&gt;em.merge&lt;/em&gt; has done two things for us:&lt;br /&gt;&lt;ol&gt;   &lt;br /&gt;  &lt;li&gt;Fetched new managed instances of the objects we already have&lt;/li&gt;   &lt;br /&gt;  &lt;li&gt;Synchronised all the changes in the instances we have back to the managed instances (and therefore back to the database&lt;/li&gt; &lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;We can illustrate this like so:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="http://www.one-dash.com/blog/fig3.png" width="711px" height="495px" alt="Figure 3"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Once we commit or otherwise end the transaction, we will now have two sets of detached instances.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Some people want to not have references to our old detached instance lying around... so they will&lt;br /&gt;typically reuse the parent instance variable like so:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  ...&lt;br /&gt;  ut.begin()&lt;br /&gt;  parent = em.merge(parent);&lt;br /&gt;  ...&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;This is OK, provided that there are no other references to the detached entities lying around.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;If you had done something like&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  ...&lt;br /&gt;  Child child = parent.getChildren().get(0);&lt;br /&gt;  ut.begin();&lt;br /&gt;  parent = em.merge(parent);&lt;br /&gt;  ut.commit();&lt;br /&gt;  ...&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Then you are in a dangerous situation as &lt;em&gt;child&lt;/em&gt; points to a different detatched instance of the same persisted entity!!!&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;img src="http://www.one-dash.com/blog/fig4.png" width="711px" height="495px" alt="Figure 4"/&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The moral of the story is that when you call em.merge, you need to know what instances you are merging and what you will do with the detatched instances after you have finished merging&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-115028780647657505?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/115028780647657505/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=115028780647657505' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/115028780647657505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/115028780647657505'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/06/how-emmerge-actually-works.html' title='How em.merge actually works'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114493156873256579</id><published>2006-04-13T13:32:00.000+01:00</published><updated>2006-04-13T13:32:51.273+01:00</updated><title type='text'>Another view on unit testing annotated entities</title><content type='html'>Borys Burnayev has an interesting article with some useful ideas on testing annotated entities and EJB3 classes requiring injection to work.&lt;br&gt; &lt;a class="moz-txt-link-freetext" href="http://www-128.ibm.com/developerworks/java/library/j-ejb3jpa.html"&gt;http://www-128.ibm.com/developerworks/java/library/j-ejb3jpa.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114493156873256579?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114493156873256579/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114493156873256579' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114493156873256579'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114493156873256579'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/04/another-view-on-unit-testing-annotated.html' title='Another view on unit testing annotated entities'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114484353130539018</id><published>2006-04-12T13:05:00.000+01:00</published><updated>2006-04-12T13:05:31.936+01:00</updated><title type='text'>Unit testing of annotated classes (part 2)</title><content type='html'>EasyGloss has been accepted as an incubator project on dev.java.net&lt;br&gt; &lt;br&gt; &lt;a class="moz-txt-link-freetext" href="https://easygloss.dev.java.net/"&gt;https://easygloss.dev.java.net/&lt;/a&gt;&lt;br&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114484353130539018?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114484353130539018/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114484353130539018' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114484353130539018'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114484353130539018'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/04/unit-testing-of-annotated-classes-part.html' title='Unit testing of annotated classes (part 2)'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114475818620397007</id><published>2006-04-11T13:23:00.000+01:00</published><updated>2006-04-11T13:23:12.746+01:00</updated><title type='text'>Unit testing of annotated classes</title><content type='html'>I ran into some fun while trying to unit test my annotated entities as some of the annotations were on private fields.&lt;br&gt; &lt;br&gt; Rather than create constructors to do the injection for me, or change the access type on the fields, I came up with this little framework that people might be interested in. (Or be able to help improve)&lt;br&gt; &lt;br&gt; Temporary home: &lt;a  href="http://www.stvconsultants.com/community/easygloss/"&gt;http://www.stvconsultants.com/community/easygloss/&lt;/a&gt;&lt;br&gt; &lt;br&gt; Permanent home: hopefully on dev.java.net&lt;br&gt; &lt;br&gt; Has anyone any comments?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114475818620397007?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114475818620397007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114475818620397007' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114475818620397007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114475818620397007'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/04/unit-testing-of-annotated-classes.html' title='Unit testing of annotated classes'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114440116602305672</id><published>2006-04-07T10:12:00.000+01:00</published><updated>2006-04-07T10:12:46.113+01:00</updated><title type='text'>JUnit testing strategies for EJB3 entities and beans</title><content type='html'>Here are my initial thoughts (which I posted as a forum comment elsewhere)&lt;br&gt; &lt;br&gt; Layer 0. Test the entity beans are deployable (You'll need some of the framework from Layer 4 for this). Basically, you need to know that all your annotations work. Things to watch out for are multiple @Id fields in one class or @EmbeddedID or @IdClass in conjunction with @ManyToOne, @ManyToMany, @OneToMany, @OneToOne and fun with @JoinTable, @JoinColumn and @JoinColumns. Once you know how these are supposed to work with the specification, it's not too bad to write it correctly each time. But there are some gotchas that will break things later on.&lt;br&gt; &lt;br&gt; Layer 1. Do the functions in the classes that don't depend on annotations work as expected. Typically, this is just going to be the getters and setters in your entity classes. Of course JUnit best practice says we don't bother testing functions that look like:&lt;br&gt; &lt;br&gt; public T getX() {&lt;br&gt; return this.x;&lt;br&gt; }&lt;br&gt; &lt;br&gt; or&lt;br&gt; &lt;br&gt; public void setX(T x) {&lt;br&gt; this.x = x;&lt;br&gt; }&lt;br&gt; &lt;br&gt; as there is nothing that can go wrong with them. So in that case, your level 1 tests will just be initial values specified from constructors and verifying that the non-get/set pairs work, and that the getters you have tagged @Transient work (because you've likely put some logic in them)&lt;br&gt; &lt;br&gt; Layer 2. Test the session bean methods that don't require injection to work.&lt;br&gt; &lt;br&gt; Layer 3. Test the session bean methods that require injection (Mock Objects). Simulate the injection for yourself, injecting Mock Objects for the entity manager. Then you can confirm that the correct methods are being called in the correct sequences, etc.&lt;br&gt; [Note this may require some skill in designing the mock. I'm working on developing my own entitymanager mock, and if it looks useful I'll release it to the world.&lt;br&gt; &lt;br&gt; Layer 4. Test the session bean methods that require injection (Real entity manager) (See Layer 0)&lt;br&gt; For this you will need an out of container persistence implementation. Currently Hibernate and Glassfish provide beta versions. You will need a different persistence.xml file that lists all the entities. You will have to use reflection to inject the entity manager(s) that you create from an entity manager factory unless you provide a constructor that takes an EntityManager as a parameter. You may need to use reflection to call any @PostConstruct method if you made it private.&lt;br&gt; &lt;br&gt; Layer 5. Navigate the relationships in the objects returned from Layer 4 using a database that has been loaded with test data.&lt;br&gt; &lt;br&gt; I am currently using Layers 0, 1, 2 &amp;amp; 4 to test my session beans and entity beans.&lt;br&gt; &lt;br&gt; Has anyone else any other ideas?&lt;br&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114440116602305672?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114440116602305672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114440116602305672' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114440116602305672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114440116602305672'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/04/junit-testing-strategies-for-ejb3.html' title='JUnit testing strategies for EJB3 entities and beans'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114284720194007180</id><published>2006-03-20T09:33:00.000Z</published><updated>2006-03-20T09:34:40.966Z</updated><title type='text'>Nope, it's in the spec</title><content type='html'>&lt;p&gt;From the spec:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;The deployment tool must first read the Java EE application deployment descriptor from the application .ear file (META-INF/application.xml). If the deployment descriptor is present, it fully specifies the modules included in the application. If no deployment descriptor is present, the deployment tool uses the following rules to determine the modules included in the application.&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt; It would be nice if glassfish gave some warning that your application.xml might not be completely specified!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114284720194007180?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114284720194007180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114284720194007180' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114284720194007180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114284720194007180'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/03/nope-its-in-spec.html' title='Nope, it&apos;s in the spec'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114284650066268681</id><published>2006-03-20T09:21:00.000Z</published><updated>2006-03-20T09:25:11.830Z</updated><title type='text'>application.xml related stuff</title><content type='html'>&lt;p class="mobile-post"&gt;I have been going nuts trying to trace problems with deployment of&lt;br /&gt;enterprise applications and resource injection.  I think the issue is&lt;br /&gt;around the &lt;code&gt;application.xml&lt;/code&gt; file in a .ear! Without an&lt;br /&gt;&lt;code&gt;application.xml&lt;/code&gt; file, everything is fine and dandy.  As&lt;br /&gt;soon as you add an &lt;code&gt;application.xml&lt;/code&gt; file, things start&lt;br /&gt;breaking... it seems that when you have defined an&lt;br /&gt;&lt;code&gt;application.xml&lt;/code&gt; file only those modules that are defined in&lt;br /&gt;the &lt;code&gt;application.xml&lt;/code&gt; file are loaded.  Without a&lt;br /&gt;&lt;code&gt;application.xml&lt;/code&gt; file, all the .jar files are scanned for&lt;br /&gt;&lt;code&gt;@Stateless&lt;/code&gt; and &lt;code&gt;@Statefull&lt;/code&gt; annotations and the&lt;br /&gt;modules are inferred.  It would help if there was some notification that&lt;br /&gt;maybe your &lt;code&gt;application.xml&lt;/code&gt; file was incomplete, or else&lt;br /&gt;some way of restoring the scanning behaviour if you want it.  Now to go&lt;br /&gt;digging through the spec to see if I'm just stupidly missing something&lt;br /&gt;or if I need to file a bug report.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114284650066268681?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114284650066268681/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114284650066268681' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114284650066268681'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114284650066268681'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/03/applicationxml-related-stuff.html' title='application.xml related stuff'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114185835704038348</id><published>2006-03-08T22:52:00.000Z</published><updated>2006-03-08T22:52:37.063Z</updated><title type='text'>Rolling your own ELResolver</title><content type='html'>ELResolvers are where you get to extend the new unified EL for your own pages.&amp;nbsp; They are not that difficult to extend, it's just a case of:&lt;br&gt; &lt;ol&gt;   &lt;li&gt;Have your class extend from ELResolver&lt;/li&gt;   &lt;li&gt;Implement the abstract methods&lt;/li&gt;   &lt;li&gt;Add an &amp;lt;el-resolver&amp;gt; element to your &amp;lt;application&amp;gt; section in faces-config.xml&lt;/li&gt;   &lt;li&gt;Start using your resolver!&lt;/li&gt; &lt;/ol&gt; OK, so why would you want to do this? Well, for one it can make some things a lot easier. I was driven to find this solution in order to dynamically generate h:dataTables with variable numbers of columns.&amp;nbsp; Ordinarily you would need to bind the h:dataTable to a backing bean and have the backing bean add in the extra columns, but with c:forEach now being compatible with JSF, we have an alternative.&lt;br&gt; &lt;br&gt; here's the model of the data we are trying to present: (the classes are coming from EJB entities which are mapping to a legacy database)&lt;br&gt; &lt;br&gt; public class Result {&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private Sample sample;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private ResultType resultType;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private String value;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ... &lt;br&gt; }&lt;br&gt; &lt;br&gt; public class Sample {&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private String name;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private Collection&amp;lt;Result&amp;gt; results;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt; }&lt;br&gt; &lt;br&gt; public class Project {&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private String name;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; private Collection&amp;lt;Sample&amp;gt; samples;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt; }&lt;br&gt; &lt;br&gt; We want to display a results table for all the samples from the project object we have been passed... it should look something like&lt;br&gt; &lt;br&gt; Sample&amp;nbsp;&amp;nbsp; TypeA&amp;nbsp;&amp;nbsp; TypeB&amp;nbsp;&amp;nbsp; TypeC&amp;nbsp;&amp;nbsp; ...&lt;br&gt; 1&amp;nbsp;&amp;nbsp; ...&amp;nbsp;&amp;nbsp; ...&amp;nbsp;&amp;nbsp; ...&lt;br&gt; 2&amp;nbsp;&amp;nbsp; ...&amp;nbsp;&amp;nbsp; ...&amp;nbsp;&amp;nbsp; ...&lt;br&gt; ...&lt;br&gt; &lt;br&gt; where there are as many columns as there are types.&lt;br&gt; &lt;br&gt; We add a method to the project class that returns Collection&amp;lt;ResultType&amp;gt;&lt;br&gt; &lt;br&gt; and what we'd like to do is:&lt;br&gt; &lt;br&gt; &amp;lt;h:dataTable value="#{project.samples}" var="sample"&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;h:column&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;lt;h:outputText value="#{sample.name}"/&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/h:column&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;c:forEach items="#{project.resultTypes}" var="resultType"&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;lt;h:column&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;lt;h:outputText value="#{sample.results[resultType].value}"/&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;lt;/h:column&amp;gt;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/c:forEach&amp;gt;&lt;br&gt; &amp;lt;/h:dataTable&amp;gt;&lt;br&gt; &lt;br&gt; Normally this would only work if sample.results is a Map&amp;lt;String,Result&amp;gt; and each ResultType can be converted into a string that happens to be the key of the map.&amp;nbsp; We end up writing a wrapper class for a Map and since we've been given a collection and not a map, efficiency is not the best.&lt;br&gt; &lt;br&gt; but a custom ELResolver can help us:&lt;br&gt; &lt;br&gt; I'll post more when I have simplified my resolver to fit this example&lt;br&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114185835704038348?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114185835704038348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114185835704038348' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114185835704038348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114185835704038348'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/03/rolling-your-own-elresolver.html' title='Rolling your own ELResolver'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114140200230157295</id><published>2006-03-03T16:06:00.000Z</published><updated>2006-03-03T16:06:43.513Z</updated><title type='text'>Note to self</title><content type='html'>The path to the solution to authorization woes could start here&lt;br&gt; &lt;br&gt; &lt;a class="moz-txt-link-freetext" href="http://www.thoughtsabout.net/blog/archives/000033.html"&gt;http://www.thoughtsabout.net/blog/archives/000033.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114140200230157295?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114140200230157295/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114140200230157295' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114140200230157295'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114140200230157295'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/03/note-to-self.html' title='Note to self'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-21580788.post-114016522560139692</id><published>2006-02-17T08:00:00.000Z</published><updated>2006-02-17T08:36:25.130Z</updated><title type='text'>Keeping up with the latest glassfish builds</title><content type='html'>Are you keeping up with the latest glassfish builds? Are you running windows 2k or higher? Are you fed up having to manually reinstall the server whenever a new weekly build comes out?&lt;br /&gt;&lt;br /&gt;Well I was, so here's a very basic Windows NT command script.&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;blockquote&gt;&lt;code&gt;@ECHO OFF&lt;br /&gt;REM Change these environment variables to your own config&lt;br /&gt;&lt;br /&gt;SET GLASSFISH_DOWNLOADS=c:\java\downloads&lt;br /&gt;SET GLASSFISH_DRIVE=c:&lt;br /&gt;SET GLASSFISH_PARENT_DIR=c:\java&lt;br /&gt;&lt;br /&gt;REM Make sure we are in the correct location&lt;br /&gt;&lt;br /&gt;%GLASSFISH_DRIVE%&lt;br /&gt;CD %GLASSFISH_PARENT_DIR%&lt;br /&gt;&lt;br /&gt;REM Find the latest build that has been downloaded&lt;br /&gt;&lt;br /&gt;SET BUILD=aaa&lt;br /&gt;FOR %%i IN (%GLASSFISH_DOWNLOADS%\glassfish-installer-9.0-*.jar) DO IF %%i GTR %BUILD% SET BUILD=%%i&lt;br /&gt;&lt;br /&gt;IF A%BUILD%A==AaaaA GOTO no_build&lt;br /&gt;&lt;br /&gt;CLS&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO Newest build found is:   %BUILD%&lt;br /&gt;IF EXIST glassfish.bld (TYPE glassfish.bld) ELSE (ECHO Current installed build: I don't know)&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] About to remove current installation of Glassfish...&lt;br /&gt;ECHO .&lt;br /&gt;ECHO This will delete any currently deployed applications, so you would want to&lt;br /&gt;ECHO either not care about them or have made a backup already!&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO This is your last chance to press Ctrl+C to abort otherwise&lt;br /&gt;ECHO press any other key to continue...&lt;br /&gt;PAUSE &gt;&gt; NUL&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Stopping domain1...&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;CALL ASADMIN stop-domain domain1&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Removing current installation...&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;RMDIR /S /Q %GLASSFISH_PARENT_DIR%\glassfish&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Starting installer...&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;JAVA -Xmx256m -jar %BUILD%&lt;br /&gt;&lt;br /&gt;ECHO Current installed build: %BUILD% &gt; glassfish.bld&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Setting up domain1...&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;CD glassfish&lt;br /&gt;&lt;br /&gt;CALL ANT -f setup.xml&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Starting domain1...&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;CALL ASADMIN start-domain domain1&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] Glassfish reinstalled!&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;GOTO end&lt;br /&gt;&lt;br /&gt;:no_build&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO Cannot find any glassfish builds in %GLASSFISH_DOWNLOADS%&lt;br /&gt;ECHO .&lt;br /&gt;&lt;br /&gt;:end&lt;br /&gt;&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO .&lt;br /&gt;ECHO [%0] I'm done, press a key and I'll be out of here...&lt;br /&gt;ECHO .&lt;br /&gt;PAUSE &gt;&gt; NUL&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;/span&gt;Save it as, e.g. REINSTALL-GLASSFISH.CMD and all you need to do is run it every time you download a new build!&lt;br /&gt;&lt;br /&gt;Todo:&lt;br /&gt;1. Add some stuff to make it go fetch the latest build from the download page&lt;br /&gt;2. Add some stuff to pull out all the applications from the autodeploy directory before removing the current build and put them back in afterwards.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21580788-114016522560139692?l=javaadventure.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://javaadventure.blogspot.com/feeds/114016522560139692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=21580788&amp;postID=114016522560139692' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114016522560139692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/21580788/posts/default/114016522560139692'/><link rel='alternate' type='text/html' href='http://javaadventure.blogspot.com/2006/02/keeping-up-with-latest-glassfish.html' title='Keeping up with the latest glassfish builds'/><author><name>Stephen Connolly</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://one-dash.com/photos/motion/m002.jpg'/></author><thr:total>0</thr:total></entry></feed>
