Using Private Module Binaries as Dependencies

When you compare a CE release with an EE release, you'll find that there are a few additional modules that are only available in EE releases. In Liferay terms, these are called "private" modules. They are private in the sense that their source code doesn't exist in any public GitHub repositories (only private ones, and usually inside of a folder named "private"), and their binaries and corresponding source code aren't published to repository.liferay.com.

From a new Liferay developer perspective, the main roadblack you might encounter with them is when you want to consume API exposed by one of those private modules, or if you want to extend one of those modules. Essentially, you run into an obstacle immediately: there are no repositories for you to use, so your build-time dependencies are never satisfied.

For a seasoned java developer, you would quickly realize that you can use Maven in order to install any JARs you need, and both Maven projects and Gradle projects would able to use those installed JARs. However, not everyone is as savvy about this sort of thing, so I thought it would be a good idea to create a blog entry to walk through the process.

Script Creation

All the JARs are present inside of the osgi/marketplace folder, buried inside of .lpkg files. So, as a first step to get at the .jar files, you might create a temporary folder (which we'll call temp), and then extract each .jar to that temporary folder.

install_lpkg() {
    mkdir -p temp

    unzip -uqq "$lpkg" -d temp

    rm -rf temp
}

With each JAR file, the next thing you'd want to do is install it to your local Maven repository, following Apache's guide to installing 3rd party JARs. This leads you to these commands, which assume you have some sense of the artifact ID and artifact version for the JAR.

install_jar() {
    mvn install:install-file -Dpackaging=jar -Dfile=$1 -DgroupId=com.liferay -DartifactId=${ARTIFACT_ID} -Dversion=${VERSION}
}

for jar in temp/*.jar; do
    install_jar "$jar"
done

Well, how would you uncover the artifact ID and artifact version for the JAR? Well, as outlined in our Configuring Dependencies documentation found in the Developer Guide, all of the module JARs that are included with LPKGs use the bundle symbolic name for the artifact ID and the bundle version for the artifact version. Since these are both stored in the JAR manifest, this means that once you have a .jar file, you can extract the intended artifact ID and the intended artifact version fairly easily.

ARTIFACT_ID=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-SymbolicName | sed 's/[\r\n]//g' | cut -d':' -f 2 | sed 's/ //g')
VERSION=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-Version | sed 's/[\r\n]//g' | cut -d':' -f 2 | sed 's/ //g')

You could iterate over every .jar and do this, but all of the public .jar files exist in repository.liferay.com, with the appropriate source code (which most IDEs will auto-download). Because using the .lpkg bundle means you have no source, it's better to restrict your installation to only those modules that are private.

How do you differentiate between a private module and a public module? Well, you could just compare CE releases and EE releaes, but there's a slightly easier way to do so. It turns out that when Liferay bundles the artifact, it adds a header Liferay-Releng-Public to each artifact to indicate whether it was intended to be private or public. This means you can check using the Liferay binary itself, without crawling Liferay's public Maven repository, you can figure out which ones are not available in public repositories and limit your installation to those artifacts.

if [ "" == "$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Liferay-Releng-Public | grep -F false)" ]; then
    return 0
fi

Script Completion

Combining all of those elements together leaves you with the following script. Simply run from the osgi/marketplace folder, and it will extract your .lpkg files, and install any non-public .jar files to your local Maven repository.

#!/bin/bash

install_jar() {
    if [ "" == "$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Liferay-Releng-Public | grep -F false)" ]; then
        return 0
    fi

    local ARTIFACT_ID=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-SymbolicName | sed 's/[\r\n]//g' | cut -d':' -f 2 | sed 's/ //g')
    local VERSION=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-Version | sed 's/[\r\n]//g' | cut -d':' -f 2 | sed 's/ //g')

    mvn install:install-file -Dpackaging=jar -Dfile=$1 -DgroupId=com.liferay -DartifactId=${ARTIFACT_ID} -Dversion=${VERSION}
}

install_lpkg() {
    mkdir -p temp

    unzip -uqq "$1" -d temp

    for jar in temp/*.jar; do
        install_jar "$jar"
    done

    rm -rf temp
}

shopt -s nullglob

for lpkg in *.lpkg; do
    install_lpkg "$lpkg"
done

If you need to publish to a remote repository, simply replace mvn install:install-file with mvn deploy:deploy-file as outlined in Apache's guide to deploying 3rd party JARs to remote repository, and provide the additional parameters: the repositoryId and the url to the repository you wish to publish to.

Blogs
Awesome! And, since the script includes the version details, it will be
easy to update your development environment as you apply new GAs,
Service Packs and Fix Packs! I'm thinking, though, that since you
compile against the lowest version you can get away with (see
https://web.liferay.com/web/user.26526/blog/-/blogs/compile-time-vs-runtime-osgi-dependencies),
you would probably want to pick the GA1 release and populate your
repository from that version. After all, you want to widen the range of
deployment of your artifacts to Liferay versions, and picking the GA1
release will do that.

Mac OSX version needs this tweaks (add $ in front of the single quote):

 

    local ARTIFACT_ID=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-SymbolicName | cut -d':' -f 2 | sed $'s/[\r ]//g'     local VERSION=$(unzip -c $1 META-INF/MANIFEST.MF | grep -F Bundle-Version | cut -d':' -f 2 | sed $'s/[\r ]//g'