So far for subversion…

When I started Java development, I got to know the unpleasant nature of CVS. Sure it was better than Visual SourceSafe (by a really small margin), but still, it was a real pain in the butt. Then I witnessed the migration to Subversion. The skies cleared and the sun was shining again. Finally a decent SCM with all the capabilities one would ever hope. Sure, merging from branches still had the capability of making you bald in a single pull, but anything was better than CVS.

Recently, I switched to Git. While the switch from CVS to Subversion was quite straightforward, the move to Git was less so. Git is a paradigm shift and a completely different ballgame. With Git, there is no ‘server’. There are just clones of repositories everywhere. Off course, one of those clones can function as a master repository, filling the role of a ‘server’, a scenario used almost every time. Everyone works on their own clone and commit to their own repository. When they want to, they can push their changes to the master (if they have the rights to do so). You can also turn it around: the master can pull changes from the clones into its repository. And you have another possibility: you can pull changes from someone else’s repository. This is the nature of the beast called ‘Distributed SCM’. It’s SkyNet, there is no system core.

Git was popular in Linux kernel development. Everyone had their own kernel code and tinker at will. If the changes were any good, someone else could pull them in and take a look. If they were great, the master could pull them in, or someone who is able to push to the master pulls them in, takes a look and pushes them to the master. It’s an OSS wet dream.

Now, a lot of big Java companies are jumping on the Git bandwagon. SpringSource has migrated all of its codebase to their own Git repository and very recently JBoss moved Hibernate to GitHub. And why wouldn’t they? Git is superior to Subversion in every way. It takes a bit of getting used to, but then again, the same could be said of other SCM tools.
All of this leads me to believe that Subversion is dying slowly, as is the case with most software for which there is a better open-source product with a small learning and adoption curve. Subversion, you’ve been a real help, sorry to see you go, but it’s time to head to the attic. Git’s moving in.

Goodbye, old friend.

4 Comments

Easy productivity gains for Java shops

Having done a fair amount of projects myself, I’m starting to get a picture of what it takes to be productive. Mostly it’s just one or two small things, but together they really make a difference. Most of them have to do with sustaining a constant flow of thought. Let me point out a few:

Dual screens
A programmer needs space to work on. I cannot emphasize this enough. A monitor is a very small investment and studies show that developers that have two or more monitors are a lot more productive than those stuck with one. Alt-tab isn’t just annoying when having to do it 50+ times a day, it disturbs a developer in his “flow”.

Good hardware
More than often companies give the same hardware to the IT department as to their other departments. This is plain wrong. There’s nothing, I repeat nothing, as annoying as a hung computer whilst developing or debugging: it disturbs the flow of thought. 2 Gb of RAM may be enough for administrative personnel using Word and Outlook, but don’t even think for a minute it’ll be enough for a developer having an IDE, an application server, a database and perhaps a virtual machine running at the same time.

Free soft drinks and/or coffee
Small investment, huge appreciation. Nothing more, nothing less.

Unrestricted internet access
Picture this: you stumbled upon an issue. After looking through the PDF reference on your hard drive to no avail, you decide to try your luck on Google. On of the search results seems interesting, but the site has been blocked. Sounds familiar? Look, I’m all for banning Facebook at work, but please, don’t block all the blogging sites. It’s ricidulous for a developer to stumble upon the solution, only to be greeted by Websense with the message ‘Blocked: Information Technology’. So, ban Facebook and online gaming sites but leave the rest open. That being said, Websense is a real PITA.

Allow for IM
Communication is key to the success of a team. So why do companies keep insisting on blocking IM? If you don’t want your employees to chat with friends, provide an internal IM server (like OpenFire). No IM capabilities for a development team is not an option, unless you want your development team running around all the time!

Use JRebel
One of those products that has a huge ROI. No restarts, less developer frustration for the cost of hiring a consultant for 2 hours.

Provide VM’s for environments
If your deployment environment takes more than 5 minutes to set up, provide a virtual machine that has a working, fully-configured environment that is ready to use and kept up to date regularly. If you need a wiki to explain an environment setup for local deployments, provide a VM instead, either locally or on a VM server (one per developer).

If you have other quick gains, feel free to comment.

3 Comments

JRebel, you won me over

Recently, I tried JRebel again. I say again, as I’ve tried it before, with varying success (mostly because of the way the applications I write behave, which isn’t JRebel’s fault). But my recent attempt was more than successful.

