So in my last post I documented how to migrate the ant build from the JasperServer 3.7 Java sample web application to a maven based build and we got that happening pretty quickly. We ended up with an easier way to deploy the application, a much smaller checkout and clearer understanding of the dependencies. However looking closely, we can certainly improve matters. Lets see what we can do.
A first check is to look at the WEB-INF/lib folder of the Maven generated war file and compare it to the contents of the old web/WEB-INF/lib folder. You will see that there are now more libraries than before. Let’s use the handy maven-dependency-plugin to see what is going on.
mvn dependency:tree
will show you among others
[INFO] +- com.lowagie:itext:jar:2.1.0:compile [INFO] | +- bouncycastle:bcmail-jdk14:jar:136:compile [INFO] | \- bouncycastle:bcprov-jdk14:jar:136:compile
That tells you that because we declared itext as a dependency, we also get bouncycastle included. How is that? itext renders the JasperReport object the sample application receives from the server into PDF and the pom.xml file for itext on the maven central repository declares that it needs bouncycastle, so we get it in our war file. We assume that the itext authors (or whoever added itext to maven central) knows more about this then us and take the jar into the pom. Most likely that is correct and we just fixed some potential issues with our application. If we are smarter than them we could add an exclusion, but more on that later.
Interestingly enough you can also get the opposite. For example looking at the dependencies for jfreechart and jcommon
<dependency> <groupId>jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.0.12</version> </dependency> <dependency> <groupId>jfree</groupId> <artifactId>jcommon</artifactId> <version>1.0.15</version> </dependency>
in the maven central repository you see that the jfreechart pom actually declares jcommon as a dependency. Thats not really surprising if you know that jcommon contains utility code for jfreechart, jfreereport and other jfree libraries. Since we don’t explicitly need jcommon in our project, we should remove the declared dependency to it. We will still get the jar in the war file, but our pom.xml will be much clearer.
That brings up an important issue. Why do we actually need so much stuff? Is that actually right? Lets ask the dependency plugin again:
mvn dependency:analyze
The result will show you that there are a whole bunch of unused declared dependencies found. In an ideal world we could happily remove all those dependencies and everything should be fine. However in this case that is not true. The dependency plugin just looks at the code and the pom files for the
declared dependencies to suss out if something is not needed. Because we use our hack of basically giving the jasperserver and jasperreports jars straight on the path without a pom.xml declaring dependencies, we need to explicitly add them to the project.
Now there are a few ways around that. The ideal way is to get the artifacts onto maven central. The problem with that is that you are not the owner of the project/domain and therefore can not just do that. If you are however the project owner, you should really look into that. Sonatype provides great help for that and all your users will be immensely grateful. Just look at how Spring Source, JBoss and other projects managed to get their stuff into the repository and the feedback they got.
A next step would be to install the artifact locally like that
mvn install:file mvn install:install-file -Dfile=<path-to-file> -DgroupId=jasperreports -DartifactId=jasperreports -Dversion=3.7.0 -Dpackaging=jar -DgeneratePom=true
but really that only helps you on the one developer machine and does not scale.
The best thing to do is to start running a repository server like Sonatype Nexus yourself (something I recommend even for local development). You can just install the artifact into a repository on the server and everybody with access to that repository server can get the file. You could therefore declare the dependency e.g. for jasperreports like that.
<dependency> <groupId>jasperreports</groupId> <artifactId>jasperreports</artifactId> <version>3.7.0</version> </dependency>
remove the jar from the folder src/webapp/WEB-INF/lib and you would be a happy camper.
Taking this one step further we could upload a pom.xml for jasperreports 3.7.0 into our local repository or better repository server and get a whole host of transitive dependencies via that pom that we could then remove from our pom.xml. But we would have to craft that pom ourselves or take the one from the source code (luckily jasperreports is open source…). A problem with this particular pom file for our use case here is that many things are declared optional. E.g. groovy is declared optional because jasperreports supports embedded groovy snippets in a report, but if there is no groovy script in the report the library is not needed. So if you know that your sample webapp will have to deal with reports that contain groovy you will need to declare the dependency explicitly in your pom with the optional tag removed. In the code of this blog post on github you can see how I added all the jasperreports dependencies and commented out the optional tag. In order to get a jar removed from the war file you would comment out the optional setting or remove the entire dependency. The current approach creates a pretty large war file, but it would be ready for pretty much any report coming from jasperserver.
Looking at the libraries again we find a typical problem. Junit or some other transitive dependency that is not needed pops up in the lib folder. Surely we dont need a test framework at run time so we check with
mvn dependency:analyze
and find
[INFO] +- commons-httpclient:commons-httpclient:jar:3.0:compile [INFO] | \- junit:junit:jar:3.8.1:compile
A similar dependency is there for groovy and we add an exclusion to both to get rid of that artifact in the resulting war.
<dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.0</version> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency>
In a similar manner you would want to go through the pom files for the other jasperreport and jasperserver libraries looking at the jasperserver source code and fixing things up here and there. If the artifacts would be all available in public repositories all that pain would be gone and you would have a much shorter pom. In the meantime I really recommend that you install a Nexus instance for yourself (and your team and build server cluster and your releases and and and), install the artifacts with the pom files in there and clean up your own application to a concise short pom. Oh and maybe convince JasperSoft to get all their artifacts into the public maven repositories, maybe even on time with their releases. Wouldn’t that be awesome?
In terms of the web app example you could do more mavenization by moving the classes into a separate jar artifact as a sibling to the war project and adding a parent pom tying the two together. Or you could leverage the reports from maven to make a project website, or findbugs to tell you where you went wrong, or add another project for integration testing of the web application with Selenium or … or … or. There are plugins for all these tasks and many more. Go check it out. I will leave that as an exercise for yourself 😉
Summarizing we now have a bigger war file, but also a much better understanding of what the application actually needs and we have prevented many potential bug reports due to the understanding of the optional dependencies. In production this would have saved us thousands of dollars.