Thursday 26 May 2016

Creating a SlingModel object

Sling Model objects can be used within Sightly to hold data from the JCR and also to request data from other OSGi resources.  A simple example of a SlingModel object is below.  In this example the Commerce API is used to get product data within the SlingModel so that the data can be displayed within a product page.

You have to be a bit careful with SlingModels as the @Inject will only work for the node that you are on.  For example, if you have a parsys and a component within it then the @SlingObject and the @Inject will be applicable to the component within the parsys rather than the actual page that holds the parsys.  There are two examples of sling models below.  One uses the @Inject to get a property and the other adapts the resource to a PageManager to get the containing page.  This may be useful if you have used the commerce API to create product pages as the cq:productMaster value is on the actual page but the @SlingObject may be a component within a parsys.

Some basic information about the Sling Model can be found here https://sling.apache.org/documentation/bundles/models.html.

Pom.xml

Without checking the pom.xml the sling model may never be wired.  The apache.felix plugin must be told where the class annotated with @Model are so that it creates the correct xml as part of the build of this bundle.

    <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
            <instructions>
                <Sling-Model-Packages>com.me.sling.model</Sling-Model-Packages>
            </instructions>
        </configuration>
    </plugin>

If this isn't included then the model will never be wired with the OSGiService or any of the @Inject annotations that are available.  Also the @PostConstruct won't be called.  This will result in a NullPointerException when a page tries to use this ProductData class.

SlingModel Class

package com.me.sling.model;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceService;
import com.adobe.cq.commerce.api.CommerceServiceFactory;
import com.adobe.cq.commerce.api.Product;

/**
 * Use the CommerceService to load the product information using an example SlingModel
 */
@Model(adaptables = Resource.class)
public class ProductData
{
    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ProductData.class);

    /**
     * The commerce service used to load the product.
     */
    @OSGiService(filter = "(service.pid=com.me.MyCommerceServiceFactory)")
    private CommerceServiceFactory commerceServiceFactory;

    /**
     * The product master value.  This is a default value which is created by a RollOut 
     * of a catalog in the commerce api process. This references the product location
     * in the /etc/commerce/products area.
     * 
     */
    @Inject
    @Named("cq:productMaster")
    private String productMaster;

    /**
     * The current resource.
     */
    @SlingObject
    private Resource currentResource;

    /**
     * This is the product.
     */
    private Product product;

    /**
     * Load the product value.
     *
     * @throws CommerceException Thrown if there is a problem loading the product
     */
    @PostConstruct
    public void create() throws CommerceException
    {
        LOG.warn("PostConstruct called");
        LOG.debug("productMaster {}", productMaster);

        LOG.debug("CommerceServiceFactory {}", commerceServiceFactory);

        final CommerceService commerceService = commerceServiceFactory.getCommerceService(currentResource);
        LOG.debug("CommerceService {}", commerceService);

        product = commerceService.getProduct(productMaster);
    }

    /**
     * Get the SKU.
     *
     * @return The SKU / Identifier / Product Code
     */
    public String getSku()
    {
        return product.getSKU();
    }
}

Here the @OSGiService annotation is used to reference a Service.  We use the filter to make sure we get the correct service that we need in case there are duplicate implementations of the interface available.
The @SlingObject is an annotation which just allows the current sling resource to be mapped into this Sling Model.

SlingModel Class (With Parsys)

In this example the component which uses this SlingModel object is within a parsys in the page.  Therefore the @Inject @Named("cq:productMaster") doesn't work because that value lives on the Page rather than on the component.  


package com.me.sling.model;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceService;
import com.adobe.cq.commerce.api.CommerceServiceFactory;
import com.adobe.cq.commerce.api.Product;

/**
 * Use the CommerceService to load the product information using an example SlingModel
 */
@Model(adaptables = Resource.class)
public class ProductData
{
    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ProductData.class);

    /**
     * The commerce service used to load the product.
     */
    @OSGiService(filter = "(service.pid=com.me.MyCommerceServiceFactory)")
    private CommerceServiceFactory commerceServiceFactory;

    /**
     * The current resource.
     */
    @SlingObject
    private Resource currentResource;

    /**
     * This is the product.
     */
    private Product product;

    /**
     * Load the product value.
     *
     * @throws CommerceException Thrown if there is a problem loading the product
     */
    @PostConstruct
    public void create() throws CommerceException
    {
        LOG.warn("PostConstruct called");
        LOG.debug("productMaster {}", productMaster);

        LOG.debug("CommerceServiceFactory {}", commerceServiceFactory);


        final PageManager pageManager = currentResource.getResourceResolver().adaptTo(PageManager.class);
        final Page currentPage = pageManager.getContainingPage(currentResource);
        LOG.debug("currentPage {}", currentPage.getPath());

        final String productMaster = currentPage.getProperties().get("cq:productMaster", String.class);

        final CommerceService commerceService = commerceServiceFactory.getCommerceService(currentResource);
        LOG.debug("CommerceService {}", commerceService);

        product = commerceService.getProduct(productMaster);
    }

    /**
     * Get the SKU.
     *
     * @return The SKU / Identifier / Product Code
     */
    public String getSku()
    {
        return product.getSKU();
    }
}

Sightly

Use of the Sling Model within sightly is really straightforward.  Just tell Sightly to use this class.  The data-sly-use.model is the name that the data can then be referenced by.  Highlighted below,

    <sly data-sly-use.model="com.me.sling.model.ProductData">

    Page Product Sly
    <p>Title: <span>${properties.jcr:title}</span></p>
    <p>Description: <span>${properties.jcr:description}</span></p>
    <p>Identifier: <span>${model.sku}</span></p>


No comments:

Post a Comment