Third party library to Bundle OSGi on Liferay DPX/7

Short explanation

If you want to convert a third party library of your portlets (modules) into a Bundle OSGi to be used by all modules, you could create a new module with your third party library and dependences and be used by your modules.

To explain this we will use JasperReport third party library. Supose that you have a module that uses JasperReport to generate PDF files. Probably you have these dependencies in the build.gradle file of your module:

/* POI */

compile group: 'org.apache.poi', name: 'poi', version: '3.15'

/* JasperReport */

compile group: 'net.sf.jasperreports', name: 'jasperreports', version: '5.6.1'

compile group: 'commons-digester', name: 'commons-digester', version: '2.1'

compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.0.1'

compile group: 'com.lowagie', name: 'itext', version: '2.1.7.js2'
 
And you may have this configuration in the bnd.bnd file of your module:
Import-Package: \

*;resolution:=optional

Bundle-ClassPath:\

  .,\

  lib/poi-3.15.jar,\

  lib/jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar

Include-Resource:\

  lib/poi-3.15.jar=poi-3.15.jar,\

  lib/jasperreports-5.6.1.jar=jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar=commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar=groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar=itext-2.1.7.js2.jar

-metatype: *
 
Then you could create a new blank module with this build.gradle file:
repositories {

mavenCentral()

maven{url "http://jasperreports.sourceforge.net/maven2/"}

    maven{url "http://jaspersoft.artifactoryonline.com/jaspersoft/third-party-ce-artifacts/"}

}

configurations {

    jasperreports {

        transitive = true

    }

}

dependencies {

  compileOnly group: "org.osgi", name: "org.osgi.core", version: "6.0.0"

  compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"

  /* JasperReport */

  compile group: 'net.sf.jasperreports', name: 'jasperreports', version: '5.6.1'

  compile group: 'commons-digester', name: 'commons-digester', version: '2.1'

  compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.0.1'

  compile group: 'com.lowagie', name: 'itext', version: '2.1.7.js2'

  compile group: 'org.apache.poi', name: 'poi', version: '3.7'

}
 
And this bnd.bnd file:
Bundle-Name: jasperreports

Bundle-SymbolicName: jasperreports

Bundle-Version: 1.0.0

Export-Package: *;-split-package:=merge-last;-noimport:=true

Import-Package: \

*;resolution:=optional

Bundle-ClassPath:\

  .,\

  lib/poi-3.7.jar,\

  lib/jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar

Include-Resource:\

  lib/poi-3.7.jar=poi-3.7.jar,\

  lib/jasperreports-5.6.1.jar=jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar=commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar=groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar=itext-2.1.7.js2.jar

-metatype: *
 
When you build and deploy your new module you will deploy a new module that exports all public class for JasperReport use.
You would only need add dependency of your new module in all modules you want to add JasperReport funcionality:
compileOnly project(":modules:jasperreports")
 

Long explanation

A usually problem on Liferay 6.x was where you can put your third party library. If you put your third party library into your portlets, you will have very heavy war files (too dificult to manage for your CI System), and other problems. If you decide to put your third party library into your Tomcat lib/ext folder, you will have light war files but you will need to restart your Tomcat Server allways you add a new library or change a library version. 
With the new OSGi paradigm, a new world have been opened in front of us. We can put our third party library into modules (as jar files or as unzipped in the module classpath), on our lib/ext Tomcat folder (as in the old version) or into a module witch are used as module of dependencies.
The last option (as module) is very interesting because your third party library will be grouped in a module (then your modules may be more ligth) and you won't need to restart your Tomcat Server when you add a new library.
Follow these steps to convert your third party library to a new OSGi module:
  • Pass your dependencies configuration from build.gradle file of your module to build.gradle file of the new library module. This will be necesary for download your third party library on your new library module.
repositories {

mavenCentral()

maven{url "http://jasperreports.sourceforge.net/maven2/"}

    maven{url "http://jaspersoft.artifactoryonline.com/jaspersoft/third-party-ce-artifacts/"}

}

configurations {

    jasperreports {

        transitive = true

    }

}

dependencies {

  compileOnly group: "org.osgi", name: "org.osgi.core", version: "6.0.0"

  compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"

  /* JasperReport */

  compile group: 'net.sf.jasperreports', name: 'jasperreports', version: '5.6.1'

  compile group: 'commons-digester', name: 'commons-digester', version: '2.1'

  compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.0.1'

  compile group: 'com.lowagie', name: 'itext', version: '2.1.7.js2'

  compile group: 'org.apache.poi', name: 'poi', version: '3.7'

}
  • Pass your dependencies configuration from bnd.bnd file of your module to bnd.bnd file of the new library module. This will be necesary for load your third party library on your new library module jar file.
Bundle-Name: jasperreports

Bundle-SymbolicName: jasperreports

Bundle-Version: 1.0.0

Import-Package: \

*;resolution:=optional

Bundle-ClassPath:\

  .,\

  lib/poi-3.7.jar,\

  lib/jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar

Include-Resource:\

  lib/poi-3.7.jar=poi-3.7.jar,\

  lib/jasperreports-5.6.1.jar=jasperreports-5.6.1.jar,\

  lib/commons-digester-2.1.jar=commons-digester-2.1.jar,\

  lib/groovy-all-2.0.1.jar=groovy-all-2.0.1.jar,\

  lib/itext-2.1.7.js2.jar=itext-2.1.7.js2.jar

-metatype: *
  • Then you will need to add a little configuration to Export-Package parameter.
Export-Package: *;-split-package:=merge-last;-noimport:=true

-split-package:=merge-last directive on Export-Package allows fine grained control over what should be done with split packages, merging split packages but overwriting resources that come earlier in the classpath. That is, the last resource wins.

-noimport:=true directive on Export-Package disables automatically import packages by Export-Package directive.

When you build your new library module you will create a jar file with your third party library unzipped into your module and the configuration you need on MANIFEST.MF to use your library on other modules.

Then you will need add the dependency of your new library module into other module these use it:

compileOnly project(":modules:jasperreports")

