YAML extensions for lighter development: to JCR and beyond

Published on April 25, 2016 by Aleksandr Pchelintcev



In this post I'd like to feature a small module I've developed that aims to improve the Magnolia configuration experience. More than a year ago we started an effort called Light Development which, among many things, implied new ways of configuring the essential Magnolia CMS components like templates, dialogs, apps etc. As a result we have gathered the previously not related registries of such components under the common API and introduced the notion of configuration sources. So far, existing sources include the good old JCR and the new YAML implementations.

Configuration via YAML seems to have been warmly welcomed by the community proving itself to be more expressive and concise than JCR version and also easier to manipulate - just open a YAML file in a favorite text editor. However, there is one feature that YAML configuration source still lacks (mostly) - component inheritance, also known as JCR's extends property. Of course, there is there is the !include Yaml tag which allows to inject a verbatim copy of one YAML file into another. However, unfortunately, until now it was not possible to modify the injected data.

With no further ado please meet my light-yaml module which proudly claims to fill this gap and even go beyond it!

(Photo credit: http://www.tor.com/2014/11/07/toy-story-4-announced/)

What's in it?

Light-yaml provides a set of YAML tags making it possible to configure things by extending and tuning other components' configuration from different sources:

  • !extend - include another YAML file into your own one and alter it as your current project requires in a really similar spirit to JCR's extends.
  • !extend-jc - a bridge between YAML and JCR, allows to import the configuration data from an arbitrary JCR node and define the missing properties and sub-definitions with YAML.
  • !extend-def - no need to specify the configuration source - just extend a definition from literally  any registry by its id.
  • !override - causes the same effect as extends:override  does for JCR: for the annotated part of extended configuration do not use the source at all - only rely on current file's contents.
  • !exclude - completely ignore some part of source configuration.
  • !include - for compatibility reasons the currently implemented inclusion tag is also supported.

 

How does it look?

Now we are going to take a look at some well-known dialog configuration samples enhanced with the new YAML capabilities. Let's assume we have a module called e.g. light-yaml-sample. Upon installation our module bootstraps some field definition samples at /modules/light-yaml-sample/fields. Roughly the JCR sub-tree structure could look like this:

 

/modules/light-yaml-sample/fields/

/modules
    /light-yaml-sample
        /fields
            /foo
                class: i.m.ui.form.field.definition.TextFieldDefinition
                label: Sample Text Field
            /bar
                class: i.m.ui.form.field.definition.SelectFieldDefinition
                label: Sample Select Field

Now it's time to add some reusable data in a couple of YAML files. First the file will pre-configure a sample form tab. The special part here is that the tab includes the field definitions from the JCR sample above!

 

/light-yaml-sample/samples/sampleTab.yaml

# The following will populate a field list by including two field definitions (foo and bar) from JCR node and complement them with a third one (baz)

fields: !extend-jcr:/modules/light-yaml-sample/fields
    - name: baz
      description:
      class: i.m.ui.form.field.definition.DateFieldDefinition


​Another snippet will provide some common actions:

/light-yaml-sample/samples/common-actions.yaml

commit:
    label: commit
    class: i.m.ui.ac.dialog.action.SaveDialogActionDefinition

cancel:
    label: cancel
    class: i.m.ui.ac.dialog.action.CancelDialogActionDefinition

Now let's create a custom dialog definition based on the snippets above:

/light-yaml-sample/dialogs/dialogA.yaml

form:
  tabs:
    # We extend a sample tab here and add a fourth field definition.
    - !extend:/light-yaml-sample/samples/sampleTab.yaml
      name: extendedTab
      fields:
        - name: qux
          class: i.m.ui.form.field.definition.LinkFieldDefinition
        # We also change the type of the field foo from simple text field to a rich-text field
        - name: foo
          class: i.m.ui.form.field.definition.RichTextFieldDefinition
# Actions are reused from the sampple file as
actions: !extend:/light-yaml-sample/samples/common-actions.yaml

So what we have got so far? We have created a dialog definition with one tab with four fields, two of which come primarily from JCR (foo and bar), one added by the /light-yaml-sample/samples/sampleTab.yaml file (baz) and one that is defined in the resulting file /light-yaml-sample/dialogs/dialogA.yaml (qux), in which we also override a property of field definition from JCR (type of field foo). Pretty powerful already, right?

To complete the picture, let's now create another definition which extends and alters the dialogA via registry reference:

/light-yaml-sample/dialogs/dialogB.yaml

# We specify the type of a definition we are going to extend (in this case it is 'dialog' which automatically maps to the DialogDefinitionRegistry) and the target definition reference in the registry,
# for the case of dialogs it consists of a module name + definition name

!extend-def:dialog:light-yaml-sample:dialogA
form:
  tabs:
    - name: extendedTab    
      # Despite the fact that we extend dialogA definition, the 'extendedTab' will contain only one field definition since we annotate 'fields' list with !override tag
      fields: !override
        - name: quux
          class: i.m.ui.form.field.definition.TextFieldDefinition
actions:
    # dialogB is not going to contain the 'commit' action, 'cancel' action is still going to be there
    commit: !exclude

 

How does it work?

I won't bore you with too many of the implementation details here: for those who are interested - the source code is available in our git repository. However it's worth highlighting several techniques that bring those funky extensions to life:

  • All of the extended entities - whether they are YAML files, JCR nodes or definition objects - are treated as so called configuration dependencies, which are recursively resolved during YAML file processing and are tracked.
  • All dependencies, as well as the root file contents, are converted to the map representation and mashed-up, providing the foundation for the resulting definition. No proxy voodoo involved here.
  • Conversion/evaluation happens lazily allowing registries to be populated and JCR repository to be properly bootstrapped during start-up.
  • With all the tracking and managed evaluation it is fairly easy to detect the changes in the hierarchy and re-evaluate the definition when any of the dependencies changes, providing a reliable solution for issues like this one.

How do I start using it?

Easily! Currently the module automatically replaces the current YAML configuration sources with the extended one for all the modules. The old files should still work thanks to the backported !include directive. Otherwise the system should be ready to be extended with the new annotations/ All you need to do is to add the following dependency to a web-app:

<dependency>

 <groupId>org.mgnlconfig</groupId>

 <artifactId>light-yaml</artifactId>

 <version>1.1</version>

</dependency>

 

That's about it - hope you'll like it! Feel free to contact me with any feedback about the module, the PRs are welcome in the Git repo and the bug reports can land in the JIRA project. Also feel free to talk to me at our conference in June - I'd love to know your opinion and thoughts on how we can push Magnolia configuration capabilities even further.

 

Further development

At the moment the partial extension of a YAML file is not yet implemented - doesn't seem to be too hard, just a matter of time. Also it is an open question whether the annotations should follow the JCR's pattern and be called extends-... or stay as they are right now (without 's' at the end), probably in the next version - both variants will be supported. Stay tuned!



Comments



{{item.userId}}   {{item.timestamp | timestampToDate}}

About the author Aleksandr Pchelintcev

Aleksandr is in the Magnolia development team and is responsible for tasks around UI and core features. In his spare time he likes playing football and reading about technology. His posts will focus on tinkering and extending Magnolia APIs and improving user experience with the Vaadin framework.


See all posts on Aleksandr Pchelintcev

Demo site Contact us Free trial