Did you know? Programmers convert coffee to code.

If you like my articles, sponsor me a coffee.

Upgrading to Liquibase 3.2.0

In this article I’ll tell about an upgrade of Liquibase to the version 3.2.0 and how it interferes with the Variations project because of its dependencies.

I changed the version of Liquibase to the most recent version: 3.2.0. I thought to do this only to keep the application’s dependencies up to date however since Liquibase 3.2.0 there are some dependency JARs for Liquibase which are signed and it makes the one-jar for the project impossible to execute with the following exception:

Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
at sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:284)
at sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:238)
at java.util.jar.JarVerifier.processEntry(JarVerifier.java:316)
at java.util.jar.JarVerifier.update(JarVerifier.java:228)
at java.util.jar.JarFile.initializeVerifier(JarFile.java:383)
at java.util.jar.JarFile.getInputStream(JarFile.java:450)
at sun.misc.URLClassPath$JarLoader$2.getInputStream(URLClassPath.java:776)
at sun.misc.Resource.cachedInputStream(Resource.java:77)
at sun.misc.Resource.getByteBuffer(Resource.java:160)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:442)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

This is a pity for the application’s future.

Never mind because it is really uncommon to have an application running from a single JAR. Such applications are mostly special development versions (such as the Variations project currently is). Server-side apps have modules and dependency libraries.

There are some solutions for this problem, I’ll introduce them now.

Profiles

To have this working, you can set up some profiles. One for the common build and packaging of the project (no single jar, this can be the default and does not need any profile declaration) and one for a single jar file.

So I’ve set the default Liquibase version of the project to 3.2.0 and disabled the shade plugin — but I stayed with the executable JAR because currently it makes no sense to pack the application into a WAR or EAR. This needs the maven-jar plugin to add the main class to the MANIFEST.MF file. This can you alter to include the class path too — to include the dependencies you need:

<plugin>
    <groupId>org.apache.maven.plugins</groupId
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
                <mainClass>biz.hahamo.dev.variations.App</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

In this case however you have to copy all your dependencies to the “lib” folder (as stated in the highlighted line above).

But fortunately there exists another plugin for maven: the maven-dependency-plugin. This copies your dependencies into your defined folder. To make this work you have to add:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.8</version>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/lib</outputDirectory>
                <overWriteReleases>false</overWriteReleases>
                <overWriteSnapshots>false</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
        </execution>
    </executions>
</plugin>

The highlighted line shows where the plugin will copy the dependencies. It should be the same folder you added to the jar plugin for class path reference.

After a maven “clean install” you can run the variations project from the command line with Liquibase 3.2.0:

java -jar target/variations-1.0-SNAPSHOT.jar

However I mentioned that you do not need a profile for be the default I suggest you to create one because in this case you end up with some extra steps (copying the dependencies and creating an execution entry in the MANIFEST.MF) when creating a single jar (see below) that are not needed for the project build.

To have a single JAR after the build I had to create a build profile.

With a profile you can override certain configurations of the build process: version numbers of dependencies, build plans and so on. So I’ve added a profile named “one-jar” which creates a single JAR for the project with the maven-shade-plugin and with Liquibase 3.1.1. The configuration looks like follows:

<profiles>
    <profile>
        <id>one-jar</id>
        <properties>
            <liquibase-version>3.1.1</liquibase-version>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-shade-plugin</artifactId>
                    <version>2.3</version>
                    <executions>
                        <execution>
                        <phase>package</phase>
                            <goals>
                                <goal>shade</goal>
                            </goals>
                            <configuration>
                                <transformers>
                                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                        <resource>META-INF/spring.handlers</resource>
                                    </transformer>
                                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                        <resource>META-INF/spring.schemas</resource>
                                    </transformer>
                                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                        <mainClass>biz.hahamo.dev.variations.App</mainClass>
                                    </transformer>
                                </transformers>
                                <artifactSet>
                                    <excludes>
                                        <exclude>testng:testng</exclude>
                                        <exclude>org.mockito:*</exclude>
                                        <exclude>org.apache.maven:lib:tests</exclude>
                                    </excludes>
                                </artifactSet>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

To call the profile you have to execute:

mvn clean install -P one-jar

where “one-jar” is the profile’s name.

With this you end up stuck at the 3.1.1 level of Liquibase. However there is a better solution for that.

excluding signature files

A more elegant version is to simply exclude the signature files from the shaded JAR.

For this I stay with the profile for “one-jar” as above but I remove the property-override for Liquibase to be dependent to the 3.2.0 version. After this I add a “filters” tag to the shade configuration as follows:

<filters>
    <filter>
        <artifact>*:*</artifact>
        <excludes>
            <exclude>META-INF/*.SF</exclude>
            <exclude>META-INF/*.DSA</exclude>
            <exclude>META-INF/*.RSA</exclude>
        </excludes>
    </filter>
</filters>
<minimizeJar>true</minimizeJar>

This excludes all signature files to prevent the SecurityException.

The highlighted line is not needed it simply reduces your generated single JAR’s size (in this case from around 20MB to around 8MB).

conclusion

Excluding the signature files is a nasty thing however if you really need a single JAR file you should do this.

Profiles are good to maintain diverse functionality for different purposes, for example overriding dependencies or creating a single JAR file on demand.

GHajba
 

Senior developer, consultant, author, mentor, apprentice.

I love to share my knowledge and insights what I achieve through my daily work which is not trivial — at least not for me.

Click Here to Leave a Comment Below 0 comments
%d bloggers like this: