Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

One of the core features of Slice is the Mapper. It's an object-resource mapper which can be perceived as JCR equivalent of object-relational mapping (ORM). Basically, it maps a JCR content to Java objects, so that you have access to a repository in your Java objects.

How to map a resource to an object?

If you want a resource to be mapped to an Java object, you have to meet to requirements:

  • class must be annotated with @SliceResource,
  • object must be created using injector. All classes annotated with @SliceResource are bound to SliceResourceProvider which is responsible for reading a current resource and invoking mapping to an object. Usually, you will be using ModelProvider to specify from which resource to map.

You don't need to invoke anything by yourself, if an object is injected and annotated with @SliceResource it will be automatically mapped from current resource.

Mapping

The Mapper reads the resource provided by the framework (current resource) and writes its properties to corresponding fields in a Java object. The matching between properties and fields is done by names. It means that if a name of property is the same as a name of field, the value of the field will be set to the value of the property.

Mapping strategies

A class can define if all its fields should be mapped or only annotated. This can be done by specifying the MappingStrategy as an argument of @SliceResource:

  • @SliceResource(MappingStrategy.ANNOTATED) or just @SliceResource – only fields annotated by @JcrProperty are mapped. This is a default.
  • @SliceResource(MappingStrategy.ALL) – all fields are mapped.

If a field is subject to mapping and there is no property in a resource which corresponds to a field, the field will be set to null.

In some cases the default name matching between properties and fields is not enough, e.g. if you want to map jcr:title property. In such cases you can specify a name of a property from which to read the value. You can do this specifying the String argument in @JcrProperty which will instruct mapper to read a property of specified name.

Examples:

@SliceResource
public class ExampleModel {
    
    @JcrProperty
    private String text;
    
    @JcrProperty("jcr:title")
    private String title;
    
    @JcrProperty("hideInNav")
    private boolean hiddenInNavigation;
    
}
@SliceResource(MappingStrategy.ANNOTATED)  //This is exactly the same as if we put @SliceResource
public class AnotherModel {
    
    private String text; //this field won't be mapped
    
    @JcrProperty("jcr:title")
    private String title;
    
    @JcrProperty("hideInNav")
    private boolean hiddenInNavigation;
    
}
@SliceResource(MappingStrategy.ALL)
public class YetAnotherModel {
    
    private String text; //this field will be mapped

    private long number; //and this will be mapped, too
    
    @JcrProperty("jcr:title")
    private String title;
    
}

Processors

In above examples you could see that some basic types of fields are mapped, including String, boolean, long...  A piece of code which is responsible for mapping a given filed type(s) is called processor. The Mapper is build for extensions which means that you can register a number of processors, each one responsible for a given type. The architecture of mapper is based on visitor pattern. While mapping a resource to an object, the Mapper goes through all fields which should be mapped and for each of these fields it iterates through all register processors checking which one is willing to map the field. If a processor is willing to map the field, it do the actual mapping, meaning it stores a value in this field.

The diagram below depicts processing of an example class. Field text is processed by the DefaultFieldProcessor, similarily the number field. hideInNav field is processed by BooleanFieldProcessor and other by custom processor called FancyFieldProcessor.

There are a number of predefined processors which can be used out of the box. But you can also implement yours and add them to your Mapper. The predefined processors include:

DefaultFieldProcessor

It is used when no other processor was willing to handle a field. It supports mapping of the basic JCR types including:

  • java.lang.String
  • long, java.lang.Long
  • double, java.lang.Double
  • java.util.Calendar

BooleanFieldProcessor

It is used to map to java.land.Boolean or boolean. The reason for having it separated is that it supports converting String properties to boolean values - this is needed because sometimes CQ writes boolean values as String in JCR repository.

ImageFieldProcessor

 

SliceResourceProcessor

SliceReferenceProcessor

Post processors

InitializableModel

It's a common task to do some stuff after the model has been mapped and its fields hold values from underlying resource. You cannot do this in the constructor of your model because mapping is done after the object has been created, so all the fields which should be mapped are null while executing constructor. Therefore, Slice introduces an interface called InitializableModel which allows you to perform an arbitrary logic after the process of mapping has finished.

The InitializableModel interface has only one, parameterless method: void afterCreated(). Here's an example use:

@SliceResource
public class ExampleModel implements InitializableModel {
    
    private final static Logger LOG = LoggerFactory.getLogger(ExampleModel.class);
	
    @JcrProperty
    private String text;
    
    @Override
    public void afterCreated() {
        if (text == null) {
		    LOG.warn("There is no text property in the resource");
	    }
	}
}
  • No labels