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 previous versions it was usually done by declaring Spring Beans. These beans were processed and registered into Jahia core. It is now possible to do the same thing by using OSGi services, without relying on any Spring context. You just need to expose the same classes as OSGi services, with the same properties. The following list gives you which class or interface to extend and to expose as a service :
Render Actions : org.jahia.bin.Action
org.jahia.services.render.filter.RenderFilter
org.jahia.bin.errors.ErrorHandler
org.jahia.services.content.nodetypes.initializers.ModuleChoiceListInitializer
and org.jahia.services.content.nodetypes.renderer.ModuleChoiceListRenderer
org.jahia.services.content.DefaultEventListener
org.jahia.services.content.decorator.JCRNodeDecoratorDefinition
and org.jahia.services.content.decorator.validation.JCRNodeValidatorDefinition
org.jahia.services.render.filter.cache.CacheKeyPartGenerator
org.jahia.bin.filters.AbstractServletFilter
Some extensions need to be declared explicitely in a specific class, which contains a map of all the extensions you want to register. Only one instance of this class need to be exposed as a service to declare your extensions :
org.jahia.services.content.decorator.JCRNodeDecoratorDefinition
and override getDecorators to return your decorators classes.org.jahia.services.content.decorator.validation.JCRNodeValidatorDefinition
and override getValidators to return your validator classes.org.jahia.services.content.rules.ModuleGlobalObject
In the same way, some configurations which were done with Spring earlier can still be done by declaring the beans as an OSGi service :
org.jahia.services.render.StaticAssetMapping
org.jahia.services.workflow.WorkflowTypeRegistration
Sample code showing how to achieve that with Declarative Services are available on https://github.com/Jahia/OSGi-modules-samples .
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");
}
A component can also rely on an OSGi configuration. The configuration will have by default 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
will be 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. It 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 configuration files can be provided in modules in resources/META-INF/configurations
- they will be copied to <data>/karaf/etc
.
Another useful feature of the 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 configurations files, all of them named org.mypackage.MyFilter-<id>.cfg
, with a different id for each config. One instance of the component will be created for each configuration.