Blogs
It's going to happen. At some point in your LR7 development, you're going to build a module which has runtime dependencies. How do you satisfy those dependencies though?
In this brief blog entry I'll cover some of the options available...
So let's say you have a module which depends upon iText (and it's dependencies). It doesn't really matter what your module is doing, but you have this dependency and now have to figure out how to satisfy it.
Option 1 - Make Them Global
This is probably the easiest option but is also probably the worst. Any jars that are in the global class loader (Tomcat's lib and lib/ext, for example), are classes that can be accessed anywhere, including within the Liferay OSGi container.
But global jars have the typical global problems. Not only do they need to be global, but all of their dependencies must also be global. Also global classes are the only versions available, you can't really vary them to allow different consumers to leverage different versions.
Option 2 - Let OSGi Handle Them
This is the second easiest option, but it's likely to not work. If you declare a runtime dependency in your module and if OSGi has a bundle that satisfies the dependency, it will be automatically available to your module.
This will work when you know the dependency can be satisfied, either because you're leveraging something the portal provides or you've actually deployed the dependency into the OSGi container (some jars also conveniently include OSGi bundle information and can be deployed directly into the container).
For our example, however, it is unlikely that iText will have already been deployed into OSGi as a module, so relying on OSGi to inject it may not end well.
Declaring the runtime dependency is going to be handled in your build.gradle file. Here's a snippet for the iText runtime dependency:
runtime group: 'com.iowagie', name: 'itext', version: '1.4.8'
If iText (and it's dependencies) have been successfully deployed as an OSGi bundle, your runtime declaration will ensure it is available to your module. If iText is not available, your module will not start and will report unsatisfied dependencies.
Option 3 - Make An Uber Module
Just like uber jars, uber modules will have all of the dependent classes exploded out of their original jars and are available within the module jar.
This is actually quite easy to do using Gradle and BND.
In your build.gradle file, you should declare your runtime dependencies just as you did for Option 2.
To make the uber module, you also need to include the resources in your bnd.bnd file:
Include-Resource: @itext-1.4.8.jar
So here you include the name of the dependent jar, usually you can see what it is when Gradle is downloading the dependency or by browsing your maven repository.
Note that you must also include any dependent jars in your include statement. For example, iText 2.0.8 has dependencies on BouncyCastle mail and prov, so those would need to be added:
Include-Resource: @itext-2.0.8.jar,@bcmail-138.jar,@bcprov-138.jar
You may need to add these as runtime dependencies so Gradle will have them available for inclusion.
If you use a zip tool to crack open your module jar, you'll see that all of the individual jars have been exploded and all classes are in the jar.
Option 4 - Include the Jars in the Module
The last option is to include the jars in the module itself, not as an uber module, but just containing the jar files within the module jar.
Similar to option 2 and 3, you will declare your runtime dependencies in the build.gradle file.
The bulk of the work is going to be done in the bnd.bnd file.
First you need to define the Bundle-ClassPath attribute to include classes in the module jar but also the extra dependency jars. In the example below, I'm indicating that my iText jar will be in a lib directory within the module jar:
Bundle-ClassPath:\ .,\ lib/itext.jar
Rather than use the Include-Resource header, we're going to use the -includeresource directive to pull the jars into the bundle:
-includeresource:\ lib/itext.jar=itext-1.4.8.jar
In this format we're saying that lib/itext.jar will be pulled in from itext-1.4.8.jar (which is one of our runtime dependencies so Gradle will have it available for the build).
This format also supports the use of wildcards so you can leave version selection to the build.gradle file. Here's an example for including any version of commons-lang:
-includeresource:\ lib/itext.jar=itext-1.4.8.jar,\ lib/commons-lang.jar=commons-lang-[0-9]*.jar
If you use a zip tool to crack open your module jar, you'll find there are jars now in the bundle under the lib directory.
Conclusion
So which of these options should you choose? As with all things Liferay, it depends.
The global option is easy as long as you don't need different versions of jars but have a lot of dependencies on the jar. For example, if you had 20 different modules all dependent upon iText 1.4.8, global may be the best path with regards to runtime resource consumption.
Option 2 can be an easy solution if the dependent jar is also an OSGi bundle. In this case you can allow for multiple versions and don't have to worry about bnd file editing.
Option 3 and 4 are going to be the most common route to choose however. In both of these cases your dependencies are included within the module so the OSGi's class loader is not polluted with different versions of dependent jars. They are also environment-agnostic; since the modules contain all of their dependencies, the environment does not need to be prepared prior to module deployment.
Personally I stick with Option 4 - uber jars will tend to step on each other when expanding the jars that contain a same path/file in each (usually xml or config info). Option 4 doesn't suffer from these sorts of issues.
Enjoy!

