Make your CQ life easier with Slice

01 November 2012
Maciej Majchrzak
Frink_Cognifide_2016_HeaderImages_0117
Are you happy with your Sling/CQ development? If yes, you’re a lucky guy – you’ve just saved a couple of minutes because there’s no need for you to read this post.

You’re still reading this, which means that you want to do your job better, write your CQ applications faster, produce better quality code; or have your code testable and reusable... or you’re just curious. Whatever your motivation is, you won’t regret reading this post because it will change the way you develop your CQ applications - exactly as Slice changed ours.

Slice has been developed internally in Cognifide and is now released to the public. It is a framework that makes Sling/CQ development much easier and more efficient. Take a while to read this short introduction to Slice and find out how you can benefit from using it.

So, why should I use it?

I promised you in the title of this post that your life (your life as a CQ developer) will be easier when using Slice. Here’s a couple of benefits you can gain:
  • No more scriptlets – writing business logic of your app in JSPs is generally not a good idea, there are plenty of reasons, and plenty of post/articles that discuss this topic (like http://stackoverflow.com/a/1832327). With Slice you write your business logic in Java; JSP is a presentation layer only.
  • Avoid re-inventing the wheel – think of how many times you have had to re-write your code, especially when starting a new project? The Slice framework makes it easier because it provides a lot of features you can reuse. You can also reuse your own modules and libraries.
  • Improve the testability of your code – you just write simple Java classes. Thanks to dependency injection (DI) they can be easily unit-tested.
  • Low cost of introduction of new developers into projects – Slice is easy to learn and if you use it in all your projects, your developers know exactly how to start.
  • Faster development – code reuse, dependency injection and simplicity make you more efficient than ever.
  • Reduce maintenance costs.

We’re taking off

Slice is a framework designed having modularity in mind. We divided it into a couple of modules, and you can add yours if you want. Modularity of the framework is achieved using Guice. Hold on, what?! Not OSGi? Yes, you might be a little bit surprised. You will still use OSGi bundles but inside your application bundles there will be a dependency injection framework. It allows you to inject simple objects which don’t have to be OSGi components/services. You will see in a minute how powerful it is and how it simplifies your code.

The framework is based on the presentation model pattern which means that your views (JSP files) determine which presentation model to use (Java classes) to return underlying data (a Java class with repository data).

 The core feature of Slice is the Mapper. If you’re familiar with Hibernate or any other object-relational mapping (ORM) solution, you can think of the Slice Mapper as a JCR equivalent of ORM. Basically, it maps your JCR content to POJOs. Thanks to this you have access to a repository in your Java objects.

Show time!

You're already familiarised with the basics. Now it’s time for development. We will be writing a simple CQ component and extending it by adding new features, which will show you different aspects of the Slice framework.

 You’re probably expecting to have some installation guide here. I have to upset you - I’ve decided not to write about it as it is covered in the Slice documentation and, basically, there are only some maven dependencies and a bundle activator.

Let’s create a simple text component which is responsible for rendering a piece of text. A JSP file of our text component looks like this:
<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"
%><%@include file="/apps/myapp/core/global.jsp"
%><slice:lookup var="model" type="<%=com.cognifide.myapp.TextModel.class%>"/>
<p>${model.text}</p>
Pay attention to the third line – this is how you define the model of data you expect to be returned by the framework. When you have your model of data to be rendered, you can just output it using JSTL (line 4). Simple as that.

A model class is more interesting:
package com.cognifide.myapp;

import com.cognifide.slice.mapper.annotation.JcrProperty;
import com.cognifide.slice.mapper.annotation.SliceResource;

@SliceResource
public class TextModel {

	@JcrProperty
	private String text;

	public String getText() {
		return text;
	}

}
We’ve got here a simple POJO class annotated by @SliceResource. This is a key annotation in Slice which informs the framework that you want to have objects of this class to be mapped from the underlying resource. Mapping resources in Slice

How does the Mapper work?

The Mapper reads the Sling resource provided by the framework (in our example it is a resource of the text component) and writes its properties to corresponding fields in a Java object. The matching between properties and fields is done by names.

All is needed to map an object is the @SliceResource annotation put at the class level. A class can define if all its fields should be mapped or only annotated. This can be done by specifying the MappingStrategy in @SliceResource:

  • @SliceResource(MappingStrategy.ANNOTATED) or just @SliceResource – only fields annotated by @JcrProperty are mapped,
  • @SliceResource(MappingStrategy.ALL) – all fields are mapped. If there is no property in a resource which corresponds to a field, the field will be set to null.
If a field is of a class annotated by @SliceResource – guess what – it will also be mapped recursively. 

There are also other field-level annotations which can be used. They allow you to either define logic of mapping or transform the mapped value. These annotations include:

  • @Unescaped – unescape an html markup, so that an html value can be correctly displayed – especially useful in fields containing richtext.
  • @ImagePath – transforms String into the CQ image path. Useful when a path to an image must be displayed (like in <img src="image_path">)
  • @SliceReference – my favourite one. It allows you to define a path in a repository where the SliceResource field will be mapped from.

Inject me!

By now, you should understand how our simple TextModel class was populated by repository data and then rendered in a JSP script. So far, so good. Let’s play toys for big boys now.

We haven’t use Guice so far – or, actually, you couldn’t see that Slice made use of it. Let’s modify our text component to display text with a title of a random page.

The JSP file, again, is very simple and performs no business logic – it only displays data provided:
<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"
%><%@include file="/apps/myapp/core/global.jsp"
%><slice:lookup var="model" type="<%=com.cognifide.myapp.PageTextModel.class%>"/>
<p>${model.text}, ${model.pageTitle}</p>

To implement business logic we need the PageManager class and a path to the page. The path will be stored in the underlying resource (chosen by the author using component dialog) and PageManger will be injected. Take a look at the code below:

package com.cognifide.myapp;

import com.cognifide.slice.mapper.annotation.JcrProperty;
import com.cognifide.slice.mapper.annotation.SliceResource;
import com.day.cq.wcm.api.PageManager;
import com.google.inject.Inject;

@SliceResource
public class PageTextModel {

	@JcrProperty
	private String text;

	@JcrProperty
	private String path;

	private final PageManager pageManager;

	@Inject
	public PageTextModel(PageManager pageManager) {
		this.pageManager = pageManager;
	}

	public String getText() {
		return text;
	}

	public String getPageTitle() {
		return pageManager.getPage(path).getTitle();
	}
}
If you want to display the title of a requested page instead of a specified one, you can... inject a requested page:
package com.cognifide.myapp;

import com.cognifide.slice.cq.qualifier.RequestedPage;
import com.cognifide.slice.mapper.annotation.JcrProperty;
import com.cognifide.slice.mapper.annotation.SliceResource;
import com.day.cq.wcm.api.Page;
import com.google.inject.Inject;

@SliceResource
public class PageTextModel {

	@JcrProperty
	private String text;

	private final Page requestedPage;

	@Inject
	public PageTextModel(@RequestedPage Page requestedPage) {
		this.requestedPage = requestedPage;
	}

	public String getText() {
		return text;
	}

	public String getPageTitle() {
		return requestedPage.getTitle();
	}
}
You will probably wonder how the framework knows what the requested page is and how to create this page. The logic of building objects which are injected is implemented in the so called providers. Providers, in turn, are defined in modules. This is a basic Guice concept. Please find out more at Guice website.

 Thanks to this approach you don’t produce boiler-plate code - classes depend on classes they really need. This improves the testability of your classes, too.

Become an expert

This post is not aiming in comprehensive guide to Slice. However, there are some additional features which are worth mentioning before I type the final dot.

Inject OSGi services easily
Thanks to use of Peaberry you can inject OSGi services as if they were standard Java classes.

Scoping of objects
You can define for how long your objects are available and how many instances of your objects Guice should create. There is the request scope provided by Slice - it allows you to have one instance of a class per request, which means that Guice will reuse this instance for all its clients within a single request. Effectively, it's a kind of a very simple cache. You can also have a dedicated scope for using objects in CQ workflows or background asynchronous jobs which are not dependent on a request. Additionally, you can introduce your own scopes, if needed.

Complex composition
Slice provides a number of useful objects which can be instantly injected into your models, e.g. SlingHttpServletRequest, ResourceResolver or PageManager. It also introduces ModelProvider and ChildrenProvider which allows you to obtain a model of data from a specified path (or resource) within a repository. Thanks to this, you can have a model of whatever repository data you need and use it inside your models, e.g.
PageTextModel pageTextModel = modelProvider.get(PageTextModel.class, "/content/mysite/mypage/jcr:content/component");

Simple validation of your models
Slice supports the validation of models and integrates it with JSP views using dedicated JSP tags.

Easy to mock Thanks to testing the framework you can mock a repository easily and use it in your unit tests.

Modularization
Thanks to the modular architecture you can divide your application into logically separated modules.

Get it now

Slice is a framework which made our day-to-day development much easier. Now you can try if it helps you because Slice is open-source! You can find it on Github: https://github.com/Cognifide/Slice
Enjoy! 

P.S. Some useful links: