Blogs
One blog entry to rule them all (well, for this problem, at least)
"There is nothing new under the sun" - King Solomon, Ecclesiastes 1
In one sense, this blog entry doesn't contain anything new. In another sense, since it brings pieces of prior entries together, I hope that it provides some new insight for portlet developers to overcome the "Unresolved requirement: Import-Package" deployment error.
Reference Blog Entries
Let's start out by referencing the following prior blog entries (ordered by newest to oldest):
- Announcement: Liferay Faces Support for Portlet 3.0, JSF 2.3, CDI, and Liferay CE/DXP 7.4 (by me, 2022)
- Archetypes for "thin" JSF portlet WAR artifacts (with optional OSGi+CDI Integration) now available (by me, 2021)
- Gradle: compile vs compileOnly vs compileInclude (by Dave Nebinger, 2021)
- How to try JSF thin WAR demo portlets (by me, 2020)
- Speeding-Up Re-Deployment of WAR Artifacts in Liferay 7.x (by me, 2019)
- Finding Bundle Dependencies (by Dave Nebinger, 2018)
Source of The Error: Embedding Dependencies
Why does the "Unresolved requirement: Import-Package" deployment error occur? It's almost always because a portlet developer has embedded a dependency in a WAR or JAR artifact.
It's easy for us to understand how this can happen in a WAR module -- it simply means that we built a WAR with one or more JARs embedded in WEB-INF/lib using the "compile" scope with either Maven or Gradle. But Liferay portlet developers (particularly those that are using MVCPortlet in a JAR-based artifact) will sometimes use the "compileInclude" scope (see blog#3 by Dave) in order to embed a JAR within a JAR.
As a result, this can happen with MVCPortlet (Liferay's MVC Framework), FrameworkPortlet (PortletMVC4Spring), GenericFacesPortlet (Liferay Faces / JSF) or any other framework.
Experiencing The Error
In order to experience the "Unresolved requirement: Import-Package" deployment error, let's follow a typical workflow for a JSF portlet developer. Let's start out by generating a new PrimeFaces project using a Maven archetype generation command found at liferayfaces.org...
Step 1: Generate the project
mvn archetype:generate \
-DarchetypeGroupId=com.liferay.faces.archetype \
-DarchetypeArtifactId=com.liferay.faces.archetype.primefaces.portlet
\
-DarchetypeVersion=8.0.0 \
-DgroupId=com.mycompany
\
-DartifactId=com.mycompany.my.jsf.portlet
Step 2: Add some dependencies
Let's add Apache POI and PrimeFaces Extensions to pom.xml, since they are very typical dependencies:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.primefaces.extensions</groupId>
<artifactId>primefaces-extensions</artifactId>
<version>10.0.0</version>
</dependency>
Step 3: Build the project with the "thin" profile
mvn -P thin package
Step 4: Examine the embedded dependencies
ls -1 target/com.mycompany.my.primefaces.portlet-1.0.0*/WEB-INF/lib
Directory Listing:
- SparseBitSet-1.2.jar
- commons-codec-1.15.jar
- commons-collections4-4.4.jar
- commons-io-2.11.0.jar
- commons-math3-3.6.1.jar
- log4j-api-2.17.2.jar
- poi-5.2.2.jar
- primefaces-extensions-11.0.6.jar
Step 5: Deploy the WAR
cp target/*.war $LIFERAY_HOME/deploy
Step 6: Examine the console log
This is the point at which you would encounter the "Unresolved requirement: Import-Package" deployment error:
com.liferay.portal.kernel.log.LogSanitizerException:
org.osgi.framework.BundleException: Could not resolve module:
com.mycompany.my.primefaces.portlet [1679]_ Unresolved requirement:
Import-Package: org.apache.poi.xssf.usermodel;
resolution:="optional"_ Unresolved requirement:
Import-Package: com.google.gson_ [Sanitized]
at
org.eclipse.osgi.container.Module.start(Module.java:444)
~[org.eclipse.osgi.jar:?]
at
org.eclipse.osgi.internal.framework.EquinoxBundle.start(EquinoxBundle.java:428)
~[org.eclipse.osgi.jar:?]
at
com.liferay.portal.file.install.internal.DirectoryWatcher._startBundle(DirectoryWatcher.java:1156)
[bundleFile:?]
at
com.liferay.portal.file.install.internal.DirectoryWatcher._startBundles(DirectoryWatcher.java:1189)
[bundleFile:?]
at
com.liferay.portal.file.install.internal.DirectoryWatcher._startAllBundles(DirectoryWatcher.java:1130)
[bundleFile:?]
at
com.liferay.portal.file.install.internal.DirectoryWatcher._process(DirectoryWatcher.java:1041)
[bundleFile:?]
at
com.liferay.portal.file.install.internal.DirectoryWatcher.run(DirectoryWatcher.java:221) [bundleFile:?]
Fixing the Error for a single, problematic package
There are two basic approaches to fixing this error, each with benefits and drawbacks:
Approach #1 (Globally / all artifacts):
Follow my advice from blog#5,
by adding com.google.gson
to the module.framework.web.servlet.annotation.scanning.blacklist
property.
Potential Benefit: If you have multiple artifacts, this provides a centralized place that fixes the error for all of them.
Potential Benefit: The deployment process will get faster, because the WAB generator will bypass scanning of classes in all of the blacklisted package.
Potential Drawback: You might not have the privilege in your DevOps to set this property.
Potential Drawback: It's unlikely, but you might not want the package blacklisted for all deployed artifacts.
Potential Drawback: Every time you add a dependency, you will likely have to add packages, which affects all deployed artifacts (including Liferay OOTB ones!), which could affect your test scenarios.
Approach #2 (Individual artifact):
Add the package to the Import-Package
directive in
src/main/webapp/WEB-INF/liferay-plugin-package.properties as resolution:=optional
:
Import-Package:\
com.google.gson;resolution:=optional,\
*
Potential Benefit: You are able to isolate the fix to a specific WAR artifact, rather than affecting all deployed artifacts.
Potential Drawback: You have to add this to every WAR artifact that encounters the problem for the package.
Drawback: It doesn't speed up deployment to the degree that Approach#1 does.
How to discover ALL of the problematic packages
Whether you choose Approach #1 or Approach #2, you will find it very tedious to redeploy the artifact over-and-over in order to find the next problematic package.
By following Dave's advice from blog#6, you can discover ALL of the problematic packages and save yourself a lot of time by avoiding the rinse/repeat cycle of redeployment.
Whether you deploy a WAR artifact or a JAR artifact, ultimately a new OSGi bundle will appear in $LIFERAY_HOME/osgi/state. When you deploy a new module, it's normally the sub-directory with the highest integer value. Under there, you will find a "bundleFile" that contains the OSGi metadata you need.
Example Command:
bnd print --impexp ~/portals/liferay-dxp-7.4.13.u31/osgi/state/org.eclipse.osgi/1679/1/bundleFile
Command Output:
[IMPEXP]
Import-Package
com.google.gson
com.google.i18n.phonenumbers
com.ibm.uvm.tools
{resolution:=optional}
com.ibm.websphere.wsoc
{resolution:=optional}
com.liferay.faces.bridge.ext
com.liferay.faces.bridge.impl
com.liferay.petra.io.unsync
com.liferay.petra.lang
com.liferay.petra.string
com.liferay.portal.kernel.bean
com.liferay.portal.kernel.configuration
com.liferay.portal.kernel.dao.jdbc
com.liferay.portal.kernel.dao.orm
com.liferay.portal.kernel.io
com.liferay.portal.kernel.io.unsync
com.liferay.portal.kernel.log
com.liferay.portal.kernel.portlet
com.liferay.portal.kernel.portlet.bridges.mvc
com.liferay.portal.kernel.servlet
com.liferay.portal.kernel.servlet.filters.invoker
com.liferay.portal.kernel.template
com.liferay.portal.kernel.util
com.liferay.portal.kernel.xml
com.liferay.portal.model
{resolution:=optional}
com.liferay.portal.osgi.web.servlet.jsp.compiler
{resolution:=optional}
com.liferay.portal.security.access.control
{resolution:=optional}
com.liferay.portal.security.auth
{resolution:=optional}
com.liferay.portal.security.permission {resolution:=optional}
com.liferay.portal.service
{resolution:=optional}
com.liferay.portal.servlet
{resolution:=optional}
com.liferay.portal.servlet.filters.aggregate
{resolution:=optional}
com.liferay.portal.servlet.filters.dynamiccss
{resolution:=optional}
com.liferay.portal.spring.context
{resolution:=optional}
com.liferay.portal.theme
{resolution:=optional}
com.liferay.portal.util
{resolution:=optional}
com.liferay.portlet
{resolution:=optional}
com.liferay.taglib.aui
com.liferay.taglib.portlet
com.liferay.taglib.portletext
com.liferay.taglib.security
com.liferay.taglib.theme
com.liferay.taglib.ui
com.liferay.taglib.util
com.sun.el {resolution:=optional}
com.sun.el.lang
{resolution:=optional}
com.sun.el.parser
{resolution:=optional}
com.sun.el.stream
{resolution:=optional}
com.sun.el.util
{resolution:=optional}
com.sun.faces
dev.morphia
dev.morphia.query
dev.morphia.query.experimental.filters
dev.morphia.query.internal
io.undertow.websockets.jsr
{resolution:=optional}
javax.annotation
javax.crypto
javax.crypto.spec
javax.el
javax.enterprise.context
javax.faces
javax.faces.application
javax.faces.component
javax.faces.component.behavior
javax.faces.component.html
javax.faces.component.visit
javax.faces.context
javax.faces.convert
javax.faces.event
javax.faces.lifecycle
javax.faces.model
javax.faces.render
javax.faces.validator
javax.faces.view.facelets
javax.faces.webapp
javax.imageio
javax.imageio.metadata
javax.imageio.stream
javax.inject
javax.portlet
javax.portlet.faces
javax.servlet
javax.servlet.http
javax.swing
javax.swing.text
javax.swing.text.html
javax.websocket {resolution:=optional}
javax.websocket.server
{resolution:=optional}
javax.xml.namespace
javax.xml.parsers
javax.xml.stream
javax.xml.transform
javax.xml.transform.dom
javax.xml.transform.stream
javax.xml.validation
org.apache.commons.logging
org.apache.logging.log4j.util.internal
org.apache.naming.java {resolution:=optional}
org.apache.tomcat.websocket.server
{resolution:=optional}
org.commonmark.node
org.commonmark.parser
org.commonmark.renderer.html
org.dom4j
org.dom4j.io
org.eclipse.core.runtime
{resolution:=optional,
x-liferay-compatibility:=spring}
org.osgi.framework
org.osgi.framework.wiring
org.owasp.html
org.primefaces
org.primefaces.behavior.ajax
org.primefaces.behavior.base
org.primefaces.clientwindow
org.primefaces.component.api
org.primefaces.component.breadcrumb
org.primefaces.component.commandbutton
org.primefaces.component.menu
org.primefaces.component.outputpanel
org.primefaces.component.row
org.primefaces.config
org.primefaces.context
org.primefaces.event
org.primefaces.expression
org.primefaces.facelets
org.primefaces.model
org.primefaces.model.menu
org.primefaces.renderkit
org.primefaces.shaded.json
org.primefaces.shaded.owasp.encoder
org.primefaces.util
org.w3c.dom
org.xml.sax
sun.misc
sun.nio.ch
weblogic.websocket.tyrus
{resolution:=optional}
Analyzing the output:
- Ignore all the lines
with
resolution:=optional
as they are already taken care of - Ignore all the lines that
start with
com.liferay
-
Ignore the lines
with
javax.annotation
,javax.el
,javax.enterprise.context
, andjavax.inject
since your WAR module is a Java EE module that depends on these packages, and they are provided by OSGi. - Ignore the
javax.portlet
line because your WAR module contains a portlet, and that package is provided by OSGi. - Ignore all the
lines for packages that you certainly depend on from OSGi -- for
example, ignore
com.sun.faces
,javax.faces
,javax.servlet
,javax.servlet.http
,javax.faces.webapp
, andjavax.portlet.faces
since you are building a "thin" JSF WAR artifact that relies on Mojarra being deployed as an OSGi bundle (see blog#1, blog#2, and blog#4 above). - Ignore all the lines that start with
org.primefaces
since the PrimeFaces dependency is deployed in $LIFERAY_HOME/osgi/modules (thin mode) - Ignore the remaining lines that start with "javax" (for now) because they are likely provided by OSGi, and might possibly be required. Better safe than sorry.
- Be suspicious and/or skeptical at this
point. For example, should I expect OSGi to be
providing the remaining packages, or is it more likely that they
really aren't required at runtime, or are actually present in
WEB-INF/lib as embedded dependencies? Some of them should be
obvious, e.g.
dev.morphia
is a really unusual package to rely on, and probably isn't provided by OSGi. - After a process of elimination, and
after careful analysis, the following lines need to be added:
com.google.gson;resolution:=optional,\
com.google.i18n.phonenumbers;resolution:=optional,\
com.lowagie.text.*;resolution:=optional,\
dev.morphia.*;resolution:=optional,\
org.apache.logging.log4j.util.internal;resolution:=optional,\
org.commonmark.*;resolution:=optional,\
org.owasp.html;resolution:=optional,\
org.primefaces.extensions.model.monacoeditor;resolution:=optional,\
sun.nio.ch;resolution:=optional,\
Setting up pom.xml
As indicated in the above NOTE, "thin" JSF portlets,
have the ${osgi.import.package}
variable in
liferay-plugin-package.properties, so rather than modify that file
directly, you would need to setup the Import-Package
directive in pom.xml instead in order to take advantage of the
variable substitution done by Maven:
<osgi.import.package>
Import-Package:\
${osgi.import.package.primefaces},\
com.google.gson;resolution:=optional,\
com.google.i18n.phonenumbers;resolution:=optional,\
com.lowagie.text.*;resolution:=optional,\
dev.morphia.*;resolution:=optional,\
org.apache.logging.log4j.util.internal;resolution:=optional,\
org.commonmark.*;resolution:=optional,\
org.owasp.html;resolution:=optional,\
org.primefaces.extensions.model.monacoeditor;resolution:=optional,\
sun.nio.ch;resolution:=optional,\
${osgi.import.package.liferay.faces.bridge}
</osgi.import.package>
Conclusion
The "Unresolved requirement: Import-Package" deployment
error typically occurs due to embedded dependencies in a WAR or JAR
module. It's possible to overcome the problem, but it takes a little
bit of time. My recommendation is to use the bnd print
--impexp
command to determine which package(s) are causing the
problem, so as to avoid repetitive redeploys which only reveal one
problematic package at a time.