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

DataBoundConstructors

Hudson uses Stapler 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 @DataBoundConstructor, Stapler will bind fields from a JSON object by matching the field name to the constructor parameter name. If a parameter also has a @DataBoundConstructor, then Stapler will recurse to construct this child object from the child JSON object.

Note: 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 ChildImpl classes all implementing Child, and all with @DataBoundConstructor and Parent's constructor has a parameter which takes Child... However, plans are afoot to fix this!

JavaNCSSPublisher

Publishers in Hudson must have a Descriptor, 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 DescriptorImpl and there is normally a static field of the publisher DESCRIPTOR that holds the Descriptor singleton. 99.995% of the time, you will want your publisher to have a @DataBoundConstructor, so without further delay, here is the publisher:

package hudson.plugins.javancss;

import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSet;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Descriptor;
import hudson.plugins.helpers.AbstractPublisherImpl;
import hudson.plugins.helpers.Ghostwriter;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

public class JavaNCSSPublisher extends AbstractPublisherImpl {

private String reportFilenamePattern;

@DataBoundConstructor
public JavaNCSSPublisher(String reportFilenamePattern) {
reportFilenamePattern.getClass();
this.reportFilenamePattern = reportFilenamePattern;
}

public String getReportFilenamePattern() {
return reportFilenamePattern;
}

public boolean needsToRunAfterFinalized() {
return false;
}

public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public Descriptor<Publisher> getDescriptor() {
return DESCRIPTOR;
}

public Action getProjectAction(AbstractProject<?, ?> project) {
return new JavaNCSSProjectIndividualReport(project);
}

protected Ghostwriter newGhostwriter() {
return new JavaNCSSGhostwriter(reportFilenamePattern);
}

public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {

private DescriptorImpl() {
super(JavaNCSSPublisher.class);
}

public String getDisplayName() {
return "Publish " + PluginImpl.DISPLAY_NAME;
}

public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return !MavenModuleSet.class.isAssignableFrom(aClass)
&& !MavenModule.class.isAssignableFrom(aClass);
}

}

}