For most of my projects, I use IntelliJ and Maven. They’re a match made in heaven and really speed up my development turnaround. But still, every time I change some of the logic in my services, I’m forced to restart or republish my application (I mostly write desktop applications). Not good. So here comes JRebel to the rescue.

JRebel is a replacement for the Hotswap capabilities of the standard Oracle JVM. As you may know, Hotswap only supports certain changes to the code, such as changes to the body of a method. Things like adding a private method, adding a field or changing an interface are not supported.
JRebel is configured as a JVM agent and extends the JVM classloader to handle on-the-fly reloading of classes and resources. Yep, you read it correctly, also resources. JRebel supports among others Spring, which results in the neat fact that when you change your Spring context XML, changes are done immediately. No restart required. If your Spring context uses a bean of a class that is changed, JRebel detects this and will reinstantiate that bean and re-inject it.

When using Maven, integrating JRebel is a piece of cake. JRebel has a Maven plugin that nicely generates the rebel.xml configuration file, which JRebel uses to determine which resources and classfiles it needs to monitor for changes. No manual editing needed.
The same goes for IntelliJ, which provides a plugin. When it sees a rebel.xml file in the classpath, it automatically adds a JRebel facet to your project. The plugin adds 2 additional run buttons for running the application with the JRebel agent.

Needless to say, this tool is one for my toolbox. Oh yeah, JRebel is free for opensource projects!

4 Comments

Using Perf4J with Logback

Perf4J is a great framework for performance logging. It’s non-intrusive and really fills the need for accurate performance logging. But despite the fact it supports SLF4J, Logback support is somewhat lacking. Log4J on the other hand has some great appenders for aggregate performance logging and graphing.
I’m a real fan of Logback, so it was a bit of a disappointment that those appenders weren’t available for my favorite logging framework. According to the mailinglists and JIRA issues, it should be easy to create a Logback appender, so I got to work.
Luckily, Perf4J has a generic implementation which can be wrapped to be used with any logging framework, a great starting point. After a round of cursing at the screen, I got a working solution. And I learned something I didn’t know about Logback appenders: the stop method is never called. Ever. Good one, guys. You need to provide your own shutdown hook in the start method to handle the stopping of your appender. Anyway, it was an easy fix, but kind of defeats the purpose of the lifecycle, which is supposed to handle that stuff.
Read the rest of this entry »

2 Comments

Small reminder for my readers

If you want to comment on my articles, please do so. However, all comments need to be approved by me, so please adhere to the following rules:

  • Use a valid email address
  • No profanity or name calling (“YOU SUCK!!!” falls under that category)
  • No spamming

If you follow these rules, I’ll approve even the most disagreeing comments (please read this if you want to disagree in style: DH levels).

No Comments

Stepping away from MySQL

MySQL is a dead product walking. I know it, people around me are starting to realize it and Monty sure as hell knows it. For those not familiar with MySQL’s roots, Monty Widenius is one of the founder of MySQL.

Recently, Monty has staged an all-out attack on the Sun-Oracle merger, due to the promises that Oracle made towards the MySQL product that apparently it has no intention to keep.

Luckily, we (MySQL users) have a great alternative: MariaDB. MariaDB is a fully compatible MySQL fork, once again backed by Monty. And it has all the features MySQL has and then some. Better multicore support, better performance, more storage engines, … If you’re planning on using MySQL for any project, consider using MariaDB instead. For the Ubuntu users out there, there are APT repositories available for MariaDB, and they are updated frequently.

Now if only Monty was able to rebuild the client libraries for MySQL to remove that pesky GPL license…

3 Comments

Creating a 16-segment character display component in Swing

Some time ago, Javalobby posted a nice article on how someone created a event talk board that resembled a departure board at the airport or trainstation.

I needed to brush up my Java2D skills, so I started on something easy that was inspired by that article: a 16-segment character display (as described here). It should only use Java2D (no images) and capable of showing A-Z and 0-9.

After an evening of drawing on paper grid, this is the end result:

16 segment display

The code is divided in 3 parts: the single character component, a character group component capable of displaying a String and the alfabet definition.

First I’ll show the single character component:

public class SixteenSegmentDisplay extends JComponent {
    private java.util.List<Integer> lightedSegments;
    public static final int WIDTH = 29;
    public static final int HEIGHT = 46;

    private final double INACTIVE_COLOR_DARKEN_FRACTION = 0.4;

    private static final Image BACKGROUND_IMAGE = createBackgroundImage();

    // Highlight Colors
    private static final java.awt.Color HIGHLIGHT_COLOR_TOP = new java.awt.Color(0x000000);
    private static final java.awt.Color HIGHLIGHT_COLOR_BOTTOM = new java.awt.Color(0x625C52);

    private Color activeSegmentColor = new Color(0xFF0000);
    private Color inactiveSegmentColor = darken(new Color(0xFF0000), INACTIVE_COLOR_DARKEN_FRACTION);

    private Color backgroundColor = Color.black;

    public SixteenSegmentDisplay() {
        lightedSegments = new ArrayList<Integer>();
    }

    @Override
    public java.awt.Dimension getSize() {
        return new Dimension(WIDTH, HEIGHT);
    }

    @Override
    public java.awt.Dimension getPreferredSize() {
        return new Dimension(WIDTH, HEIGHT);
    }

    @Override
    public java.awt.Dimension getSize(java.awt.Dimension rv) {
        return new Dimension(WIDTH, HEIGHT);
    }

    public void setSegmentColor(Color segmentColor) {
        activeSegmentColor = segmentColor;
        inactiveSegmentColor = darken(segmentColor, INACTIVE_COLOR_DARKEN_FRACTION);
    }

    public void setBackgroundColor(Color backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    public void setCharacter(char character) {
        setLightedSegments(SixteenSegmentAlfabet.MAP.get(character));
    }

    private void setLightedSegments(SixteenSegmentAlfabet character) {
        if (character != null && character.getSegments() != null)
            this.lightedSegments = Arrays.asList(character.getSegments());
        else
            this.lightedSegments = new ArrayList<Integer>(0);
        this.repaint();
    }

    private Color darken(Color c, double fragment) {
        Double newRed = fragment * c.getRed();
        Double newBlue = fragment * c.getBlue();
        Double newGreen = fragment * c.getGreen();
        return new Color(newRed.intValue(), newGreen.intValue(), newBlue.intValue());
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D graphics = (Graphics2D) g.create();
        graphics.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setRenderingHint(java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION, java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        graphics.setRenderingHint(java.awt.RenderingHints.KEY_COLOR_RENDERING, java.awt.RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        paintBackground(graphics);
        // left top to top middle
        paintSegment(graphics, lightedSegments.contains(1), getPoints("2,2;3,1;14,1;15,2;14,3;3,3;2,2"));
        // top middle to right top
        paintSegment(graphics, lightedSegments.contains(2), getPoints("15,2;16,1;27,1;28,2;27,3;16,3;15,2"));
        // left top to left middle
        paintSegment(graphics, lightedSegments.contains(3), getPoints("2,2;3,3;3,22;2,23;1,22;1,3;2,2"));
        // top left to middle middle
        paintSegment(graphics, lightedSegments.contains(4), getPoints("4,4;5,4;13,19;13,21;12,21;4,6;4,4"));
        // top middle to middle middle
        paintSegment(graphics, lightedSegments.contains(5), getPoints("15,2;14,3;14,22;15,23;16,22;16,3;15,2"));
        // top right to middle middle
        paintSegment(graphics, lightedSegments.contains(6), getPoints("25,4;26,4;26,6;18,21;17,21;17,19;25,4"));
        // top middle to right middle
        paintSegment(graphics, lightedSegments.contains(7), getPoints("28,2;29,3;29,22;28,23;27,22;27,3;28,2"));
        // left middle to middle middle
        paintSegment(graphics, lightedSegments.contains(8), getPoints("2,23;3,22;14,22;15,23;14,24;3,24;2,23"));
        // middle middle to right middle
        paintSegment(graphics, lightedSegments.contains(9), getPoints("15,23;16,22;27,22;28,23;27,24;16,24;15,23"));
        // left middle to bottom left
        paintSegment(graphics, lightedSegments.contains(10), getPoints("2,23;3,24;3,43;2,44;1,43;1,24;2,23"));
        // left bottom to middle middle
        paintSegment(graphics, lightedSegments.contains(11), getPoints("13,27;13,25;12,25;4,40;4,42;5,42;13,27"));
        // bottom middle to middle middle
        paintSegment(graphics, lightedSegments.contains(12), getPoints("15,23;16,24;16,43;15,44;14,43;14,24;15,23"));
        // bottom right to middle middle
        paintSegment(graphics, lightedSegments.contains(13), getPoints("17,25;18,25;26,40;26,42;25,42;17,27;17,25"));
        // right middle to bottom right
        paintSegment(graphics, lightedSegments.contains(14), getPoints("28,23;29,24;29,43;28,44;27,43;27,24;28,23"));
        // bottpm left to bottom middle
        paintSegment(graphics, lightedSegments.contains(15), getPoints("2,44;3,43;14,43;15,4;14,45;3,45;2,44"));
        // bottom middle to bottom right
        paintSegment(graphics, lightedSegments.contains(16), getPoints("15,244;16,43;27,43;28,44;27,45;16,45;15,44"));
    }

    private void paintBackground(Graphics2D g) {
        g.drawImage(BACKGROUND_IMAGE, 0, 0, this);
    }

    private static final BufferedImage createBackgroundImage() {
        GraphicsConfiguration gfxConf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        final BufferedImage IMAGE = gfxConf.createCompatibleImage(WIDTH, HEIGHT, Transparency.OPAQUE);
        Graphics2D g2 = IMAGE.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        final java.awt.geom.Point2D START_INNER_BACKGROUND = new java.awt.geom.Point2D.Float(0, 0);
        final java.awt.geom.Point2D STOP_INNER_BACKGROUND = new java.awt.geom.Point2D.Float(0, HEIGHT);
        final float[] FRACTIONS_INNER_BACKGROUND =
                {
                        0.0f,
                        1.0f
                };
        final Color[] COLORS_INNER_BACKGROUND =
                {
                        new Color(0x3D3A31),
                        new Color(0x232520)
                };
        final LinearGradientPaint GRADIENT_INNER_BACKGROUND = new LinearGradientPaint(START_INNER_BACKGROUND, STOP_INNER_BACKGROUND, FRACTIONS_INNER_BACKGROUND, COLORS_INNER_BACKGROUND);
        g2.setPaint(GRADIENT_INNER_BACKGROUND);
        g2.fill(new java.awt.geom.Rectangle2D.Float(0, 0, WIDTH, HEIGHT));
        // Highlight
        g2.setColor(HIGHLIGHT_COLOR_TOP);
        g2.drawLine(0, 0, WIDTH, 0);
        g2.setColor(HIGHLIGHT_COLOR_BOTTOM);
        g2.drawLine(0, HEIGHT, WIDTH, HEIGHT);
        g2.dispose();
        return IMAGE;
    }

    private void paintSegment(Graphics2D g, boolean active, Point... points) {
        if (points != null) {
            g.setColor(active ? activeSegmentColor : inactiveSegmentColor);
            Polygon polygon = new Polygon();
            for (Point point : points) {
                polygon.addPoint(point.x, point.y);
            }
            g.fillPolygon(polygon);
        }
    }

    private static Point[] getPoints(String coded) {
        if(coded.length() == 0)
        {
            return null;
        }
        java.util.List<Point> pointList = new ArrayList<Point>();
        String[] points = coded.split(";");
        for (String point : points) {
            String[] pointCoordinates = point.split(",");
            pointList.add(new Point(Integer.parseInt(pointCoordinates[0]), Integer.parseInt(pointCoordinates[1])));
        }
        return pointList.toArray(new Point[pointList.size()]);
    }
}

To use this, you need an alfabet definition that defines which segments should light up for a given character:

public enum SixteenSegmentAlfabet {
    A(new char[]{'a', 'A'}, 1, 2, 3, 7, 8, 9, 10, 14),
    B(new char[]{'b', 'B'}, 1, 2, 5, 7, 9, 12, 14, 15, 16),
    C(new char[]{'c', 'C'}, 1, 2, 3, 10, 15, 16),
    D(new char[]{'d', 'D'}, 1, 2, 5, 7, 12, 14, 15, 16),
    E(new char[]{'e', 'E'}, 1, 2, 3, 8, 9, 10, 15, 16),
    F(new char[]{'f', 'F'}, 1, 2, 3, 8, 10),
    G(new char[]{'g', 'G'}, 1, 2, 3, 9, 10, 14, 15, 16),
    H(new char[]{'h', 'H'}, 3, 7, 8, 9, 10, 14),
    I(new char[]{'i', 'I'}, 1, 2, 5, 12, 15, 16),
    J(new char[]{'j', 'J'}, 7, 10, 14, 15, 16),
    K(new char[]{'k', 'K'}, 3, 6, 8, 10, 13),
    L(new char[]{'l', 'L'}, 3, 10, 15, 16),
    M(new char[]{'m', 'M'}, 3, 4, 6, 7, 10, 14),
    N(new char[]{'n', 'N'}, 3, 4, 7, 10, 13, 14),
    O(new char[]{'o', 'O'}, 1, 2, 3, 7, 10, 14, 15, 16),
    P(new char[]{'p', 'P'}, 1, 2, 3, 6, 8, 10),
    Q(new char[]{'q', 'Q'}, 1, 2, 3, 7, 10, 13, 14, 15, 16),
    R(new char[]{'r', 'R'}, 1, 2, 3, 7, 8, 9, 10, 13),
    S(new char[]{'s', 'S'}, 1, 2, 3, 8, 9, 14, 15, 16),
    T(new char[]{'t', 'T'}, 1, 2, 5, 12),
    U(new char[]{'u', 'U'}, 3, 7, 10, 14, 15, 16),
    V(new char[]{'v', 'V'}, 3, 6, 10, 11),
    W(new char[]{'w', 'W'}, 3, 7, 10, 11, 13, 14),
    X(new char[]{'x', 'X'}, 4, 6, 11, 13),
    Y(new char[]{'y', 'Y'}, 4, 6, 12),
    Z(new char[]{'z', 'Z'}, 1, 2, 6, 11, 15, 16),
    BLANK(new char[]{' '}, null),
    ZERO(new char[]{'0'}, 1, 2, 3, 4, 7, 10, 13, 14, 15, 16),
    ONE(new char[]{'1'}, 7, 14),
    TWO(new char[]{'2'}, 1, 2, 7, 8, 9, 10, 15, 16),
    THREE(new char[]{'3'}, 1, 2, 7, 9, 14, 15, 16),
    FOUR(new char[]{'4'}, 3, 7, 8, 9, 14),
    FIVE(new char[]{'5'}, 1, 2, 3, 8, 13, 15, 16),
    SIX(new char[]{'6'}, 1, 2, 3, 8, 9, 10, 14, 15, 16),
    SEVEN(new char[]{'7'}, 1, 2, 7, 14),
    EIGHT(new char[]{'8'}, 1, 2, 3, 7, 8, 9, 10, 14, 15, 16),
    NINE(new char[]{'9'}, 1, 2, 3, 7, 8, 9, 14, 15, 16);
    private final Integer[] segments;
    private final char[] characters;

    public static final Map<Character, SixteenSegmentAlfabet> MAP;

    static {
        MAP = new HashMap<Character, SixteenSegmentAlfabet>(36);
        for (SixteenSegmentAlfabet alfabet : SixteenSegmentAlfabet.values()) {
            for (char character : alfabet.characters) {
                MAP.put(character, alfabet);
            }
        }
    }

    SixteenSegmentAlfabet(char[] characters, Integer... segments) {
        this.characters = characters;
        this.segments = segments;
    }

    public Integer[] getSegments() {
        return segments;
    }
}

To finish, the component capable of showing a String merely groups together a couple of the character components:

public class MultiCharacterDisplay extends JComponent {
    private static final int GAP = 5;
    private int segmentDisplayCount;
    private List<SixteenSegmentDisplay> segmentDisplays;

    public MultiCharacterDisplay(int segmentDisplayCount) {
        this(segmentDisplayCount, Color.red);
    }

    public MultiCharacterDisplay(int segmentDisplayCount, Color segmentColor) {
        this.segmentDisplayCount = segmentDisplayCount;
        segmentDisplays = new ArrayList<SixteenSegmentDisplay>(segmentDisplayCount);
        setLayout(new FlowLayout(FlowLayout.LEFT, GAP, 0));
        for (int i = 0; i < segmentDisplayCount; i++) {
            SixteenSegmentDisplay display = new SixteenSegmentDisplay();
            display.setSegmentColor(segmentColor);
            segmentDisplays.add(display);
            add(display);
        }
    }

    public void setText(String text) {
        if (text.length() > segmentDisplayCount) {
            text = text.substring(0, segmentDisplayCount);
        }
        int i = 0;
        for (char c : text.toCharArray()) {
            segmentDisplays.get(i++).setCharacter(c);
        }
    }
}

All in all, it was a nice exercise :) . If I find some more time, I’ll try and build some sort of marquee with it.

No Comments

Code Kata: BBCode

Here’s a nice code kata: convert BBCode to HTML. So [b]test[/b] becomes <strong>test</strong>, etc… Provide tags for all the basic BBCode tags: i, u, b, img, url, li, ol, ul and newlines.

For you cheaters, here’s my implementation, using regex:

import org.springframework.web.util.HtmlUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BBCodeConverter {

    public static String convertString(String bbCodeString)
    {
        bbCodeString = HtmlUtils.htmlEscape(bbCodeString);
        for(BBCodeTag tag : BBCodeTag.values())
        {
            bbCodeString = tag.convert(bbCodeString);
        }
        return bbCodeString;
    }

    private enum BBCodeTag
    {
        B("\\[b\\](.*?)\\[/b\\]", "<strong>$1</strong>"),
        I("\\[i\\](.*?)\\[/i\\]", "<em>$1</em>"),
        U("\\[u\\](.*?)\\[/u\\]", "<span style=\"text-decoration:underline\">$1</span>"),
        NEWLINE("\n", "<br/>"),
        IMG("\\[img\\](.*?)\\[/img\\]", "<img src=\"$1\"></img>"),
        URL("\\[url=(.*?)\\](.*?)\\[/url\\]", "<a href=\"$1\">$2</a>"),
        UL("\\[ul\\](.*?)\\[/ul\\]","<ul>$1</ul>"),
        OL("\\[ol\\](.*?)\\[/ol\\]","<ol>$1</ol>"),
        LI("\\[li\\](.*?)\\[/li\\]","<li>$1</li>"),
        SIZE("\\[size=(.*?)\\](.*?)\\[/size\\]","<span style=\"font-size:$1%\">$2</span>"),
        COLOR("\\[color=(.*?)\\](.*?)\\[/color\\]","<span style=\"color:$1\">$2</span>")
        ;
        private String tagPattern;
        private String htmlConversion;

        private BBCodeTag(String tagPattern, String htmlConversion) {
            this.htmlConversion = htmlConversion;
            this.tagPattern = tagPattern;
        }

        public String convert(String bbCodeTag)
        {
            Pattern pattern = Pattern.compile(tagPattern);
            Matcher matcher = pattern.matcher(bbCodeTag);
            String replaced = matcher.replaceAll(htmlConversion);
            return replaced;
        }
    }
}

4 Comments

Using JIRA efficiently

Let’s face it, there is only one issue tracker worth using, and that’s JIRA. Believe me, I’ve used quite a few (Mantis, Savannah, Trac, Bugzilla, FogBugz, …) and none of the other have the same power and user friendliness like JIRA.

JIRA also has a ton of plugins, a lot of which are free. And even if they aren’t, the investment usually repays itself within months. The ROI is incredible. Today, I’ll shed some light on the plugins I usually install on any JIRA I get to do administration on.

Tempo

JIRA’s worklog system is great. However, once you start to use Tempo, you’ll never use the standard ‘Log work’ again. Tempo is a complete time tracking system, including holiday tracking, non-issue related time tracking, detailed reporting and integration with ERP and CRM systems (although I haven’t used that functionality yet). You do need to do some configuration, but once you set it up, it’s invaluable. Using Tempo promotes the idea that all work done on a project should be issue related, an idea I support wholeheartedly.
Price: free when using the Jira Starter license ($10 donation is appreciated), $300 for 25 users to $2000 for unlimited users. So if you can invoice 1/2 hour per person a month more because of accurate time tracking and you’re invoicing 25 people at $50/h, you have a ROI of more than 200% in just one month.

Greenhopper

An official Atlassian plugin. If you’re using Scrum or any other Agile system and you’re using JIRA, you really need this plugin. It’s invaluable when doing planning and following up sprints. Even if you’re not going agile, this plugin can really help keeping the people in the project informed.
Price: $10 for 10 users (Starter license), $600 for 25 users to $4000 for unlimited users. Usually repays itself through increased team productivity and follow-up in a couple of months.

FishEye plugin

As far as online source control browser tools go, FishEye is a good as it gets. Atlassian provides great integration (off course, FishEye is one of their products). Can be replaced with the specific source control integration plugins, but if you can afford FishEye, use it.
Price: free (FishEye not included)

JIRA Labels

Enabled users to easily add metadata tags to issues, facilitating searches.
Price: free

3 Comments

Compiling JasperReports with Maven

For those working with JasperReports, this is the plugin config you can use to compile jrxml files:

...
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>target/generated-jasper</directory>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jasperreports-maven-plugin</artifactId>
            <version>1.0-beta-2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile-reports</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outputDirectory>${project.build.directory}/generated-jasper</outputDirectory>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>net.sf.jasperreports</groupId>
                    <artifactId>jasperreports</artifactId>
                    <version>3.7.2</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
...

If you’re using groovy as a template language in your JasperReport reports, don’t forget to add the groovy-all 1.5.5 dependency to your plugin and add this to your plugin configuration:

<compiler>net.sf.jasperreports.compilers.JRGroovyCompiler</compiler>

Have fun reporting!

2 Comments