“He travels the fastest who travels alone.”
Rudyard Kipling
The follow-up article on FreshMarker Features took a little longer because there was simply no feature that wanted to manifest itself in a plugin. In the FreshMarker Includes article, the features for activating and deactivating functionalities within FreshMarker were presented. The outlook was given that FreshMarker plugins should also be able to contribute their own features. How this can work is explained in this article.
Before we get to the additions to FreshMarker, there is a brief summary of the FreshMarker Feature implementation. The features are managed as instances of implementations of the TemplateFeature interface. In order for FreshMarker to recognize a feature, it must be registered with the TemplateFeatures
class. There is a single instance of this class within the Configuration
. If a TemplateBuilder
is created, it receives a snapshot of the current features as an instance of the FeatureSet
class. Features can now be activated and deactivated on this instance. If a Template
is created, this in turn receives a snapshot of the FeatureSet
from the TemplateBuilder
. This ensures that a Template
always generates the same output, even if a feature has been changed in the TemplateBuilder
or in the Configuration
in the meantime.
The features created so far were created directly in FreshMarker.
templateFeatures.addSwitches(IncludeDirectiveFeature.ENABLED, false); templateFeatures.addSwitches(IncludeDirectiveFeature.LIMIT_INCLUDE_LEVEL, true);
This interface has been modified slightly in FreshMarker 1.7.1. A TemplateFeature
uses its isEnabledByDefault
method to signal whether it is initially enabled or disabled. For this reason, the second parameter is no longer necessary for addSwitches
. The addFeatures
method can be used instead.
templateFeatures.addFeatures(IncludeDirectiveFeature.class);
However, in order for plugins to contribute features, we have to lead the registration of new features to the plugins. The easiest way to do this is to extend the PluginProvider
interface. Each plugin implements this interface and different things are configured depending on the overridden methods.
The following method can be overridden by plugins to register your own built-ins in the configuration.
default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) { }
An additional method is required to register your own features.
default void registerFeatures(TemplateFeatures features) { }
A plugin can register its own features using the registerFeatures
method. The following DemoPlugin
with a DemoFeature
shows the code required to register the feature.
public DempPlugin implements PluginProvider { public enum DemoFeature implements TemplateFeature { DEMO } public void registerFeatures(TemplateFeatures features) { features.addFeatures(DemoFeature.class); } }
The DEMO
feature is added to the TemplateFeatures
instance via addFeatures
. As the isEnabledByDefault
method has not been overwritten, the DEMO
feature is activated at the start and can be deactivated in the TemplateBuilder
via without(DemoFeature.DEMO)
.
A plugin that only registers one feature is a little useless, so we still need PluginProvider
methods that allow further configurations with the help of all registered TemplateFeature
.
default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns, FeatureSet features) { registerBuiltIn(builtIns); }
If a plugin overwrites this registerBuildIn
method, then a FeatureSet
is available via which all previously registered TemplateFeatures
can be queried. As the registerFeature
method is called beforehand, the TemplateFeatures
are contained by the plugin.
public void registerPlugin(PluginProvider provider) { provider.registerFeatures(templateFeature) Map<BuiltInKey, BuiltIn> registerBuiltIns = new HashMap<>(this.builtIns); provider.registerBuildIn(registerBuiltIns, templateFeature); this.builtIns = registerBuiltIns; // ... }
Only the new registerBuildIn
method is called by the Configuration
during registration. Existing plugins that implement the old registerBuildIn
method can still be used because the default implementation of the new method simply calls the old one.
This means that support for features in plugins has already been implemented and only the plugins with features are still missing.
The features for plugins and more can be tried out in the new version 1.7.1 of FreshMarker.