By inheriting from AbstractPublisherImpl we get a lot of the work done for us, all we really need to do is provide a Ghostwriter and the project level report (JavaNCSSProjectIndividualReport which we will see later

We need hudson/plugins/javancss/JavaNCSSPublisher/config.jelly to allow the user to specify the report file name pattern... not much to this, so here it is:

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health">
<f:entry title="JavaNCSS xml report pattern"
description="
This is a file name pattern that can be used to locate the JavaNCSS xml report files
(for example with Maven2 use &lt;b&gt;**/target/javancss-raw-report.xml&lt;/b&gt;).
The path is relative to &lt;a href='ws/'&gt;the module root&lt;/a&gt; unless
you are using Subversion as SCM and have configured multiple modules, in which case it is
relative to the workspace root.&lt;br/&gt;
JavaNCSS must be configured to generate XML reports for this plugin to function.
">
<f:textbox name="javancss.reportFilenamePattern" value="${instance.reportFilenamePattern}"/>
</f:entry>
</j:jelly>

JavaNCSSMavenPublisher

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 pom.xml.

We could, if necessary, tweak the pom.xml 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. Note: 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.

Ok, so here is the Maven publisher...

package hudson.plugins.javancss;

import hudson.maven.*;
import hudson.model.Action;
import hudson.plugins.helpers.AbstractMavenReporterImpl;
import hudson.plugins.helpers.Ghostwriter;
import net.sf.json.JSONObject;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.File;

public class JavaNCSSMavenPublisher extends AbstractMavenReporterImpl {

@DataBoundConstructor
public JavaNCSSMavenPublisher() {
}

private static final String PLUGIN_GROUP_ID = "org.codehaus.mojo";
private static final String PLUGIN_ARTIFACT_ID = "javancss-maven-plugin";
private static final String PLUGIN_EXECUTE_GOAL = "report";

protected boolean isExecutingMojo(MojoInfo mojo) {
return mojo.pluginName.matches(PLUGIN_GROUP_ID, PLUGIN_ARTIFACT_ID)
&& PLUGIN_EXECUTE_GOAL.equals(mojo.getGoal());
}

protected Ghostwriter newGhostwriter(MavenProject pom, MojoInfo mojo) {
// get the name of the xml report

String tempFileName;
try {
tempFileName = mojo.getConfigurationValue("tempFileName", String.class);
} catch (ComponentConfigurationException e) {
tempFileName = null;
}
if (tempFileName == null) {
// the name was not overridden in the pom, so use the default
tempFileName = "javancss-raw-report.xml";
}

// get the xml output directory

File baseDir = pom.getBasedir().getAbsoluteFile();
File xmlOutputDirectory;
try {
xmlOutputDirectory = mojo.getConfigurationValue("xmlOutputDirector", File.class);
} catch (ComponentConfigurationException e) {
xmlOutputDirectory = null;
}
if (xmlOutputDirectory == null) {
// the directory was not overridden in the pom, so use the default
xmlOutputDirectory = new File(pom.getBuild().getDirectory());
}

String searchPath;
String targetPath = makeDirEndWithFileSeparator(fixFilePathSeparator(xmlOutputDirectory.getAbsolutePath()));
String baseDirPath = makeDirEndWithFileSeparator(fixFilePathSeparator(baseDir.getAbsolutePath()));

if (targetPath.startsWith(baseDirPath)) {
searchPath = targetPath.substring(baseDirPath.length()) + tempFileName;
} else {
// we have no clue where this is, so default to anywhere
searchPath = "**/" + tempFileName;
}

return new JavaNCSSGhostwriter(searchPath, targets);
}

private String makeDirEndWithFileSeparator(String baseDirPath) {
if (!baseDirPath.endsWith(File.separator)) {
baseDirPath += File.separator;
}
return baseDirPath;
}

private String fixFilePathSeparator(String path) {
return path.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);
}

public Action getProjectAction(MavenModule module) {
for (MavenBuild build : module.getBuilds()) {
if (build.getAction(JavaNCSSBuildIndividualReport.class) != null) {
return new JavaNCSSProjectIndividualReport(module);
}
}
return null;
}

public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

/**
* {@inheritDoc}
*/
public MavenReporterDescriptor getDescriptor() {
return DESCRIPTOR; //To change body of implemented methods use File | Settings | File Templates.
}

public static final class DescriptorImpl extends MavenReporterDescriptor {

/**
* Do not instantiate DescriptorImpl.
*/
private DescriptorImpl() {
super(JavaNCSSMavenPublisher.class);
}

/**
* {@inheritDoc}
*/
public String getDisplayName() {
return "Publish " + PluginImpl.DISPLAY_NAME;
}

public MavenReporter newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return req.bindJSON(JavaNCSSMavenPublisher.class, formData);
}

}

}

The only complexity is in the newGhostwriter method, where we have to find out what the configuration for the maven plugin is in order to find the xml report.

We will need a hudson/plugins/javancss/JavaNCSSMavenPublisher/config.jelly file for this publisher... not much to this one as we get what we need from the pom.xml

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health">
</j:jelly>

The reports

We have a total of four reports to generate:

  • Individual build report - used for freestyle and maven 2 modules
  • Individual project report - used for freestyle and maven 2 modules
  • Aggregated build report - used for maven 2 projects
  • Aggregated project report - used for maven 2 projects

To keep to our DRY principals, we'll use some abstract classes to pull together the common code. First, AbstractBuildReport which will form the basis of our build reports:

package hudson.plugins.javancss;

import hudson.model.AbstractBuild;
import hudson.plugins.helpers.AbstractBuildAction;
import hudson.plugins.javancss.parser.Statistic;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import java.io.IOException;
import java.util.Collection;

