JSF 2 / RichFaces portlet in eXo (Part 2/3) : Content Management integration

eXoPlatform comes with powerful content management features and a large set of portlets to use these features. However you may want to use these content management capabilities in your own portlets.
This second tutorial will learn you how to use eXo Content Management API and portlets to create a sample store application. The first part about JSF / RichFaces integration is available here.
This application will expose a list of products with related pictures. Each product will be composed of a name, a description, a price, a thumbnail and some pictures. The thumbnail and the related pictures will be retrieved from the eXo Content Management.
The page will display the product’s thumbnail and the product’s caracteristics. The related pictures will be available by links. A Content Detail portlet will be used to display each related picture.

The source code of this tutorial is available here.

Adding contents

In order to easily retrieve the contents, they should be organized correctly. Under the root folder (let’s say repository:collaboration:/sites content/live/acme/documents/products), a folder will be created for each product, named with product’s id. All the product related contents will be stored in the corresponding folder. The thumbnail will also be stored in the product’s folder, but with the special name “thumbnail” to be able to distinguish it from the others pictures
So the contents tree will looks like this :

Instead of creating all these contents by hand, you can download this JCR export. To use it :

  • go to the Content Explorer
  • select the Sites Management drive
  • go to acme > documents
  • click on the System tab
  • click on the Import Node button
  • select the export file for the Upload File field
  • click on Import

 To be sure that all contents are in published state, you may need to republish them.

Adding eXo dependencies

The first step is to add the eXo dependencies that will be used in our portlet. Edit your pom.xml and add these lines :

        <dependency>
            <groupId>org.exoplatform.ecms</groupId>
            <artifactId>exo-ecms-core-publication</artifactId>
            <version>2.3.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.exoplatform.ecms</groupId>
            <artifactId>exo-ecms-core-services</artifactId>
            <version>2.3.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.exoplatform.ecms</groupId>
            <artifactId>exo-ecms-core-webui</artifactId>
            <version>2.3.4</version>
            <scope>provided</scope>
        </dependency>

Using eXo Content Management API

In order to manage data displayed in our page, we need a managed bean :

@ViewScoped
@ManagedBean
public class ComicsStoreBean implements Serializable {

    private static final long serialVersionUID = -6239437588285327644L;

    private ContentService contentService;
    private ProductService productService;

    private List<Product> products;

    public ComicsStoreBean() {
        PortletPreferences prefs = ((PortletRequest)  FacesContext.getCurrentInstance().getExternalContext().getRequest()).getPreferences();
        String contentsRootPath = prefs.getValue("contentsRootPath", "");

        contentService = new ContentServiceImpl(contentsRootPath);
        productService = new ProductServiceImpl();
    }

    public List getProducts() {
        if(products == null) {
            products = productService.getProducts();

            for(Product product : products) {
                product.setThumbnailPath(contentService.getProductThumbnailPath(product.getId()));
                product.setPictures(contentService.getProductPictures(product.getId()));
            }
        }

        return products;
    }
}

This bean instantiates 2 services : one for the products (ProductService), one for the contents (ContentService) which is initialized with the root folder in the content management system of the contents used in the store (retrieved from a portlet preference).

The bean exposes only one data, the products, via the getProducts method. This method calls the ProductService object to retrieve all the products. The ProductService class simply returns sample products :

public class ProductServiceImpl implements ProductService {
    @Override
    public List getProducts() {
        List products = new ArrayList();
        products.add(new Product(1, "Ironman", "Ironman", 12));
        products.add(new Product(2, "Wolverine", "Wolverine", 15.5));
        products.add(new Product(3, "Spiderman", "Spiderman", 13));
        products.add(new Product(4, "Thor", "Thor", 10));
        products.add(new Product(5, "Hulk", "Hulk", 11));
        products.add(new Product(6, "Captain America", "Captain America", 15));
        products.add(new Product(7, "Human Torch", "Human Torch", 11));
        products.add(new Product(8, "Magneto", "Magneto", 17));
        products.add(new Product(9, "Dardevil", "Dardevil", 16.5));
        return products;
    }
}

Then the managed bean calls the ContentService object to retrieve the contents related to the products. Here is the most interesting part as it deals with eXo Content Management.
ExoPlatform provides an API to interact with all content management capabilities (contents, taxonomy, links, publication, SEO, …). In our sample application we will use the WCMComposer API which allows to work with contents.

Exo Content Management provides an utility method to easily instantiate a service :

WCMComposer wcmComposer = WCMCoreUtils.getService(WCMComposer.class);

This service is used in the 2 methods of ContentService : getProductThumbnailPath and getProductPictures.
The getProductThumbnailPath method returns the path of the thumbnail of the product if it exists :

@Override
public String getProductThumbnailPath(int productId) {
    String thumbnailPath = null;

    // get wcmcomposer service
    WCMComposer wcmComposer = WCMCoreUtils.getService(WCMComposer.class);

    HashMap filters = new HashMap();
    //filters.put(WCMComposer.FILTER_LANGUAGE, Util.getPortalRequestContext().getLocale().getLanguage());
    filters.put(WCMComposer.FILTER_MODE, Utils.getCurrentMode());
    // take the last published version
    filters.put(WCMComposer.FILTER_VERSION, null);

    try {
        Node productThumbnailNode = wcmComposer.getContent(workspace, path + productId + "/thumbnail", filters,
        WCMCoreUtils.getUserSessionProvider());
        if (productThumbnailNode != null) {
            thumbnailPath = path + productId + "/thumbnail";
        }
    } catch (Exception e) {
        e.printStackTrace();
        thumbnailPath = null;
    }

    return thumbnailPath;
}

The getContent method of the WCMComposer API is used here. It allows to retrieve a content based on its path. Some filters can be added to select the right content base for instance on its language, its version or its publication state.

The getProductPictures method returns all the contents related to a product :

