OSGi tooling in Jahia

October 8, 2024

Jahia Maven Plugin

The Jahia Maven plugin has a few new goals to help you with your OSGi module projects. Here is a quick overview:

  • jahia:dependencies : this goal will parse your project and its dependencies to generate a realistic list of imports for your module. It does this by scanning a lot of different resources to produce a list of package imports as well as content definition export and dependencies.
  • jahia:find-package-uses : will use the BND tool from Peter Kriens internally to figure out where the package import came from, specifically which class coming from which Maven dependency that is generating this import. You could then use a tool such as JD-GUI (http://java-decompiler.github.io/) to open the JAR and decompile the class that is referencing the package to understand its uses, and if it is mandatory or not. One thing you could do once you understand the package use is marking it as an optional OSGi resolution.
  • jahia:find-packages : will scan all the projects dependencies, including optional and provided ones, to find a package. So, you can use this if you suspect the package must be provided by a JAR in the project’s dependencies but have trouble finding it (for example because it is optional).
  • jahia:osgi-inspect : will output a nicely formatted and easy to read view of the MANIFEST.MF headers and optionally also the packages contained inside a JAR (it doesn’t even have to be an OSGi bundle).
  • jahia:help : to get inline information
mvn jahia:help

jahia:dependencies goal

The jahia:dependencies goal helps you build the following OSGi MANIFEST headers:

  • Import-Package (list of packages required by the OSGi bundle)
  • Export-Package (list of packages exported by the OSGi bundle
  • Provide-Capability (list of capabilities provided by the OSGi bundle)
  • Require-Capability (list of capabilities required by the OSGi bundle)

It is capable of scanning a lot of different resource types, to complement the class scanning that the Felix Maven Bundle Plugin already does, notably:

  • .jsp files (both page import and taglib dependencies)
  • .tld (tag library definition) files
  • .cnd (content node definition) files
  • .drl (Drools Rule) files
  • .jbpm.xml (JBPM Workflow definition) files
  • Spring context files
  • Jackrabbit XML import files

The following graph explains how the jahia:dependencies goal integrates with the Felix Bundle Maven plugin to generate extended package imports.

Build steps:

  1. The Jahia Maven plugin jahia:dependencies goal scans the project source code to detect all package references inside resources that are not supported by the Felix Bundle Maven Plugin class scanning
  2. It generates a list of packages that is then made available to the Maven runtime in the following variable: ${jahia.plugin.projectPackageImport}
  3. The Felix Bundle Maven plugin is configured to generate an OSGi bundle using its own configuration and also references the variable generated by the Jahia Maven plugin goal to generate the final OSGi manifest headers.

Package scanning

Most of the work the plugin does is scan different resource types to see which packages are used. For example, in the case of a JSP file it will scan the directives at the beginning of the file to see if any page import or taglibs are used. In the case of a page import it will simply retrieve the list of packages, but in the case of a tag library it will retrieve the corresponding TLD file, and scan its content to find the packages used inside the TLD file. This makes it easy for integrators to make sure they are importing the proper packages even when they use tag libraries, the plugin does all the work behind the scenes to make sure that the proper imports are generated. Also, it will also check for inconsistencies such as a missing dependency if a tag library is used in a JSP but missing from the project’s Maven dependencies.

Once all the package scanning is completed, the plugin sets the following Maven project property: ${jahia.plugin.projectPackageImport}. This property contains a list of all the packages found in the project, and can then be used as import to the Import-Package instruction of the Maven Bundle plugin as in the example below:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>${project.name}</Bundle-Name>
            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
            <Bundle-Category>jahia-module</Bundle-Category>
            <Implementation-Title>${project.name}</Implementation-Title>
            <Implementation-Version>${project.version}</Implementation-Version>
            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
            <Implementation-URL>${project.organization.url}</Implementation-URL>
            <Import-Package>*,${jahia.plugin.projectPackageImport}</Import-Package>
            <Specification-Title>${project.name}</Specification-Title>
            <Specification-Version>${project.version}</Specification-Version>
            <Specification-Vendor>${project.organization.name}</Specification-Vendor>
            <!-- Jahia manifest attributes -->
            <Jahia-Depends>default</Jahia-Depends>
            <Jahia-Module-Type>module</Jahia-Module-Type>
            <Jahia-Root-Folder>${project.artifactId}</Jahia-Root-Folder>
            <Jahia-Source-Folders>${project.basedir}</Jahia-Source-Folders>
            <Jahia-Static-Resources>/css,/icons,/images,/img,/javascript</Jahia-Static-Resources>

Content definition scanning

Much in the same way that we scan a project for package dependencies, the jahia:dependencies goal always scans all the resources for content node type definitions and references. Specifically, it scans the following file types:

  • *.cnd : content definition files. Here it actually parses the files to extract all the new node type definitions as well as all the node types used either as super types, child node types, or even property types that can use a node type in their values.
  • JCR import files (*.xml) : the scanner uses the following XPath queries to retrieve content node type references : //@jcr:primaryType and //@jcr:mixinTypes

The result of the scanning is the stored in Maven project properties:

  • ${jahia.plugin.providedNodeTypes}: a list of all content node type definitions defined in the project, formatted in OSGi Provide-Capability format.
  • ${jahia.plugin.requiredNodeTypes}: a list of all required content node type definitions found in the project, formatted in OSGi Require-Capability format.

Here is an example of what these properties look like when generated for a Jahia module:

Provide-Capability: com.jahia.services.content; nodetypes:List<String>="jmix:retrievableContent,jnt:contentRetrieval"
Require-Capability: com.jahia.services.content; filter:="(nodetypes=jmix:basicContent)",com.jahia.services.content; filter:="(nodetypes=jmix:cache)",com.jahia.services.content; filter:="(nodetypes=jmix:editorialContent)",com.jahia.services.content; filter:="(nodetypes=jmix:list)",com.jahia.services.content; filter:="(nodetypes=jmix:queryContent)",com.jahia.services.content; filter:="(nodetypes=jmix:renderableList)",com.jahia.services.content; filter:="(nodetypes=jnt:content)",com.jahia.services.content; filter:="(nodetypes=jnt:page)",com.jahia.services.content; filter:="(nodetypes=mix:title)"

In the above example we see that the project defined the new node type definitions jmix:retrievableContent and jnt:contentRetrieval and that it needs content node definitions such as jmix:basicContent in order to work correctly. If these requirements are not fulfilled the module will not be able to start. We can therefore integrate the capability generation with the Maven Bundle plugin by expanding on the example provided previously in the package scanning example, as seen here:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>${project.name}</Bundle-Name>
            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
            <Bundle-Category>jahia-module</Bundle-Category>
            <Implementation-Title>${project.name}</Implementation-Title>
            <Implementation-Version>${project.version}</Implementation-Version>
            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
            <Implementation-URL>${project.organization.url}</Implementation-URL>
            <Specification-Title>${project.name}</Specification-Title>
            <Specification-Version>${project.version}</Specification-Version>
            <Specification-Vendor>${project.organization.name}</Specification-Vendor>
            <!-- Jahia manifest attributes -->
            <Jahia-Depends>default</Jahia-Depends>
            <Jahia-Module-Type>module</Jahia-Module-Type>
            <Jahia-Root-Folder>${project.artifactId}</Jahia-Root-Folder>
            <Jahia-Source-Folders>${project.basedir}</Jahia-Source-Folders>
            <Jahia-Static-Resources>/css,/icons,/images,/img,/javascript</Jahia-Static-Resources>
            <Export-Package></Export-Package>
            <Import-Package>*,${jahia.plugin.projectPackageImport}</Import-Package>
            <Embed-Dependency>*; scope=compile; type=!pom;
                inline=true</Embed-Dependency>
            <Embed-Transitive>true</Embed-Transitive>
            <_removeheaders>
                Include-Resource,
                Private-Package,
                Embed-Dependency,
                Embed-Transitive
            </_removeheaders>
        </instructions>
        <archive>
            <addMavenDescriptor>false</addMavenDescriptor>
        </archive>
    </configuration>
</plugin>

Troubleshooting dependencies

Sometimes understanding why a dependency was generated can be tedious, so fortunately launching the jahia:dependencies goal in debug mode will generate a lot of interesting logging information on where it has extracted a dependency. You can activate the debug mode simply by using the -X option on the command line as in the following example:

mvn -X jahia:dependencies

Deactivating/controlling content definition scanning

If for some reason you need to control the content definition scanning, there are three possibilities:

  • artifactExcludes property: deactivate it for specific files by using the artifactExcludes configuration option
    <artifactExcludes>
        <exclude>org.jahia.modules:*</exclude>
        <exclude>org.jahia.templates:*</exclude>
        <exclude>org.jahia.test:*</exclude>
        <exclude>*.jahia.modules</exclude>
    </artifactExcludes>
  • scanDirectories property: using the scanDirectory you can specify a list of directories to scan. The default value is:
    scanDirectories.add(project.getBasedir() + "/src/main/resources");
    scanDirectories.add(project.getBasedir() + "/src/main/import");
    scanDirectories.add(project.getBasedir() + "/src/main/webapp");
  • excludeFromDirectoryScan : you can specify which files to exclude from the directory scan
    <excludeFromDirectoryScan>
        <exclude>imports/import.xml</exclude>
        <exclude>imports/importIndexOptionNodes.xml</exclude>
    </excludeFromDirectoryScan>

     

jahia:find-package-uses goal

Our Jahia Maven plugin has new tools to help with resolving dependency problems when generating bundles using the Maven bundle plugin. Often this plugin will generate imports for packages that you might not know where they are coming from. It is usually due to some third-party library that has a dependency on a package either by importing it, or by using it in a Class.forName() reflection API call (BND actually finds these).

Here is an example with our Jahia test module project (jahia/test/jahia-test-module). We noticed on deployment that it was generating two strange imports: kaffe.util and weblogic. We can use the new find-packages-uses goal to figure out where they are used:

mvn jahia:find-package-uses -DpackageNames=weblogic,kaffe.util

This will generate the following result:

[INFO] =================================================================================
[INFO] SEARCH RESULTS
[INFO] ---------------------------------------------------------------------------------
[INFO] Found package weblogic used in class org.apache.tools.ant.taskdefs.rmic.WLRmic from trail org.jahia.test:jahia-test-module:bundle:7.3.0.0 -> org.apache.ant:ant:jar:1.8.2
[INFO] Found package kaffe.util used in class org.apache.tools.ant.util.JavaEnvUtils from trail org.jahia.test:jahia-test-module:bundle:7.3.0.0 -> org.apache.ant:ant:jar:1.8.2

We can then go into the JARs for these two dependencies and decompile the classes using JD-GUI to understand the usage. We can then mark the dependencies as optional in the Maven Bundle Plugin configuration as in the following example:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>${project.name}</Bundle-Name>
            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
            <Bundle-Category>jahia-module</Bundle-Category>
            <Implementation-Title>${project.name}</Implementation-Title>
            <Implementation-Version>${project.version}</Implementation-Version>
            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
            <Implementation-URL>${project.organization.url}</Implementation-URL>
            <Specification-Title>${project.name}</Specification-Title>
            <Specification-Version>${project.version}</Specification-Version>
            <Specification-Vendor>${project.organization.name}</Specification-Vendor>
            <!-- Jahia manifest attributes -->
            <Jahia-Depends>default</Jahia-Depends>
            <Jahia-Deploy-On-Site>all</Jahia-Deploy-On-Site>
            <Jahia-Module-Type>system</Jahia-Module-Type>
            <Jahia-Root-Folder>${project.artifactId}</Jahia-Root-Folder>
            <Jahia-Source-Folders>${project.basedir}</Jahia-Source-Folders>
            <Jahia-Static-Resources>/css,/icons,/images,/img,/javascript</Jahia-Static-Resources>
            <Import-Package>*,
                ...
                kaffe.util;resolution:=optional,
                weblogic;resolution:=optional,
                ...
                ${jahia.modules.importPackage}</Import-Package>
            <Embed-Dependency>*;scope=compile;type=!pom;inline=false</Embed-Dependency>
            <Export-Package>!org.jahia.services.*,org.jahia.test.*,junit.*,org.junit.*</Export-Package>
            <Embed-Transitive>true</Embed-Transitive>
            <_removeheaders>
                Include-Resource,
                Private-Package,
                Embed-Dependency,
                Embed-Transitive
            </_removeheaders>
        </instructions>
    </configuration>
</plugin>

jahia:find-packages goal

We could use the findPackages goal if we suspected that a dependency included some specific packages, as in the following example:

mvn jahia:find-packages -DpackageNames=weblogic,kaffe.util

This generates the following result:

[INFO] =================================================================================
[INFO] SEARCH RESULTS
[INFO] ---------------------------------------------------------------------------------
[WARNING] Couldn't find weblogic anywhere !
[WARNING] Couldn't find kaffe.util anywhere !

This confirms we were right in marking the dependencies as optional, as we don’t have them in our project dependencies but they might be provided at runtime by another OSGi bundles, but they are also not required at runtime.

jahia:osgi-inspect goal

This new goal makes easy to dump the headers of an OSGi bundle JAR file. It will also work with normal JARs though, so it can be useful to check if the headers were properly generated. By default, it will look for the project’s generated artifact (must have been previously generated). Here is an example that will print out the contents of the project’s artifact JAR:

mvn jahia:osgi-inspect

If you prefer to pass a parameter to specify which JARs should be inspected, you can simply use the jarBundles parameter as in the following example:

mvn jahia:osgi-inspect -DjarBundles=target/project-1.0-SNAPSHOT.jar,target/project-1.0-SNAPSHOT-sources.jar

This will print out the headers for both JAR files.

Felix Maven Bundle Plugin

(Note: the following section is a reproduction of the online Felix Maven Bundle plugin documentation, available in full here: http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html) This plugin for Maven 2 is based on the BND tool from Peter Kriens. The way BND works is by treating your project as a big collection of classes (e.g., project code, dependencies, and the class path). The way you create a bundle with BND is to tell it the content of the bundle's JAR file as a subset of the available classes. This plugin wraps BND to make it work specifically with the Maven 2 project structure and to provide it with reasonable default behavior for Maven 2 projects.

More information about the plugin

You can find more documentation and information about the plugin at the official site: http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html As well as the Maven standard plugin documentation available here: http://svn.apache.org/repos/asf/felix/releases/maven-bundle-plugin-2.3.7/doc/site/index.html

Apache Felix OSGi Web Console

dx-osgi-web-console.png

The Apache Felix Web Console is an OSGi tool that is integrated into Jahia. To access it, open a browser at the URL: http://localhost:8080/tools, enter the requested login information and then click on “OSGi console”. The Web Console is a powerful tool to see the internals of the Jahia OSGi framework. As this is an integrated external tool, to learn more please go to the Apache Felix Web Console project website: felix.apache.org/documentation/subprojects/apache-felix-web-console.html.

Apache Karaf SSH command line shell

Integrated directly into Jahia, enabled only for localhost by default for security reasons, the Apache Karaf command line shell is incredibly useful to diagnose and query the OSGi framework in the cases where the Web Console might not yet be available (during Jahia startup for example), or simply when users prefer a command line interface.

Command line configuration

By default, the shell is only available for localhost (127.0.0.1) on port 8101. To change this default settings, while the Jahia server is not running, open up the Jahia configuration file located at WEB-INF/etc/config/jahia.properties, uncomment the following line and set its value to something like this:

# The following setting is used to change the port which the
# Apache Karaf OSGi command line shell will listen to for SSH
# connections. Set it to a negative value to disable this feature
# entirely.
karaf.remoteShell.port = 8101
# The bind address for the SSH shell. '127.0.0.1' means the SSH shell
# will only allow local connections to be established. You may want to
# define here an dedicated IP address, the console will bind to, or
# a '0.0.0.0' which will mean it will be bound to all available
# network interfaces.
karaf.remoteShell.host = 127.0.0.1

You can find more information about Apache Karaf configuration here:

https://karaf.apache.org/manual/latest/#_sshd_server

Configuration files are available here:

digital-factory-data/karaf/etc

Accessing the Apache Karaf SSH shell

The Apache Karaf shell is accessible via SSH:

ssh –p 8101 jahia@localhost

To connect, use the same credentials as a user who can access Jahia Tools, for example, as a user who is a member of the System administrator system role. For information on Jahia Tools permissions, see Managing roles and permissions. The first connection will ask you to allow the host as a known secured host, then you should be greeted with a screen that looks something like this:

_ ~ _ ssh -p 8101 jahia@localhost
The authenticity of host '[localhost]:8101 ([127.0.0.1]:8101)' can't be established.
RSA key fingerprint is SHA256:AhtzuXQ90I5l7qvhxTTLZsOxw2S47aRhqYvwBZXhOtA.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:8101' (RSA) to the list of known hosts.
Password authentication
Password:
      \ \  __ _| |__ (_) __ _
       \ \/ _` | '_ \| |/ _` |
    /\_/ / (_| | | | | | (_| |
    \___/ \__,_|_| |_|_|\__,_|

  Jahia 8.0.0.0 [Indigo]

(©) Copyright 2002-2020 Jahia Solutions Group SA - All rights reserved.
Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'logout' to disconnect shell from current session.
jahia@dx()>

You can then type in command such as: jahia:modules This will generate an output that looks something like this:

Id | State | Symbolic-Name | Version | Depends on | Details
------------------------------------------------------------------------------------------------------------------------------------
139 | Installed | calendar | 2.0.3 | [default] |
162 | Installed | bootstrap-components | 3.0.2 | [default, bootstrap, search,tabularList] |
71 | Started | dx-base-demo-core | 1.0.1-SNAPSHOT | [default, event, rating, topstories] |
72 | Started | translateworkflow | 2.0.4 | [default] |
73 | Started | twitter | 2.0.2 | [default] | 
74 | Started | document-thumbnails | 2.0.6-SNAPSHOT | [document-management-api, default] |
75 | Started | sitemap | 2.0.4 | [default] |

The jahia:modules command gives precise state information on the deployed Jahia modules.

JCR Commands

We also provide from the console several commands that let you browse the JCR from the console. The prefix is jcr Available commands are:

  • jcr:cd : to change the current path
  • jcr:l : to list the nodes under the current path
  • jcr:prop-delete : to delete a property
  • jcr:prop-get : to read a property
  • jcr:prop-set : to set a property
  • jcr:query : to perform a query
  • jcr:workspace : to read current or switch workspace Example:
    jahia@dx()> jcr:cd users/root/
    /users/root
    jahia@dx()> jcr:l
    Name | UUID | Type
    ----------------------------------------------------------------------------
    files | 5732847d-41e0-4eff-a339-631fcd61916f | jnt:folder
    portlets | a09c11e2-8f8a-4176-bce2-3bf066ee80f1 | jnt:portletFolder
    contents | 3a4c2fab-c365-4a40-b909-9cdf753cda69 | jnt:contentFolder
    preferences | cdf802b0-53cf-48a1-98a4-8954331f46e9 | jnt:preferences
    j:acl | c48d8201-cbf2-4e44-8952-3f16d1266d0b | jnt:acl
    j:addresses | 89a98897-8ab0-47db-8c57-b9c1f6e5cb43 | jnt:addresses
    j:phones | 03643752-ff35-4178-abc3-24a8f0ed842e | jnt:phoneNumbers
    passwordHistory | 40231a5b-b9e1-4f3f-955e-d204e81ac1ba | jnt:passwordHistory
    workflowTasks | e7975eed-8def-4850-ba46-470cba7a5080 | jnt:tasks

     

SSH Shell Public key authentication

It is possible to also configure public key based authentication for the Karaf SSH Shell. Here are the steps to enable it:

  1. Using OpenSSH in a shell terminal, execute the command: ssh-keygen -t rsa -f shuber.id_rsa
  2. Put the generated key part from the shuber.id_rsa.pub into the digital-factory-data/karaf/etc/keys.properties file like in the following example:
    shuber=AAAAB3NzaC1yc2EAAAADAQABAAABAQDQc2Y/CCfBplGMdYfNxUco+XrlwJVMH6tjiiNSx5yxLW2XnKHtsNtTAaLbUQdz27dhZCQmguFf6KHE6btuPeqVh7oilLA59N0MtPP941+p5Kkdlq93mOLGxmxcHiEc2DfewwQKUdx7fgpJmihZqm9nH0Irc41HTefuwXWTIUeEL5VdXKyZpmPRFuzMsE/OJcyACUbKGadr9SND8u/ehszrRePQGs/WZNNtNtiycZ+6U+5dDclX6GSH6v22SUxd6IS7EideMxJts4p7KEvx2vnAy7WIK5iuo0BTBwV8Hw9Q11x0XVkRqjAqw4PvbEJhlEMsw1Yk69Dagvl5uQ9xiF/B,_g_:admingroup
    _g_\:admingroup = group,admin,manager,viewer,systembundles
  3. Modify the digital-factory-data/karaf/etc/org.apache.karaf.shell.cfg file setting :
    sshRealm = jahia
    to
    sshRealm = karaf
  4. Startup Jahia and try to login using:
    ssh -vvv -p 8101 -i ./shuber.id_rsa shuber@localhost

It should now work (You can remove the -vvv if everything works it's only there to generate debug information).

Note: Changing the realm will deactivate the ability to login to /tools with a valid username and password, but that should be acceptable for most scenarios.