public abstract class AbstractBuildReport<T extends AbstractBuild<?, ?>> extends AbstractBuildAction<T> {
private final Collection<Statistic> results;
private final Statistic totals;

public AbstractBuildReport(Collection<Statistic> results) {
this.results = results;
this.totals = Statistic.total(results);
}

public Collection<Statistic> getResults() {
return results;
}

public Statistic getTotals() {
return totals;
}

public String getSummary() {
AbstractBuild<?, ?> prevBuild = getBuild().getPreviousBuild();
while (prevBuild != null && prevBuild.getAction(getClass()) == null) {
prevBuild = prevBuild.getPreviousBuild();
}
if (prevBuild == null) {
return totals.toSummary();
} else {
AbstractBuildReport action = prevBuild.getAction(getClass());
return totals.toSummary(action.getTotals());
}
}

public String getIconFileName() {
return PluginImpl.ICON_FILE_NAME;
}

public String getDisplayName() {
return PluginImpl.DISPLAY_NAME;
}

public String getUrlName() {
return PluginImpl.URL;
}

public boolean isGraphActive() {
return false;
}

}

Similarly, we have AbstractProjectReport which will be used for project reports:

package hudson.plugins.javancss;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.ProminentProjectAction;
import hudson.plugins.helpers.AbstractProjectAction;
import hudson.plugins.javancss.parser.Statistic;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

public abstract class AbstractProjectReport<T extends AbstractProject<?, ?>>
extends AbstractProjectAction<T>
implements ProminentProjectAction {

public AbstractProjectReport(T project) {
super(project);
}

public String getIconFileName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {

final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.ICON_FILE_NAME;
}
}
return null;
}

public String getDisplayName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.DISPLAY_NAME;
}
}
return null;
}

public String getUrlName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild(); build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.URL;
}
}
return null;
}

public String getSearchUrl() {
return PluginImpl.URL;
}

public boolean isGraphActive() {
return false;
}

public Collection<Statistic> getResults() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return action.getResults();
}
}
return Collections.emptySet();
}

public Statistic getTotals() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return action.getTotals();
}
}
return null;
}

protected abstract Class<? extends AbstractBuildReport> getBuildActionClass();

}

Now that we have these abstract classes, we can roll out our concrete reports. First the individual build report:

package hudson.plugins.javancss;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import hudson.maven.AggregatableAction;
import hudson.maven.MavenAggregatedReport;
import hudson.maven.MavenBuild;
import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSetBuild;
import hudson.model.AbstractBuild;
import hudson.plugins.javancss.parser.Statistic;

public class JavaNCSSBuildIndividualReport extends AbstractBuildReport<AbstractBuild<?, ?>>
implements AggregatableAction {

public JavaNCSSBuildIndividualReport(Collection<Statistic> results) {
super(results);
}

@Override
public synchronized void setBuild(AbstractBuild<?, ?> build) {
super.setBuild(build);
if (this.getBuild() != null) {
for (Statistic r : getResults()) {
r.setOwner(this.getBuild());
}
}
}

public MavenAggregatedReport createAggregatedAction(MavenModuleSetBuild build,
Map<MavenModule,
List<MavenBuild>> moduleBuilds) {
return new JavaNCSSBuildAggregatedReport(build, moduleBuilds);
}

}

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:

package hudson.plugins.javancss;

import hudson.maven.*;
import hudson.model.Action;
import hudson.plugins.javancss.parser.Statistic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class JavaNCSSBuildAggregatedReport
extends AbstractBuildReport<MavenModuleSetBuild>
implements MavenAggregatedReport {

public JavaNCSSBuildAggregatedReport(MavenModuleSetBuild build,
Map<MavenModule, List<MavenBuild>> moduleBuilds) {
super(new ArrayList<Statistic>());
setBuild(build);
}

public synchronized void update(Map<MavenModule, List<MavenBuild>> moduleBuilds,
MavenBuild newBuild) {
JavaNCSSBuildIndividualReport report =
newBuild.getAction(JavaNCSSBuildIndividualReport.class);

if (report != null) {
Collection<Statistic> u = Statistic.merge(report.getResults(), getResults());
getResults().clear();
getResults().addAll(u);
getTotals().add(report.getTotals());
}
}

public Class<? extends AggregatableAction> getIndividualActionType() {
return JavaNCSSBuildIndividualReport.class;
}

public Action getProjectAction(MavenModuleSet moduleSet) {
for (MavenModuleSetBuild build : moduleSet.getBuilds()) {
if (build.getAction(JavaNCSSBuildAggregatedReport.class) != null) {
return new JavaNCSSProjectAggregatedReport(moduleSet);
}
}
return null;
}

}