@Override
public List getProductPictures(int productId) {
    List pictures = null;

    // get wcmcomposer service
    WCMComposer wcmComposer = WCMCoreUtils.getService(WCMComposer.class);

    HashMap filters = new HashMap();
    // content of the currently selected language
    //filters.put(WCMComposer.FILTER_LANGUAGE, Util.getPortalRequestContext().getLocale().getLanguage());
    // live or edit mode (will respectively get draft or published content)
    filters.put(WCMComposer.FILTER_MODE, Utils.getCurrentMode());
    filters.put(WCMComposer.FILTER_VERSION, WCMComposer.BASE_VERSION);
    // order clauses
    filters.put(WCMComposer.FILTER_ORDER_BY, "exo:dateModified");
    filters.put(WCMComposer.FILTER_ORDER_TYPE, "ASC");

    try {
        // service call to retrieve the contents
        List picturesNodes = wcmComposer.getContents(workspace, path + productId, filters,
        WCMCoreUtils.getUserSessionProvider());
        pictures = new ArrayList();
        for (Node pictureNode : picturesNodes) {
            // exclude thumbnail
            if (!pictureNode.getProperty("exo:name").getString().equals("thumbnail")) {

                // In live mode, the last published version is a frozenNode, so
                // we need to get the node referenced by this frozen to get the real path
                String picturePath = null;
                if (pictureNode.isNodeType("nt:frozenNode")) {
                    String uuid = pictureNode.getProperty("jcr:frozenUuid").getString();
                    Node originalNode = pictureNode.getSession().getNodeByUUID(uuid);
                    picturePath = originalNode.getPath();
                } else {
                    picturePath = pictureNode.getPath();
                }

                Picture picture = new Picture(pictureNode.getProperty("exo:name").getString(), picturePath, pictureNode.getProperty("exo:title").getString());
                pictures.add(picture);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        pictures = null;
    }

    return pictures;
}

We now use the getContents method, the equivalent of getContent but for multiple contents.
One really interesting point is that by using this API you can profit from all the eXo Content Management features. For example, by calling the getContents method with the filter WCMComposer.FILTER_VERSION set to WCMComposer.BASE_VERSION and the filter WCMComposer.FILTER_MODE set to the current mode (live or edit), only the link of the published pictures will be visible in live mode whereas link of pictures in draft state will be visible in edit mode.

As you can see, the node’s type is checked to distinguish the frozenNode case :

if (pictureNode.isNodeType("nt:frozenNode"))

In fact, a frozenNode is a published version of an original node. A frozenNode references the original node (in our case, the picture node) through its jcr:frozenUuid property. So we need to get this property to retrieve the original node and get its path.

The last part is to edit our JSF pages to display all the products. For so, a RichFaces data grid (rich:dataGrid) is used :

                <rich:dataGrid value="#{comicsStoreBean.products}" var="product" columns="3" elements="6" width="600px" border="0">
                    <rich:panel bodyClass="pbody">
                        <f:facet name="header">
                            <h:outputText value="#{product.name}"></h:outputText>
                        </f:facet>
                        <h:panelGrid id="product" columns="2" columnClasses="productThumbnailColumn, productDetailColumn">
                                <h:panelGroup>
                                        <h:panelGroup rendered="#{not empty product.thumbnailPath}">
                                                <img src="/rest/jcr/repository/collaboration#{product.thumbnailPath}" width="80px"></img>
                                        </h:panelGroup>
                                </h:panelGroup>
                                <h:panelGrid columns="2">
                                     <h:outputText value="Description:" styleClass="label"></h:outputText>
                                     <h:outputText value="#{product.description}"/>
                                     <h:outputText value="Price:" styleClass="label"></h:outputText>
                                     <h:outputText value="#{product.price} €"/>
                                     <h:outputText value="Photos:" styleClass="label"></h:outputText>
                                     <ui:repeat value="#{product.pictures}" var="picture" varStatus="status">
                                        <h:outputLink onclick="window.location = window.location.pathname + '?content-id=/repository/collaboration#{picture.path}'; return false;" value="#">
                                                <h:outputText value="#{picture.title}"></h:outputText>
                                        </h:outputLink>
                                        #{status.last ? '' : ' | '}
                                     </ui:repeat>
                                </h:panelGrid>
                        </h:panelGrid>
                        <div class="buyButtonDiv">
                                <h:commandButton value="Buy" styleClass="buyButton"></h:commandButton>
                        </div>
                    </rich:panel>
                    <f:facet name="footer">
                        <rich:dataScroller/>
                    </f:facet>
                </rich:dataGrid>

The ui:repeat lists all links towards the product’s pictures. The link simply goes to the same page (because a Content Detail portlet will be added in this page) and pass the content-id parameter with the path of the content to display. The Content Detail will just look at this parameter and display the targeted content.

Adding Content Detail in the page

In order to display the products’ pictures we need to add a Content Detail portlet in the page :

  • edit the page (Edit > Page > Layout in the top bar)
  • in the portlet catalog, open the Contents category
  • drag and drop a Content Detail portlet on the page, under the first portlet
  • edit the portlet
  • select a default content for the Content Path field (products/Comics)
  • scroll down the preferences screen and click on the Advanced link
  • choose Enabled for the Contextual Content field
  • leave the other field as default (by content-id)
  • click on Save
  • click on Close
  • click on the Save icon (upper right) to save the modifications done to the page

Our store is open !

Let’s play with it.
Click on a picture link, for instance the Photo 1 of Wolverine :

The picture is now displayed below.
Let’s now add a new picture. To do so :

  • go to the Content Explorer
  • go to the Ironman folder (acme > documents > products > 1)
  • click on the Upload FIles button
  • select your file
  • click on Save

The picture is now uploaded. Leave it in draft state and go back to the Comics store page. As you can see, your new picture does not appears in the Ironman’s pictures.
Now switch to edit mode by clicking on Edit > Content.

The picture link now appears and you can click it to display the new picture. Up to you now to publish it to make it available in live mode.

This tutorial showed how to use the eXo Content Management API to profit from all the eXo Content Management features inside your own portlets.
Don’t hesitate to take a look at the documentation to discover all the available APIs. Enjoy !

About these ads

, , , ,

  1. #1 by Gustavo on 05/10/2012 - 23:19

    I work with eXo on large projects and your blog is helping a lot! Please continue doing so this beautiful work!

  2. #3 by Mahek Sabharwal on 16/10/2012 - 19:15

    I never imagined how much information there was on this!
    Thanks for making this all easy to understand

  3. #4 by Renata on 20/11/2012 - 12:25

    Hi,
    I didn´t understand when you said “Instead of creating all these contents by hand, you can download this JCR export. To use it :” on “Adding contents” subject. I click on the link and a page shows up with a lot a of code. What I am supposed to do?

    • #5 by Thomas on 20/11/2012 - 12:34

      This code contains all the contents used in this tutorial (in the JCR export format).
      You have to right click on the link, then “Save link as…”. Save the file on your local disk. Then you can continue.

  4. #6 by Adércio Reinan on 21/06/2013 - 21:59

    Thanks for sharing, excellent quality!

  1. Portlet Development in eXo Platform with JSF and RichFaces (Part 2/3) – Open Source Enterprise Social Platform | eXo Blog and News

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: