Composum Models

Describes the usage of Composum Models and its integration with Sling Models.

Scope

Composum includes a framework to facilitate creating model objects. The base class AbstractModel includes many helper methods for accessing commonly used items including properties inherited from upwards in the page hierarchy. The Composum Taglibs support the creation of such models. 

Sling-Models provides a declarative way to access properties, while keeping the code lean. This page describes how to use Sling Models in the Composum framework and how to get the best of both worlds.

Creation of Models

Sling-Models is integrated into the Sling Adapter framework, creating models from a single object like a Resource or SlingHttpServletRequest. Since Models often not only depend on the Resource, but also on the SlingHttpServletRequest and sometimes even on the response, the Composum Framework suggests to use a container BeanContext as basis for the creation of models, which contains a resource, a resolver, the request, the response and also supports the storage of additional attributes in various scopes and the access to OSGI-Services. Using BeanContext it is not necessary to restructure the Model if one discovers later during development the need to use information from other things than the e.g. resource. Thus, the use of BeanContext is recommended as the Adaptable to create the models from.

AbstractModel.getProperty / @Property Annotation

The Composum Pages module provides a base class AbstractModel for models that provides many utility methods for accessing elements of the Pages framework and accessing properties read from the resource(s). The Composum Platform includes a module models that provides extensions to Sling-Models allow its relatively easy annotation-based injection in a way compatible to AbstractModel and the other parts of the Pages framework. 

The following example illustrates various features of this:

@Model(adaptables = BeanContext.class) @PropertyDefaults(i18nStrategy = PagesInternationalizationStrategy.class, inheritanceType = InheritedValues.Type.contentRelated) @PropertyDetermineResourceStrategy(Page.PageDetermineResourceStrategy.class) public static class MyOwnModel extends AbstractModel { @Property private String cssClass; @Property(name = "test/avalue", basePath = "config", inherited = true) @Default(intValues = 7) private Integer avalue; @Property(i18n = true) @Optional private String title; @Self @Via(value = "config", type = DescendantPath.class) MyProps props; MyProps getProps() { return myprops; } @Model(adaptables = BeanContext.class) public interface MyProps { @Property(i18n = true) String getHeadline(); } }

The annotation @Property provides the injection of values compatible to AbstractModel.getProperty and AbstractModel.getInherited, providing configurable internationalization and various inheritance strategies from parent reources or containing pages.

The inner interface MyProps illustrates a pattern that could reduce the necessary code when exporting many properties. Sling-Models is able to inject values into fields, which require a corresponding getter when exported. But it is able to automatically implement an interface (here MyProps) with methods annotated with the injection annotations. Thus, every exported property requires only the getter. The DescendantPath ViaProvider allows selecting descendant paths (e.g. "config") from the current resource while keeping the original adaptable type at SlingHttpServletRequest or BeanContext (or Resource).

The @PropertyDefaults allows to specify defaults for all @Property annotations given in the class, such as the strategy for finding internationalized values and for property inheritance, and the @PropertyDetermineResourceStrategy is a hook that allows to change the resource that is used for finding the properties to, in this case, the containing page of the resource. Both are just put in for illustration here, these are not necessary when deriving a model from AbstractModel since it already contains the appropriate default annotations. 

The following discussion applies to both the use of AbstractModel.getProperty / AbstractModel.getInherited and use of the (Composum-)annotation @Property within Sling-Models.

Internationalization / Localization

The Composum Pages framework supports the storage of internationalized (that is in this context locale dependent) properties within the JCR-repository. When a i18n-able property name is stored at a resource at path, it is looked up at the following paths, depending on the current locale (the examples refer to a hypothetical locale with language=de, country=DE, variant=EURO).

  • path/i18n/language_country_variant/name, for example path/i18n/de_DE_EURO/name
  • path/i18n/language_country/name, for example path/i18n/de_DE/name
  • path/i18n/language/name, for example path/i18n/de/name
  • path/name, as is; the value in the default language

Please note that path is often the node jcr:content of e.g. a page or site or a subnode of that, and name can itself be a path - resulting e.g. in /content/site/home/about/jcr:content/meta/i18n/de_DE/search/title. 

Property Inheritance

If a property cannot be found for the current resource, it is possible to apply inheritance by searching the property at paths starting from parents of the resource. There are various strategies (compare InheritedValues.Type); in the Composum Pages framework the strategy InheritedValues.Type.contentBased is usually used. If the property is not found at the current resource, the property is looked up with the same relative path below the jcr:content parent at the parents of the resource. For example, if the resource has a path is /n1/n2/n3/jcr:content/aaa/bbb and the name of the property is the relative path p1/p2, the property is set to the first value found at the paths /n1/n2/n3/jcr:content/aaa/bbb/p1/p2, /n1/n2/jcr:content/aaa/bbb/p1/p2 and /n1/jcr:content/aaa/bbb/p1/p2.

Notes

  • The Composum Pages model classes extending com.composum.pages.commons.model.Model do not currently use Sling-Models - the instantiation of the classes is usually done directly by calling a constructor with various arguments. When creating new classes implementing Model or extending AbstractModel are created, it is possible to introduce Sling-Models annotations and instantiate these with BeanContext.adaptTo instead of a constructor or the Composum Taglibs.

Implementation concerns and limitations

Since Sling-Models does unfortunately not allow to initialize already constructed objects, it is no longer possible to create a SlingBean by calling its constructor and initialize(BeanContext) it, since one cannot know whether there are Sling-Model parts contained in it that would not be initialized. It is always necessary to use BeanContext.adaptTo, instead, possibly using .withResource:

Since the injectors within Sling-Models do not have access to the class being constructed nor the object being constructed, the defaults for injecting with @Property can only be determined by the @PropertyDefaults annotation found from the AnnotatedElement to be injected. Thus, these cannot be changed downwards the inheritance hierarchy of a model, and it is not possible to use @Property on constructor parameters, since these do currently (as of Sling-Models 1.4.2) not allow determine the actual constructor with its declaring class.