This report is only used for the Maven2 project types. The two key methods are update which is called as each module completes, and getProjectAction which should return the project level aggregated report if there is a report to show. At this point we're ready for the individual project report:

package hudson.plugins.javancss;

import hudson.model.AbstractProject;
import hudson.model.Actionable;
import hudson.model.ProminentProjectAction;
import hudson.model.AbstractBuild;
import hudson.util.ChartUtil;
import hudson.util.DataSetBuilder;
import hudson.plugins.javancss.parser.Statistic;

import java.util.Collection;

public class JavaNCSSProjectIndividualReport
extends AbstractProjectReport<AbstractProject<?, ?>>
implements ProminentProjectAction {

public JavaNCSSProjectIndividualReport(AbstractProject<?, ?> project) {
super(project);
}

protected Class<? extends AbstractBuildReport> getBuildActionClass() {
return JavaNCSSBuildIndividualReport.class;
}
}

Don't repeat ourselves comes in handy here as essentially all the work has been done for us!. The project aggregated report:

package hudson.plugins.javancss;

import hudson.model.Actionable;
import hudson.model.ProminentProjectAction;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
import hudson.plugins.javancss.parser.Statistic;

public class JavaNCSSProjectAggregatedReport
extends AbstractProjectReport<MavenModuleSet>
implements ProminentProjectAction {

public JavaNCSSProjectAggregatedReport(MavenModuleSet project) {
super(project);
}

protected Class<? extends AbstractBuildReport> getBuildActionClass() {
return JavaNCSSBuildAggregatedReport.class;
}
}

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: hudson/plugins/javancss/AbstractBuildReport/reportDetail.jelly and hudson/plugins/javancss/AbstractProjectReport/reportDetail.jelly. Here they are:

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<h1>Results</h1>

<table border="1px" class="pane sortable">
<thead>
<tr>
<th>Package</th>
<th title="Class count">Classes</th>
<th title="Function count">Functions</th>
<th title="Javadoc count">Javadocs</th>
<th title="Non-commenting Source Statements">NCSS</th>
<th title="Javadoc line count">JLC</th>
<th title="Single-line comment line count">SLCLC</th>
<th title="Multi-line comment line count">MLCLC</th>
</tr>
</thead>
<tfoot>
<tr>
<th align="left">Totals</th>
<th align="right">${it.totals.classes}</th>
<th align="right">${it.totals.functions}</th>
<th align="right">${it.totals.javadocs}</th>
<th align="right">${it.totals.ncss}</th>
<th align="right">${it.totals.javadocLines}</th>
<th align="right">${it.totals.singleCommentLines}</th>
<th align="right">${it.totals.multiCommentLines}</th>
</tr>
</tfoot>
<tbody>
<j:forEach var="r" items="${it.results}">
<tr>
<th align="left">${r.name}</th>
<td align="right">${r.classes}</td>
<td align="right">${r.functions}</td>
<td align="right">${r.javadocs}</td>
<td align="right">${r.ncss}</td>
<td align="right">${r.javadocLines}</td>
<td align="right">${r.singleCommentLines}</td>
<td align="right">${r.multiCommentLines}</td>
</tr>
</j:forEach>
</tbody>
</table>
</j:jelly>

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

Making a plugin

Now we are ready to make our plugin.... for this we need a class that extends hudson.Plugin and registers our publisher's descriptors with the appropriate lists... here it is:

package hudson.plugins.javancss;

import hudson.Plugin;
import hudson.maven.MavenReporters;
import hudson.tasks.BuildStep;

public class PluginImpl extends Plugin {
public void start() throws Exception {
BuildStep.PUBLISHERS.add(JavaNCSSPublisher.DESCRIPTOR);
MavenReporters.LIST.add(JavaNCSSMavenPublisher.DESCRIPTOR);
}

public static String DISPLAY_NAME = "Java NCSS Report";
public static String GRAPH_NAME = "Java NCSS Trend";
public static String URL = "javancss";
public static String ICON_FILE_NAME = "graph.gif";
}

