Developing Jahia extensions

October 8, 2024

Extension points

Multiple parts of Jahia can be extended by adding Java classes in modules. A module developer can, for example, add a new action that will add a new HTTP endpoint, add a render filter that will change the rendering, or modify the HTML cache key generation by adding CacheKeyPartGenerator.

In versions before Jahia 8, this was usually done by declaring Spring Beans. These beans were processed and registered into the Jahia core. Now you can do the same thing with OSGi services, without relying on any Spring context. You just need to expose the same classes as OSGi services with the same properties.

Using Declarative Services to build extensions

All extension points in Jahia should be implemented using OSGi Declarative Services. This section shows how to achieve this before looking at the various extension types.

Code samples

Sample code showing how to extend Jahia with Declarative Services (DS) are available at https://github.com/Jahia/OSGi-modules-samples.

Simple component

When using Declarative Services, you need to add the @Component annotation to declare your class as DS component with the service attribute set to the base class. The component can set its attribute directly in its activator:

@Component(service = RenderFilter.class, immediate = true)
public class TestFilter extends AbstractFilter {

    @Activate
    public void activate() {
        setPriority(1);
        setApplyOnConfigurations("page");
    }

Configured component

A component can also rely on an OSGi configuration. By default, the configuration has the same name as the component, which is the fully qualified class name. In the following example, the content of <data>/karaf/etc/org.mypackage.MyChoiceListInitializer.cfg is passed in the props parameter of the activator.

@Component(service = {ModuleChoiceListInitializer.class, ModuleChoiceListRenderer.class})
public class MyChoiceListInitializer extends AbstractChoiceListRenderer implements ModuleChoiceListRenderer, ModuleChoiceListInitializer {

    @Activate
    public void activate(Map<String, ?> props) {
        setKey((String) props.get("key"));
    }
}

You can also use an annotation type containing the properties you expect instead of the generic Map<String, ?> type. This allows you to have typed attributes and default values.

    @interface Param {
        String key() default "default-key";
    }

    @Activate
    public void activate(Param p) {
        setName(p.key());
    }

Note that you can provide configuration files in modules in resources/META-INF/configurations. They will be copied to <data>/karaf/etc.

Component factories

Another useful feature of DS components is the component factory. You can create multiple instances of your beans based on multiple configuration files. Compared to the previous example, you need to modify the component scope to Bundle, as in the following example:

@Component(service = RenderFilter.class, scope = ServiceScope.BUNDLE)
public class MyFilter extends AbstractFilter {

    @Activate
    public void activate(Map<String, ?> props) {
        try {
            BeanUtils.copyProperties(this, props);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

You can then define multiple configuration files, all named org.mypackage.MyFilter-<id>.cfg, with a different id for each config. One instance of the component is created for each configuration.

Hot reload

Another benefit of DS components is that it allows hot reloading of components without requiring server restarts. For example, you can use Declarative Services to add a servlet filter (by extending AbstractServletFilter) and deploy without requiring a server restart to activate. (See sample code https://github.com/Jahia/OSGi-modules-samples/tree/master/servlet-filter-samples).

Extension types

Basic usage

Custom GraphQL Query implementation

Definition Adding new queries to Jahia’s GraphQL endpoint
Usage Allows GraphQL clients to retrieve data using custom logic, for example to integrate with a single-page application built using a front-end framework, such as ReactJS or Angular
Example Retrieve data from a third-party server or perform some query/aggregation on JCR content
Class/Interface
org.jahia.modules.graphql.provider.dxm.DXGraphQLExtensionsProvider
More details See GraphQL extensions
Sample code https://github.com/Jahia/graphql-core/tree/master/graphql-extension-example

Custom GraphQL Mutation implementation

Definition Adding new mutations to Jahia’s GraphQL endpoint
Usage Allows GraphQL clients to send data to custom logic hosted in a Jahia module, for example to have a single-page application perform some state-changing event or forward to a third-party server application.
Example Send data collected from the user to a custom backend service
Class/Interface org.jahia.modules.graphql.provider.dxm.DXGraphQLExtensionsProvider
More details See GraphQL extensions
Sample code https://github.com/Jahia/graphql-core/tree/master/graphql-extension-example

Custom Actions

Important: Actions will be deprecated in future versions of Jahia. We recommended that you use custom GraphQL mutations instead.
Definition Defines a new action that can operate on a content object by exposing a new HTTP endpoint
Usage Actions integrate with Jahia’s URL scheme to make it easy to call custom code on a content object using a simple HTTP request
Example Use an action to process a form submission
More details See Manipulating content with APIs>Actions
Sample code https://github.com/Jahia/OSGi-modules-samples/blob/master/action-samples/src/main/java/org/foo/modules/actions/SimpleAction.java
Note the usage of the @Activate method that initializes requireAuthenticatedUser, requirePermission, and other setters.

Custom Render filters

Definition Makes it possible to control the rendering of any content element, such as a page or simple text node
Usage Built-in Jahia render filters including HTML caching, but basically any custom code can be called before and after a content node is rendered, making the possibilities endless
Example Use a render filter to personalize the output based on the location of the IP address
Class/Interface org.jahia.services.render.filter.RenderFilter
More details See Rendering content>Filters
Sample code https://github.com/Jahia/OSGi-modules-samples/blob/master/filter-samples/src/main/java/org/foo/modules/filters/SimpleFilter.java

Custom Choicelist Initializer

Definition Custom code used to populate a dropdown choice list in Content Editor
Usage ChoiceList initializers enable the use of custom logic to populate the content of a field in Content Editor. This applies to many different uses cases, ranging from retrieving content from a content list to calling a third party service to retrieve choice list values
Example Create a choice list of products by connecting to a PIM and retrieving available products
Class/Interface org.jahia.services.content.nodetypes.initializers.ModuleChoiceListInitializer
More details See Defining choicelist initializers and Enhancing content types for editors>Choicelists. Also see the Choicelist developer training topic.
Sample code https://github.com/Jahia/OSGi-modules-samples/blob/master/choicelist-samples/src/main/java/org/foo/modules/choicelists/SimpleChoiceListInitializer.java

Authentication

Custom Authentication Valve

Definition Provides a way to extend Jahia’s authentication mechanism to connect to any custom authentication service
Usage Using custom authentication valves, you can extend Jahia’s built-in system to integrate it with third-party auth services. Valves don’t replace other built-in valves but rather are used in order of priority, making it possible to use multiple auth systems at the same time.
Example Use a custom valve to provide authentication from social media services such as Facebook or Twitter
Class/Interface org.jahia.params.valves.BaseAuthValve
More details See About the Jahia authentication layer>Implementing custom authentication valve
Sample code https://github.com/Jahia/OSGi-modules-samples/blob/master/auth-valve/src/main/java/org/foo/modules/valve/MyValve.java