You can use my example liferay workspace (https://github.com/ironcero/jasperreports-bundle-test).

Edited

As Miroslav said you could simplify this configuration changing the compile directives on your gradle file (build.gradle):

compileInclude group: 'org.apache.poi', name: 'poi', version: '3.7'

With compileInclude Gradle will copy the library jar file in lib folder inside the module jar file. I Built some diferent scenarios trying to understand the diferences between compileOnly, compile and compileInclude. In gradle for OSGi bundle I haven't found any diferences between compileOnly and compile:

For this build.gradle file:

repositories {

mavenCentral()

maven{url "http://jasperreports.sourceforge.net/maven2/"}

    maven{url "http://jaspersoft.artifactoryonline.com/jaspersoft/third-party-ce-artifacts/"}

}



configurations {

    jasperreports {

        transitive = true

    }

}





dependencies {

compileOnly group: "org.osgi", name: "org.osgi.core", version: "6.0.0"

compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"

/* JasperReport */

compileOnly group: 'net.sf.jasperreports', name: 'jasperreports', version: '5.6.1'

compile group: 'commons-digester', name: 'commons-digester', version: '2.1'

compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.0.1'

compile group: 'com.lowagie', name: 'itext', version: '2.1.7.js2'

compile group: 'org.apache.poi', name: 'poi', version: '3.7'



// https://mvnrepository.com/artifact/org.apache.commons/commons-math3

compileInclude group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'



}
 

Where net.sf.jasperreports.jasperreports library is configured as compileOnly; org.codehaus.groovy.groovy-all is configured as compile; and org.apache.commons.commons-math3 is configured as compileInclude.

To complete the example, bnd.bnd was:

Bundle-Name: jasperreports

Bundle-SymbolicName: jasperreports

Bundle-Version: 1.0.0

-metatype: *

Result was that only commons-math3 library was included on lib filder inside the bundle jar file (logical). And this was MANIFEST.MF file:

Manifest-Version: 1.0

Bnd-LastModified: 1516660109235

Bundle-ClassPath: .,lib/commons-math3-3.6.1.jar

Bundle-ManifestVersion: 2

Bundle-Name: jasperreports

Bundle-SymbolicName: jasperreports

Bundle-Version: 1.0.0

Created-By: 1.8.0_73 (Oracle Corporation)

Import-Package: net.sf.jasperreports.components.table,org.codehaus.gro

 ovy.ast.builder;version="[2.0,3)"

Javac-Debug: on

Javac-Deprecation: off

Javac-Encoding: Cp1252

Private-Package: jasperreports,jasperreports.api;version="1.0.0",lib,a

 ssets.org.apache.commons.math3.exception.util,assets.org.apache.commo

 ns.math3.random,org.apache.commons.math3,org.apache.commons.math3.ana

 lysis,org.apache.commons.math3.analysis.differentiation,org.apache.co

 mmons.math3.analysis.function,org.apache.commons.math3.analysis.integ

 ration,org.apache.commons.math3.analysis.integration.gauss,org.apache

 .commons.math3.analysis.interpolation,org.apache.commons.math3.analys

 is.polynomials,org.apache.commons.math3.analysis.solvers,org.apache.c

 ommons.math3.complex,org.apache.commons.math3.dfp,org.apache.commons.

 math3.distribution,org.apache.commons.math3.distribution.fitting,org.

 apache.commons.math3.exception,org.apache.commons.math3.exception.uti

 l,org.apache.commons.math3.filter,org.apache.commons.math3.fitting,or

 g.apache.commons.math3.fitting.leastsquares,org.apache.commons.math3.

 fraction,org.apache.commons.math3.genetics,org.apache.commons.math3.g

 eometry,org.apache.commons.math3.geometry.enclosing,org.apache.common

 s.math3.geometry.euclidean.oned,org.apache.commons.math3.geometry.euc

 lidean.threed,org.apache.commons.math3.geometry.euclidean.twod,org.ap

 ache.commons.math3.geometry.euclidean.twod.hull,org.apache.commons.ma

 th3.geometry.hull,org.apache.commons.math3.geometry.partitioning,org.

 apache.commons.math3.geometry.partitioning.utilities,org.apache.commo

 ns.math3.geometry.spherical.oned,org.apache.commons.math3.geometry.sp

 herical.twod,org.apache.commons.math3.linear,org.apache.commons.math3

 .ml.clustering,org.apache.commons.math3.ml.clustering.evaluation,org.

 apache.commons.math3.ml.distance,org.apache.commons.math3.ml.neuralne

 t,org.apache.commons.math3.ml.neuralnet.oned,org.apache.commons.math3

 .ml.neuralnet.sofm,org.apache.commons.math3.ml.neuralnet.sofm.util,or

 g.apache.commons.math3.ml.neuralnet.twod,org.apache.commons.math3.ml.

 neuralnet.twod.util,org.apache.commons.math3.ode,org.apache.commons.m

 ath3.ode.events,org.apache.commons.math3.ode.nonstiff,org.apache.comm

 ons.math3.ode.sampling,org.apache.commons.math3.optim,org.apache.comm

 ons.math3.optim.linear,org.apache.commons.math3.optim.nonlinear.scala

 r,org.apache.commons.math3.optim.nonlinear.scalar.gradient,org.apache

 .commons.math3.optim.nonlinear.scalar.noderiv,org.apache.commons.math

 3.optim.nonlinear.vector,org.apache.commons.math3.optim.nonlinear.vec

 tor.jacobian,org.apache.commons.math3.optim.univariate,org.apache.com

 mons.math3.optimization,org.apache.commons.math3.optimization.direct,

 org.apache.commons.math3.optimization.fitting,org.apache.commons.math

 3.optimization.general,org.apache.commons.math3.optimization.linear,o

 rg.apache.commons.math3.optimization.univariate,org.apache.commons.ma

 th3.primes,org.apache.commons.math3.random,org.apache.commons.math3.s

 pecial,org.apache.commons.math3.stat,org.apache.commons.math3.stat.cl

 ustering,org.apache.commons.math3.stat.correlation,org.apache.commons

 .math3.stat.descriptive,org.apache.commons.math3.stat.descriptive.mom

 ent,org.apache.commons.math3.stat.descriptive.rank,org.apache.commons

 .math3.stat.descriptive.summary,org.apache.commons.math3.stat.inferen

 ce,org.apache.commons.math3.stat.interval,org.apache.commons.math3.st

 at.ranking,org.apache.commons.math3.stat.regression,org.apache.common

 s.math3.transform,org.apache.commons.math3.util

Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"

Tool: Bnd-3.2.0.201605172007

You can see that all packages of commons-math3 library were included on Private-package section. However, in Import-Package section only were included the packages used on module source files (net.sf.jasperreports.components.table,org.codehaus.groovy.ast.builder).

The problem come when we want to export these libraries to used outside our modules. Then you will need to add Export-Package section on bnd.bnd file. If you only includes some package in this section, then all classes of first package will be copied in the classpath of bundle jar file independently of compile directive. I think that bnd doesn't know how to build the MANIFEST.MF in this case. 

On the other hand, if you includes '*' in Export-Package section, then all classes of all packages includes on your bundle will be copied in the classpath of bundle jar file. If you includes '*' in Export-Package section and same library with compileInclude then all classes of library will be copied in classpath and in lib folder (duplicate).

Conclusion

At last I'm going to try to summarize all common scenarios:

  1. Add a third party library: If you only need to add one third party library to your bundle you can do easily adding the library with compileInclude in your build.gradle. You won't need anything else.
  2. Add a third party library that is already in OSGi container: If you only need to add one third party library that is exported by other bundle of your OSGi container you can do easily adding the library with compileOnly in your build.gradle. You won't need anything else.
  3. Add a third party library that you want to export to another bundle: If you need to include a library in your bundle to used in other bundle (to build a libraries bundle, for example), you will need to add the library to build.gradle with compileInclude and add all packages you need to Export-Package section on bnd.bnd. However if you need to export all package of third party libraries, I think that is better option include these library with compile in build.gradle and add '*' to Export-Package section on bnd.bnd.
Blogs
Hi nice post but it would probably worth to mention compileInclude. This will do a lot of the mentioned stuff for you automatically.
Thank you very much for your comment Miroslav. After reading your comment, I've done some tests to better understand the difference between common types of compile directives. And I modified the post accordingly. As you say it is possible to use compileInclude instead of the Export-Package combination.

Hi, thanks for  your knowledge , consult i am using liferay 7.2 messagelistener with jasperreport and method receive  of messagelistener when execute generate jasperreport, show me error : 

Exception in thread "logging/export-1" java.lang.ExceptionInInitializerError     at net.sf.jasperreports.engine.DefaultJasperReportsContext.getExtensions(DefaultJasperReportsContext.java:268)     at net.sf.jasperreports.engine.util.MessageUtil.getMessageProvider(MessageUtil.java:70)     at net.sf.jasperreports.engine.JRRuntimeException.resolveMessage(JRRuntimeException.java:166)     at net.sf.jasperreports.engine.JRRuntimeException.getMessage(JRRuntimeException.java:146)     at net.sf.jasperreports.engine.JRRuntimeException.getMessage(JRRuntimeException.java:138)     at pe.com.pacifico.portal.corredores.admin.logging.messaging.impl.LoggingReportsMessagingImpl.receive(LoggingReportsMessagingImpl.java:90)     at com.liferay.portal.kernel.messaging.InvokerMessageListener.receive(InvokerMessageListener.java:74)     at com.liferay.portal.messaging.internal.ParallelDestination$1.run(ParallelDestination.java:56)     at com.liferay.portal.kernel.concurrent.ThreadPoolExecutor$WorkerTask._runTask(ThreadPoolExecutor.java:752)     at com.liferay.portal.kernel.concurrent.ThreadPoolExecutor$WorkerTask.run(ThreadPoolExecutor.java:664)     at java.lang.Thread.run(Thread.java:748)