And that's pretty much it... we should have a working plugin

Finishing touches

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 :-(

8

View comments

  1. Hi,

    I follow this tutorial and i try to run it.
    But i use the last version of Hudson (1.299) and the package hudson.maven seems to be deleted ... so a lot of error appear in the plugin project.

    Thanks for your help

    Laure

    ReplyDelete
  2. You need to write all the classes (e.g. even BuildProxy) that this tutorial mentions.

    For an example of this tutorial, checkout the latest version of the javancss plugin from the hudson svn server. It has all the working code from this tutorial and it compiles with hudson 1.299

    ReplyDelete
  3. Can you indicate me wich url i must use for checkout the project from eclipse ?

    Thanks for your help and for this very good tutorial.

    Laure

    ReplyDelete
  4. I have search in the java documentation of hudson core 1.299 and i did not found the package hudson.maven....

    ReplyDelete
  5. Finally, in order to resolve my problem I have addded a reference in my classpath to the java hudson core 1.279. With it the plugin run correctly.

    Again thanks for this very good tutorial !!

    ReplyDelete
  6. Hello there

    I would like to use your plugin for Flex development. I found your plugin on the following site:

    http://opensource.adobe.com/wiki/display/flexpmd/FlexMetrics

    There is a nice trend graph image shown there so I assumed that I can display a graph of my results in hudson. However you mention in your 7th blog-post, that this graph is not yet implemented. Is there a way to display a trend graph?

    The graph: http://opensource.adobe.com/wiki/download/attachments/39617059/metrics-trend.png

    ReplyDelete
  7. I don't know anything about that adobe page

    ReplyDelete

  8. I appreciate your tutorial! Regarding the plugin I'm creating, my requirements must display checkboxes on the project page, which I am trying to bind to an Action. I've looked into the ProminentProjectAction interface, but to no avail.
    Do you know of a sure way to bind data from a floatingBox.jelly to a Java object?

    ReplyDelete
The costs of guarding logging with Apache Log4J version 2
The costs of guarding logging with Apache Log4J version 2
The costs of other logging frameworks
The costs of other logging frameworks
The costs of Java Utils Logging
Version numbers
Log4j 2.0 Async loggers and immutability
Log4j 2.0 Async loggers and immutability
If you don&#39;t like Maven, you would not be a good manager...
If you don&#39;t like Maven, you would not be a good manager...
Jenkins&#39; Maven job type considered evil
Jenkins&#39; Maven job type considered evil
6
About test coverage
Note to self: Updated usejava BASH function for MacOSX
Note to self: Updated usejava BASH function for MacOSX
Note to self: Mac Keyboard and ‘fancy’ characters
Note to self: Mac Keyboard and ‘fancy’ characters
The Cross-Build Injection Attack Fallacy
The Cross-Build Injection Attack Fallacy
Do you want to become a Maven Committer?
Do you want to become a Maven Committer?
3
Note to self: Java Generics - The Get and Put principle
Note to self: Java Generics - The Get and Put principle
How to add Mac-like shadow to your screenshots
1
Java Serialization and locks
Java Serialization and locks
Open Source: the Meritocracy vs the Circle of Trust
3
The worst Unit Tests I ever saw
1
Backwards compatibility
Backwards compatibility
2
Continuously deploy your java apps to the cloud
Deploy your java apps to the cloud
1
Quick and very Dirty Mavenizer
Quick and very Dirty Mavenizer
My magic Maven and ANT version selection bash functions (for the Mac)
My magic Maven and ANT version selection bash functions (for the Mac)
2
Handy bash script to fix a pom's SCM section to the current Subversion location
Handy bash script to fix a pom's SCM section to the current Subversion location
A handy string for when testing i18n
A handy string for when testing i18n
The Clean Coder: QA or When do you flip a pancake?
The Clean Coder: QA or When do you flip a pancake?
3D TV, level headed people will say it looks fine, looks bad lying down
3D TV, level headed people will say it looks fine, looks bad lying down
Quick and dirty remove -SNAPSHOTS from your local maven repository
Quick and dirty remove -SNAPSHOTS from your local maven repository
2
Keeping Hudson configuration and data in SVN
Keeping Hudson configuration and data in SVN
5
Easy parallelization with Bash in Linux
Easy parallelization with Bash in Linux
Crazy counting...
Crazy counting...
Don't Do What Donny Don't Does #1 - Seam @Synchronized and synchronized methods
Don't Do What Donny Don't Does #1 - Seam @Synchronized and synchronized methods
OpenEjb, Jetty and Maven - Transaction Management
OpenEjb, Jetty and Maven - Transaction Management
10
Review Board and Subversion Hooks
Review Board and Subversion Hooks
2
HowTo: CentOS 5, Apache 2.2, Subversion 1.5 with ActiveDirectory Authentication
HowTo: CentOS 5, Apache 2.2, Subversion 1.5 with ActiveDirectory Authentication
2
Apache 2.2 Authentication with Active Directory via Cyrus SASL
Apache 2.2 Authentication with Active Directory via Cyrus SASL
Acer Aspire One, Huawei E169G and Three IR
Acer Aspire One, Huawei E169G and Three IR
3
Writing a Hudson plugin (Part 7 - Putting it all together)
Writing a Hudson plugin (Part 7 - Putting it all together)
8
More praise for Hudson
More praise for Hudson
Writing a Hudson plugin (Part 6 - Parsing the results)
Writing a Hudson plugin (Part 6 - Parsing the results)
1
Writing a Hudson plugin (Part 5½ - Typos corrected)
Writing a Hudson plugin (Part 5½ - Typos corrected)
1
Writing a Hudson plug-in (Part 5 – Reporting)
Writing a Hudson plug-in (Part 5 – Reporting)
8
Writing a Hudson plug-in (Part 4 – Abstract Publishers and MavenReporters)
Writing a Hudson plug-in (Part 4 – Abstract Publishers and MavenReporters)
1
Writing a Hudson plug-in (Part 3 – Subcontracting for Publisher and MavenReporter)
Writing a Hudson plug-in (Part 3 – Subcontracting for Publisher and MavenReporter)
2
Writing a Hudson plug-in (Part 2 – Understanding m2 and freestyle projects)
2
Writing a Hudson plug-in (Part 1 – Preparation)
6
JPA, equals() and hashCode()
JPA, equals() and hashCode()
1
Combining Hibernate and Facelets with Maven, Netbeans and Glassfish
Combining Hibernate and Facelets with Maven, Netbeans and Glassfish
Sventon and Hudson
Sventon and Hudson
2
Hudson Coverage plugin
Hudson Coverage plugin
1
Stumbled upon
Stumbled upon
JAX-WS 2.1 madness
JAX-WS 2.1 madness
4
pom for jsf-comp.sf.net's chartcreator
pom for jsf-comp.sf.net's chartcreator
1
Near minimal Maven 2 pom.xml for Jetty development of Facelets applications using JSF RI 1.2
Near minimal Maven 2 pom.xml for Jetty development of Facelets applications using JSF RI 1.2
1
JPA Entity exerciser
JPA Entity exerciser
4
hashCode() pitfalls with HashSet and HashMap
hashCode() pitfalls with HashSet and HashMap
4
How em.merge actually works
How em.merge actually works
4
Another view on unit testing annotated entities
Another view on unit testing annotated entities
Unit testing of annotated classes (part 2)
Unit testing of annotated classes (part 2)
Unit testing of annotated classes
Unit testing of annotated classes
JUnit testing strategies for EJB3 entities and beans
JUnit testing strategies for EJB3 entities and beans
6
Nope, it's in the spec
Nope, it's in the spec
application.xml related stuff
application.xml related stuff
Rolling your own ELResolver
Rolling your own ELResolver
2
Note to self
Note to self
Keeping up with the latest glassfish builds
Keeping up with the latest glassfish builds
About Me
About Me
Labels
Useful links
Blog Archive
Loading
Dynamic Views theme. Powered by Blogger. Report Abuse.