Archive for category Maven
Why I never use the Maven release plugin
Posted by Lieven Doclo in Maven on 2012/01/23
Just about every 6 months or so an article appears cursing Maven, attracting both proponents as opponents to Maven and Ant. While it’s real fun to watch (I really get a laugh when people start to advocate the return to Ant), most of the time it’s always the same arguments. Maven lacks flexibility, the plugin system sucks (when will people learn to use plugin versions…), you can’t use scripting and the all time favorite: the release plugin sucks. Well, I am a Maven addict and I’m happy to say: yes, I agree, the release plugin sucks. Big time. But here’s something you may have forgotten: you don’t need it! Even more: you shouldn’t use it. Read the rest of this entry »
Excluding a dependency throughout the POM a.k.a. “the version 99 fix”
Posted by Lieven Doclo in Maven on 2010/02/09
Although I am a Maven fanatic, that doesn’t mean I don’t get mad because of some missing features in Maven. One of those is general exclusion of dependencies.
Most projects still use either log4j or commons-logging for their logging needs. While there is nothing wrong with that, I prefer SLF4J. SLF4J also has these really nice adapters for libraries that use those other logging frameworks so everything gets logged through it anyway. And here lies the problem. When you want to use these adapter classes, you need to exclude the real log4j and commons logging libraries. When you only have like 3 dependencies, that’s not a big issue. If you have 50+, it is. So you’re left with 2 choices:
- Exclude log4j and commons logging from every dependency (you’ll need to look at the POM to see which one it uses)
- Use the somewhat ugly, but effective version 99-does-not-exist fix
Having fun with JSON and DbUnit
Posted by Lieven Doclo in Maven, Testing on 2010/02/05
DbUnit is one of those invaluable frameworks in my toolbox. The only thing I don’t like are the XML dataset formats. Having played around with JSON for the last couple of weeks, I decided a nice exercise would be to create a JSON-based dataset.
I started off with the dataset format I’d like to use
{
"mytable":
[
{
"field1": "value1",
"field2": "value2"
},
{
"field1": "value3",
}
]
}
This would create 2 rows in the mytable database table, with the second row having its field2 set to NULL. Writing the code for DbUnit to be able to process this took me about 15 minutes (including unit tests for it). It uses Jackson for the JSON processing. Here it is:
import org.codehaus.jackson.map.ObjectMapper;
import org.dbunit.dataset.*;
import org.dbunit.dataset.datatype.DataType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* DBUnit DataSet format for JSON based datasets. It is similar to the flat XML layout,
* but has some improvements (columns are calculated by parsing the entire dataset, not just
* the first row). It uses Jackson, a fast JSON processor.
* <br/><br/>
* The format looks like this:
* <br/>
* <pre>
* {
* "<table_name>": [
* {
* "<column>":<value>,
* ...
* },
* ...
* ],
* ...
* }
* </pre>
* <br/>
* I.e.:
* <br/>
* <pre>
* {
* "test_table": [
* {
* "id":1,
* "code":"JSON dataset",
* },
* {
* "id":2,
* "code":"Another row",
* }
* ],
* "another_table": [
* {
* "id":1,
* "description":"Foo",
* },
* {
* "id":2,
* "description":"Bar",
* }
* ],
* ...
* }
* </pre>
*
* @author Lieven DOCLO
*/
public class JSONDataSet extends AbstractDataSet {
// The parser for the dataset JSON file
private JSONITableParser tableParser = new JSONITableParser();
// The tables after parsing
private List<ITable> tables;
/**
* Creates a JSON dataset based on a file
* @param file A JSON dataset file
*/
public JSONDataSet(File file) {
tables = tableParser.getTables(file);
}
/**
* Creates a JSON dataset based on an inputstream
* @param is An inputstream pointing to a JSON dataset
*/
public JSONDataSet(InputStream is) {
tables = tableParser.getTables(is);
}
@Override
protected ITableIterator createIterator(boolean reverse) throws DataSetException {
return new DefaultTableIterator(tables.toArray(new ITable[tables.size()]));
}
private class JSONITableParser {
private ObjectMapper mapper = new ObjectMapper();
/**
* Parses a JSON dataset file and returns the list of DBUnit tables contained in
* that file
* @param jsonFile A JSON dataset file
* @return A list of DBUnit tables
*/
public List<ITable> getTables(File jsonFile) {
try {
return getTables(new FileInputStream(jsonFile));
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Parses a JSON dataset input stream and returns the list of DBUnit tables contained in
* that input stream
* @param jsonStream A JSON dataset input stream
* @return A list of DBUnit tables
*/
@SuppressWarnings("unchecked")
public List<ITable> getTables(InputStream jsonStream) {
List<ITable> tables = new ArrayList<ITable>();
try {
// get the base object tree from the JSON stream
Map<String, Object> dataset = mapper.readValue(jsonStream, Map.class);
// iterate over the tables in the object tree
for (Map.Entry<String, Object> entry : dataset.entrySet()) {
// get the rows for the table
List<Map<String, Object>> rows = (List<Map<String, Object>>) entry.getValue();
ITableMetaData meta = getMetaData(entry.getKey(), rows);
// create a table based on the metadata
DefaultTable table = new DefaultTable(meta);
int rowIndex = 0;
// iterate through the rows and fill the table
for (Map<String, Object> row : rows) {
fillRow(table, row, rowIndex++);
}
// add the table to the list of DBUnit tables
tables.add(table);
}
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
return tables;
}
/**
* Gets the table meta data based on the rows for a table
* @param tableName The name of the table
* @param rows The rows of the table
* @return The table metadata for the table
*/
private ITableMetaData getMetaData(String tableName, List<Map<String, Object>> rows) {
Set<String> columns = new LinkedHashSet<String>();
// iterate through the dataset and add the column names to a set
for (Map<String, Object> row : rows) {
for (Map.Entry<String, Object> column : row.entrySet()) {
columns.add(column.getKey());
}
}
List<Column> list = new ArrayList<Column>(columns.size());
// create a list of DBUnit columns based on the column name set
for (String s : columns) {
list.add(new Column(s, DataType.UNKNOWN));
}
return new DefaultTableMetaData(tableName, list.toArray(new Column[list.size()]));
}
/**
* Fill a table row
* @param table The table to be filled
* @param row A map containing the column values
* @param rowIndex The index of the row to te filled
*/
private void fillRow(DefaultTable table, Map<String, Object> row, int rowIndex) {
try {
table.addRow();
// set the column values for the current row
for (Map.Entry<String, Object> column : row.entrySet()) {
table.setValue(rowIndex, column.getKey(), column.getValue());
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
I tested it with 1000+ row dataset and it is quite fast. It’s even documented! Have fun.
Self-signed JAR signing with Maven for webstart – without the keystore
Posted by Lieven Doclo in Maven on 2010/01/25
Anyone reading the above title will have serious doubts about the validity of this article. But let me explain. Mind you, this is an article for development purposes, for production you’ll probably need to have a keystore with a properly signed SSL certificate (then again, most webstart applications are intranet, so why bother).
That the Maven Webstart plugin hosted by Codehaus is ill-documented is a well known fact. However, as with most Maven-related subjects, you can learn a whole lot about its workings by looking at the code, or the examples if they are provided.
A beginner will read the plugin documentation, and then start fidgeting with the Java keytool utility to get the keystore needed for jar signing. Needless to say, the keytool utility isn’t the easiest one of the lot. Probably after about half an hour you’ll have a decent enough keystore, going through about every function of the keytool command-line. Then he’ll utilize the keystore in the plugin (preferably using a relative path).
Why the Codehaus guys haven’t mentioned the easier way on the plugin’s website is beyond me, but here it is:
<plugin>
<groupId>org.codehaus.mojo.webstart</groupId>
<artifactId>webstart-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>jnlp-download-servlet</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectoryName>webstart</outputDirectoryName>
<excludeTransitive>false</excludeTransitive>
<jnlpFiles>
<jnlpFile>
<templateFilename>template.vm</templateFilename>
<outputFilename>myapp.jnlp</outputFilename>
<jarResources>
<jarResource>
<groupId>be.insaneprogramming</groupId>
<artifactId>myapp-gui</artifactId>
<version>${project.version}</version>
<mainClass>be.insaneprogramming.myapp.GUIMain</mainClass>
</jarResource>
</jarResources>
</jnlpFile>
</jnlpFiles>
<sign>
<keystore>/tmp/myappkeystore</keystore>
<keypass>m2m2m2</keypass>
<storepass>m2m2m2</storepass>
<alias>foobar</alias>
<validity>3650</validity>
<dnameCn>Insane Programming</dnameCn>
<dnameOu>Software Development</dnameOu>
<dnameO>Insane Programming</dnameO>
<dnameL>Maldegem</dnameL>
<dnameSt>Oost-Vlaanderen</dnameSt>
<dnameC>BE</dnameC>
<verify>false</verify>
<keystoreConfig>
<delete>true</delete>
<gen>true</gen>
</keystoreConfig>
</sign>
<outputJarVersions>true</outputJarVersions>
<verbose>true</verbose>
<unsign>true</unsign>
<verifyjar>false</verifyjar>
</configuration>
</plugin>
This configuration will create a keystore on the fly and use that one for the signing. The advantage?
- No keystore is needed in the project sourcetree or in a shared location
- No more messing around with the keytool
- Signing information is kept in the POM
- You can reuse information in the POM for the signing (organisation name etc.), or even use Maven properties and profiles to customize the signing without having to resort to multiple keystore or a keystore with multiple aliases.
For example you could do this in the signing configuration:... <validity>${signing.validity}</validity> <dnameCn>${signing.dname.cn}</dnameCn> <dnameOu>${signing.dname.ou}</dnameOu> <dnameO>${signing.dname.o}</dnameO> <dnameL>${signing.dname.l}</dnameL> <dnameSt>${signing.dname.st}</dnameSt> <dnameC>${signing.dname.c}</dnameC> ...where the placeholder information is kept in the company super POM. A lot easier to maintain, I reckon.
- The signature will always be valid when signing (no need to replace keystore to remove warnings)
Maven and Ant guys: you’ll never agree. On anything. Period. Deal with it!
Posted by Lieven Doclo in Maven on 2010/01/02
Every 3 months or so, you’ll see a new article pop up somewhere written by an Ant (mostly, sometimes Gradle or Rake) lover discussing the horrible nature of Maven and how it works.
People, please! How old are you guys? I’ve used both and I like Maven more. Is that a reason for me to post another rant on Ant? Seriously, most rants are one-way. It’s easy to break down a tool based on some criteria, but how about proposing some solutions to problems some Maven users face that you can easily solve in [your favorite buid tool]? Whoops. Negative criticism is easy, isn’t it?
Recently, I read this article. It doesn’t take a genius to discover that this blogger is a Maven hater. Or to put it more bluntly: it doesn’t support the way HE wants to build software. Here’s an idea: try and take every feature you hate in Maven and give a Rake or Ant alternative way. Every single one. I’m waiting for the excuses you’ll throw at me.
Let’s see. According to this blogger, it’s really hard to build a war. Last time I built a WAR with Maven, I just needed to change the packaging to WAR. Okay, you do need to know that the WEB-INF folder goes into src/main/webapp. Is that really that hard? The other rant was about the WAR size. Here he actually has a (albeit minor) point. Maven projects (especially frameworks) really need to learn the value of optional dependencies. But then again, I’d like to see Ant projects solve this issue too without their usual solution: provide a README stating which jars should be included when you want to use certain functionalities.
I’ve heard the statement “Why use an entire toolbox (Maven) when you only need a hammer (Ant)”. Okay, you CAN put a screw in a wall with a hammer. The result ain’t pretty, but it works. Does that mean a hammer is the only tool to use. No. Know when to use a tool. Any tool for that matter. Would you throw the screwdriver out of your toolbox because it can’t handle a nail?
Any of the rants can be translated into either incorrect use of Maven or just plain stupidity (or even lazyness): I can point out a lot of Ant, Gradle or Rake misuse that is equally painful. Dependency resolving takes to much time? Use Artifactory or Nexus internally (it’ll even solve some other problems concerning snapshots). Got problems with plugins? Stop omitting the version number in your POM’s (or use a company-wide POM). Too many unused transitive dependencies that should be optional? Contact the author. Got a problem with XML POM’s? Maven 3 has polyglot functionality.
All I’m saying is: stop ranting. Either come up with non-abstract solutions, or shut up. Ranting is easy. Coming up with solutions using your own solutions is less so.