Your first Juzu portlet

Juzu ?

Juzu is a Buddhist prayer bead. One sentence and I am sure you have already learnt something, impressive no? ;)

Ok, I won’t speak about Buddhism here.
Juzu is also a new framework for developing portlets (and standalone applications soon) very quickly. You can find all the information you need on the Juzu website.

Now let’s create our first portlet with Juzu !

Creating a new project

Juzu comes with a maven archetype. We can use it to quickly create our first application :

mvn archetype:generate \
   -DarchetypeGroupId=org.juzu \
   -DarchetypeArtifactId=juzu-archetype \
   -DarchetypeVersion=0.5.1 \
   -DgroupId=org.example \
   -DartifactId=myapp \
   -Dversion=1.0.0-SNAPSHOT

This creates a juzu project in a myapp folder.

Deploying the Juzu portlet

Before deploying the application, you need to build it.
Simply run mvn clean package in the myapp folder. It will generate a myapp.war under your myapp/target folder.

We are now ready to deploy the portlet in a portal container. We will use the latest GateIn release (3.4), the tomcat bundle version. Once downloaded, install it by unzipping it in the location of your choice.

The only thing you need to do is to drop the myapp.war file in the webapps folder, and start GateIn with bin/gatein.sh run.

Once started, add your portlet in a page. You should see :

Great ! You just finished your first Juzu portlet !

Let’s explore the project before enhancing it.

Exploring the project

The project structure looks like this :

The mandatory web.xml is there. It does not contain anything.


portlet.xml

The archetype generates a basic portlet.xml with some juzu init parameters :

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
             version="2.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd

http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd">

   <portlet>
     <portlet-name>SampleApplication</portlet-name>
     <display-name xml:lang="EN">Juzu Sample Application</display-name>
     <portlet-class>juzu.portlet.JuzuPortlet</portlet-class>
     <init-param>
       <name>juzu.run_mode</name>
       <value>prod</value>
     </init-param>
     <init-param>
       <name>juzu.inject</name>
       <value>weld</value>
       <!--
       <value>spring</value>
       -->
     </init-param>
     <supports>
       <mime-type>text/html</mime-type>
     </supports>
     <portlet-info>
       <title>Sample Application</title>
     </portlet-info>
   </portlet>
</portlet-app>

The portlet-class is the generic Juzu portlet class juzu.portlet.JuzuPortlet.
This class declares 2 init parameters :

  • juzu.run_mode
    • dev : changes made on source files are automatically hot recompiled and reloaded, so you don’t need to redeploy your application to test them. This is a real productivity boost while developing an application !
    • prod : “classic” mode, where you need to recompile and redeploy your application to apply your changes.
  • juzu.inject – defines the inject implementation. Two implementations are currently supported : weld (CDI Reference Implementation) and spring.

The Juzu portlet class uses the package-info.java file to gather needed extra information.

The portlet.xml file also contains basic information about the portlet : portlet-name, display-name and portlet-info. You can change them or add some others if needed.


package-info.java

This file contains all the configuration of the application.
The file allows to activate plugins, add JS/CSS resources, … but let’s keep it simple for now.
The only mandatory configuration is the declaration of the application, thanks to the @juzu.Application annotation. You have to declare the base package of your application, in our case org.sample.


Controller.java

This class is a Juzu controller. It is composed of a view method index (annotated with @View) which allows to render the index template.
The path of the index template is set with the @Path annotation. By default, Juzu uses the templates package of the application as its root path. So in our case, the template is located at org/sample/templates/index.gtmpl.

Switching to dev mode

Now that we know a little bit more about what is a Juzu application, let’s improve a little bit our basic helloworld application.
First of all, we will switch from prod to dev mode, in order to quickly test our changes. For that, edit your portlet.xml file and change the value of the init-param juzu.run_mode to dev. Then build your application and drop the war in the webapps folder of GateIn. Here you don’t need to stop/start GateIn as the webapp will be automatically redeployed.

As we did not change anything in the source files of our application, you should see the same “Hello World” message in your portlet.

In order to test the dev mode, you can for instance rename the file webapps/myapp/WEB-INF/src/org/sample/templates/index.gtmpl to index2.gtmpl. After refreshing your page, you will get the following message :

Now edit webapps/myapp/WEB-INF/src/org/sample/Controller.java and change

@Inject
@Path("index.gtmpl")
Template index;

by

@Inject
@Path("index2.gtmpl")
Template index;

and refresh your page once again.
Everything back to normal ! Pretty cool, isn’t it ? ;)

Forms, Actions and type safe template parameters

We will create an application which displays the map of the location choosen by the user.
Firstly, update your index.gtmpl template :

#{param name=location/}
#{param name=mapURL/}

Location :
<form action="@{updateLocation()}" method="post">
	<input type="text" name="location" value="${location}"/>
	<input type="submit"/>
</form>
<br/>
<%if(location) {%>
<div id="map">
	
</div>
<%}%>
  • #{param name=location/} and #{param name=mapURL/} declares 2 type safe template parameters which will be used latter in our Controller
  • the form contains a input text, and submit to our juzu controller action updateLocation
  • finally, if a location is specified, the maps is displayed

Now, let’s update update our Controller.java :

package org.sample;

import juzu.Action;
import juzu.Path;
import juzu.Resource;
import juzu.Response;
import juzu.View;
import juzu.template.Template;

import javax.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Controller {

  @Inject
  @Path("index.gtmpl")
  org.sample.templates.index index;

  @View
  public void index() throws IOException {
    index("", "");
  }

  @View
  public void index(String location, String mapURL) throws IOException {
	index.with().location(location).mapURL(mapURL).render();
  }

  @Action
  public Response updateLocation(String location) throws IOException {
    String mapURL = "https://maps.google.fr/maps?f=q&source=s_q&hl=en&geocode=&q=" + location + "&aq=&t=m&ie=UTF8&hq=&hnear=" + location + "&z=12&output=embed";

	return Controller_.index(location, mapURL);
  }
}
  • the index template is now of type org.sample.templates.index. This class is generated thanks to the annotations, and is a subclass of Template. Using this specific type will allow us to leverage declared template parameters, location and mapURL in our case.
  • the default index View now calls a new index View which accepts the location and mapURL arguments. This new view uses the index template class and its fluent syntax (do you like it ? Personnaly I do ;)). Thanks to the declaration of the location and mapURL parameters in the template, the org.sample.templates.index template class accepts a location method and a mapURL method to set their values.
  • the updateLocation method is defined as an action thanks to tthe @Action annotation. It is called by the form to retrieve the correct URL (building the map URL is a basic example, generally you will call your services here). Then it redirects to the index View method in order to render the index template. Note the _ at the end of the Controller name. The class Controller_ is the “annotations processed” version of the Controller class.

If you did all these changes in the deployed version of your application (in webapps/myapp), you just need to refresh, and you should be able to enter a location and then see the corresponding map :

Ajax

Juzu provides some ease to use Ajax in your application. We will use them to avoid reloading our page when submitting a new location in our form.
The Ajax plugin needs JQuery. We can add it to our application by simply dropping the JQuery js file in the project and declare it in the package-info.java file with the Asset plugin (I dropped the JQuery js file in public/scripts) :

@juzu.plugin.asset.Assets(
	scripts = {
		@juzu.plugin.asset.Script(
			id = "jquery",  
			src = "public/scripts/jquery-1.7.1.min.js")
	}
)

We will now update our controller in order to add a new method which will only provide the map URL :

  @Ajax
  @Resource
  public Response.Content<Stream.Char> getMapURL(String location) throws IOException {
	String mapURL = "https://maps.google.fr/maps?f=q&source=s_q&hl=en&geocode=&q=" + location + "&aq=&t=m&ie=UTF8&hq=&hnear=" + location + "&z=12&output=embed";

	return Response.ok("{\"mapURL\": \"" + mapURL +"\"}").withMimeType("application/json");
  }

Note that this new method is not annotated with @Action anymore. Annotating a method with @Ajax will make it accessible for Ajax calls. The @Resource annotation makes this method send the entire response to the client. That’s what we want as this method simply creates the new URL and sends it back to the client as a JSON response.

Finally, we have to update our template file to add the Ajax call :

#{param name=location/}
#{param name=mapURL/}

<script>
function submitLocation(location) {
	$('#map').jzAjax({
		url: "Controller.getMapURL()",
		data: {"location": location}
	}).done(function(data) {
		$('#map > iframe').attr('src', data.mapURL);
	});
	return false;
}
</script>

Location :
<form onsubmit="return submitLocation(this.location.value)">
	<input type="text" name="location" value="${location}"/>
	<input type="submit"/>
</form>
<br/>

<div id="map">
	
</div>

The submission of the form now calls the submitLocation javascript function. This function uses the juzu Ajax function jzAjax (which uses the ajax JQuery function under the hood). This function calls the URL provided in the url param with the parameters provided in data. So here it will call the newly created method of our Controller and receive the new map URL in JSON :

{"mapURL": "https://maps.google.fr/maps?f=q&source=s_q&hl=en&geocode=&q=nantes&aq=&t=m&ie=UTF8&hq=&hnear=nantes&z=12&output=embed"}

Then we just use JQuery to update the map.

Once again, simply refresh your page to see it in action !

You can now learn more on Juzu by going to the website or watching the screencasts.

, , ,

2 Comments

Starting eXoPlatform from Eclipse

Prerequisites

  • Download and unzip the latest version of eXo Platform 3.5 (Tomcat version). The eXoPlatform location will be referenced as <eXoPlatform_dir> in this tutorial.
  • Download and install an Eclipse JEE bundle (I used Juno).

Creating the server

The first step is to create a server runtime environment, pointing to our eXoPlatform Tomcat :

  • Go to Window > Preferences > Server > Runtime Environnements
  • Click Add
  • Select Apache Tomcat v6.0
  • Click Next
  • In the field Tomcat installation directory, select the folder <eXoPlatform_dir>/tomcat-bundle
  • Click on Finish
  • Click on OK

Then we create a new instance of this server :

  • In the Servers panel, click-right then click on New > Server
  • Select Apache > Tomcat v6.0 Server
  • Enter the host name (localhost is generally good)
  • Enter the server name (eXoPlatform for example)
  • Select the Apache Tomcat v6.0 server
  • Click on Next
  • Click on Finish

You should now see your new eXoPlatform server in the Servers view :

Configuring the server

The next step is to configure the server instance :

  • Double-click on the eXoPlatform server in this view to edit its properties
  • In the Server Locations panel,
    • select Use Tomcat installation (takes control of Tomcat installation)
    • in the Deploy path, select the <eXoPlatform_dir>/tomcat-bundle/webapps folder
  • In the Timeouts pabel, increase the start timeout (300 par example)

Finally, let’s define the environment variables used by eXoPlatform :

  • Now click on Open launch configuration (still in the server properties, in the General information panel)
  • Select the Arguments tab
  • In the VM arguments, add the eXoPlatform environment variables from the <eXoPlatform_dir>/tomcat-bundle/bin/setenv.(sh|bat) file :
    -Dexo.conf.dir.name=gatein/conf
    -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
    -Djava.security.auth.login.config=../conf/jaas.conf
    -Dexo.profiles=default
    
  • In the Working directory panel, select Other and select the <eXoPlatform_dir>/tomcat-bundle/bin folder
  • Click on OK

Your eXoPlatform is now ready to be launched in Eclipse. In the Server view, you simply have to right-click on it, and click on Start.

Switching to eXoPlatform developing mode

In order to switch to the eXoPlatform debug mode, you just need to add the following environment variables (in the launch configuration properties) :

-Dorg.exoplatform.container.configuration.debug
-Dexo.product.developing=true

You can even create 2 instances of this server to easily switch between these 2 modes.

Enjoy !

,

3 Comments

Creating node type’s view template in eXoPlatform ECMS

eXo ECMS supports the inline visualization of a lot of file formats. For example let’s see it in action with a PDF file :

For those not yet available, a generic message is displayed, allowing to download the file. Here the view of a ZIP file (in the File Explorer) :

Fortunately, eXo ECMS allows to easily add your own file preview (and even modify the existing ones). Let’s do it with our ZIP file.
The goal is to display the list of file’s names contained in the ZIP file.

All the files that we will create have to be packaged in a JAR and added to the classpath.

Creation of the view template

We will start by creating the view template. It is written in Groovy. You can access to all the node’s properties, and even navigate in the node hierarchy :

<style>
ul.zip-file-list {
	padding: 0 20px;
}
ul.zip-file-list li {
	list-style-position: inside;
    list-style-type: circle;
}
</style>
<%
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.exoplatform.webui.core.UIComponent

def uiParent = uicomponent.getParent()
def originalNode = uiParent.getOriginalNode()
def contentNode = originalNode.getNode("jcr:content")
def zis;

try {
	zis = new ZipInputStream(contentNode.getProperty("jcr:data").getStream())

	ZipEntry ze

	out.println("<ul class=\"zip-file-list\">")
	while ((ze = zis.getNextEntry()) != null) {
	  out.println("<li>" + ze.getName() + "</li>")
	}
	out.println("</ul>")
} finally {
	zis?.close()
}
%>

Here we only iterate on all the ZIP’s files in order to display thier names.
Save it as ZipViewer.gtmpl and add it in the JAR in a folder templates.

Registration of the preview template

Once the view template ready, it has to be registered and linked to the ZIP file type.

The first step for registering the template is to create a simple class which extends UIComponent and define the path of the view template :

package org.exoplatform.ecm.dms;

import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.core.UIComponent;

@ComponentConfig(
template = "classpath:templates/ZipViewer.gtmpl"
)
public class ZipViewer extends UIComponent {
}

Note that this class defines the template’s path. In our case templates/ZipViewer.gtmpl.

Then this class needs to be declared in the configuration. This will be done with the org.exoplatform.webui.ext.UIExtensionManager component. Just create a file called configuration.xml in conf/portal in your jar, with the following content :

<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"
	xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd">
  <external-component-plugins>
    <target-component>org.exoplatform.webui.ext.UIExtensionManager</target-component>
    <component-plugin>
      <name>Zip File dynamic viewer</name>
      <set-method>registerUIExtensionPlugin</set-method>
      <type>org.exoplatform.webui.ext.UIExtensionPlugin</type>
      <init-params>
        <object-param>
          <name>Zip</name>
          <object type="org.exoplatform.webui.ext.UIExtension">
            <field name="type">
              <string>org.exoplatform.ecm.dms.FileViewer</string>
            </field>
            <field name="rank">
              <int>110</int>
            </field>
            <field name="name">
              <string>Zip</string>
            </field>
            <field name="category">
              <string>FileViewer</string>
            </field>
            <field name="component">
              <string>org.exoplatform.ecm.dms.ZipViewer</string>
            </field>
            <field name="extendedFilters">
              <collection type="java.util.ArrayList">
                <value>
                  <object type="org.exoplatform.webui.ext.filter.impl.FileFilter">
                    <field name="mimeTypes">
                      <collection type="java.util.ArrayList">
                        <value>
                          <string>application/zip</string>
                        </value>
                      </collection>
                    </field>
                  </object>
                </value>
              </collection>
            </field>
          </object>
        </object-param>
      </init-params>
    </component-plugin>
  </external-component-plugins>
</configuration>

This configuration links the org.exoplatform.ecm.dms.ZipViewer component to the application/zip mimetype.

Your JAR should now contain 3 files :

  • templates/ZipViewer.gtmpl
  • org/exoplatform/ecm/dms/ZipViewer.class
  • conf/portal/configuration.xml

The last step is to add this JAR in the classpath and restart the plaform. You should now see the content of the ZIP when displaying it :

,

2 Comments

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

One of the greatest strenghs of eXoPlatform is to integrate a lot of features together, such as content management and collaboration or social capabilities.

After the development of a JSF 2 / RichFaces portlet, and the integration of the Content Management capabilities of eXoPlatform, we will learn, in this third tutorial, how to leverage eXo Social features in your own business portlets. For so, we will start from the portlet created in the previous tutorial and post an activity in the user’s activity stream when he buys a product.

The source code of this tutorial is available here.

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.social</groupId>
  <artifactId>exo.social.component.core</artifactId>
  <version>1.2.6</version>
  <scope>provided</scope>
</dependency>

Using eXo Social API

Now let’s create a service which will use the eXo Social API to post an activity in the user’s activity stream. For this, we will create a new class with the following method :

@Override
public void postActivity(String activityText) {

    // Gets the current container.
    PortalContainer container = PortalContainer.getInstance();

    // Gets the current user id
    ConversationState conversationState = ConversationState.getCurrent();
    org.exoplatform.services.security.Identity identity = conversationState.getIdentity();
    String userId = identity.getUserId();

    // Gets identityManager to handle an identity operation.
    IdentityManager identityManager = (IdentityManager) container.getComponentInstanceOfType(IdentityManager.class);

    // Gets an existing social identity or creates a new one.
    Identity userIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, userId, false);

    // Gets activityManager to handle an activity operation.
    ActivityManager activityManager = (ActivityManager) container.getComponentInstanceOfType(ActivityManager.class);

    // Saves an activity by using ActivityManager.
    activityManager.saveActivity(userIdentity, null, activityText);
}

This service retrieves the current user’s identity and then post an activity for this identity with the given text.

We now use this service in our JSF view. The ‘Buy’ button of each product calls an action with the product’s data as parameter :

<h:commandButton value="Buy" styleClass="buyButton" action="#{comicsStoreBean.buy}">
  <f:param name="productId" value="#{product.id}" />
  <f:param name="productName" value="#{product.name}" />
</h:commandButton>

And this action, added in the ComicsStoreBean managed bean, uses our previously created service to post the activity :

public void buy() {
    FacesContext context = FacesContext.getCurrentInstance();
    ActionRequest request = (ActionRequest) context.getExternalContext().getRequest();
    String productName = request.getParameter("productName");
    socialService.postActivity("I have just bought <b>" + productName + "</b> !");
}

You can now see the result in your activity stream after clicking on the Buy button of one of the products by going to the ‘intranet’ demo site (My Sites > intranet) :

eXo Social offers you a lot of others possibilities such as posting an activity in a space, posting different kinds of activity (for instance, content activity such as the one in the screenshot for the ‘iron-man-2.jpg’ file), adding a comment to an activity, getting the relations of an user, getting the users of a space, …
Besides this Java API, eXo Social offers a REST API and the OpenSocial API.
In this tutorial, we learnt how to use the eXo Social API to post an activity from your own business portlet.
Don’t hesitate to take a look at the documentation to discover all the available APIs.

1 Comment

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 !

, , , ,

7 Comments

JSF 2 / RichFaces portlet in eXo (Part 1/3)

eXoPlatform comes with a set of advanced portlets to quickly build rich applications. But you may want to develop your own portlets to fit your needs.
The development of portlet using the raw portlet API can be a bit painful. It is often interesting to use a higher level framework such as JSF. JSF is a request-driven MVC web framework based on component-driven UI design model, and is part of the JEE standard.

This article is the first part of a 3-steps tutorials which will learn you how to develop a portlet with JSF 2.0 and RichFaces 4, and leverage eXo Content and eXo Social capabilities.
RichFaces is a set a advanced JSF components which will allow us to add even more richness to our application.

The source code of this first part is available here.

Prerequisites

  • Download the latest version of eXo Platform 3.5.
  • Install and configure Apache Maven.

To be able to run a JSF application in a portal context, we need a JSF Portlet Bridge. This technology is also part of the JEE standard, and thus several implementations are available. For this tutorial the JBoss implementation will be used, but you don’t have to install anything as it will be added as a library in your portlet via maven.

Creating the JSF Portlet Bridge project

The JBoss JSF Portlet Bridge project provides a maven archetype to easily create a fresh JSF Portlet Bridge project with JSF 2.0 and the last version of the Portlet bridge (3.0.0.Beta1 when this tutorial has been written). Type the following command :

mvn archetype:generate -DarchetypeGroupId=org.jboss.portletbridge.archetypes -DarchetypeArtifactId=2.0-basic -DarchetypeVersion=3.0.0.Beta1 -DgroupId=org.exoplatform -DartifactId=exo-jsfportletbridge-portlet

Maven will prompt you to change the default value of the project’s properties. You can leave the default values by simply pressing Enter.

Your project is now created. However some little changes need to be done to make it fully operational.

portlet.xml

  • change the portlet-app tag with the following :
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
             version="2.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd

http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd">

  • move the portlet-info tag under the supports tag

faces-config.xml

  • remove everything under the faces-config tag :
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
</faces-config>

pom.xml

Optionnally, the pom.xml file can be updated to use the last available version of the JSF libraries by changing :

<properties>
  <jsf.version>2.0.3-b03</jsf.version>
</properties>

with the new value. For example :

<properties>
  <jsf.version>2.1.2</jsf.version>
</properties>

Your project is now ready and can be built to be deployed in eXoPlatform. You can now build your project by launching the following command at the root of your project : mvn clean install

A war file should be available in the target folder of your project (JSF2RIPortlet.war by default).

Deploying the portlet in eXoPlatform

The next step is to deploy the portlet in eXoPlatform. For so,

  • start your eXoPlatform 3.5
  • once started, copy the war file of the portlet in the webapps directory. If the portlet deploys correctly, you should see the following lines in the console :

2 janv. 2012 14:00:37 org.apache.catalina.startup.HostConfig deployWAR
INFO: Déploiement de l’archive JSF2RIPortlet.war de l’application web
2 janv. 2012 14:00:38 com.sun.faces.config.ConfigureListener contextInitialized
INFO: Initialisation de Mojarra 2.1.2 (FCS 20110610) pour le contexte «/JSF2RIPortlet»
2 janv. 2012 14:00:39 javax.portlet.faces.GenericFacesPortlet init
INFO: Init GenericFacesPortlet for portlet riPortlet
2 janv. 2012 14:00:39 javax.portlet.faces.GenericFacesPortlet calculateBridgeClassName
INFO: Bridge class name is org.jboss.portletbridge.AjaxPortletBridge

The portlet has been successfully deployed in eXoPlatform. It should now be added in a page of the portal :

  • click on Portlets. The list of all the deployed portlets is available on the left side. We will now make the portlet addable in the portal’s pages.
  • Select our portlet (RiPortlet in the JSF2RIPortlet category).
  • in the right part, click on “Click here to add this portlet to a category.”
  • select a category, Adoption for example

  • click on Save
  • come back to the acme site’s home page (My Sites > acme in the top bar)

The next step is to create a page to place our portlet :

  • in the top bar, click on Add Page in Edit > Page

  • in the left side, click on the up arrow to add the page at the root
  • in the right side, enter “comics-store” as the name and “Comics Store” as the display name (guess what this tutorial will deal with ? ;-)). Leave others fields as is. Note than you can easily define title in others languages.
  • click on Next
  • we will use the default empty dashboard, so click on Next
  • in the portlet catalog, find the RiPortlet and drag&drop it in the page

  • click on the Save icon (upper right)

The portlet is now available on our new page :

Adding RichFaces

RichFaces is a set of advanced JSF components. To use it in your project, edit your pom.xml file to add the following dependencies :

<dependency>
  <groupId>org.richfaces.ui</groupId>
  <artifactId>richfaces-components-ui</artifactId>
  <version>4.1.0.Final</version>
</dependency>
<dependency>
  <groupId>org.richfaces.core</groupId>
  <artifactId>richfaces-core-impl</artifactId>
  <version>4.1.0.Final</version>
</dependency>

then, add the following lines in your web.xml :

<context-param>
  <param-name>org.richfaces.resourceMapping.enabled</param-name>
  <param-value>false</param-value>
</context-param>

<context-param>
  <param-name>org.richfaces.skin</param-name>
  <param-value>ruby</param-value>
</context-param>

finally, in your JSF views, add the following namespace :

xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich"

You can now use all the RichFaces components in your pages.
As an example, we will add a tabPanel component to the page. The code is simply taken form the RichFaces demo website. Just edit the src/main/webapp/home.xhtml file and add these lines before </h:body> :

<rich:tabPanel switchType="client">
  <rich:tab header="Overview">
    RichFaces is a component library for JSF and an advanced framework for
    easily integrating AJAX capabilities into business applications.
    <ul>
      <li>100+ AJAX enabled components in two libraries</li>
      <li>a4j: page centric AJAX controls</li>
      <li>rich: self contained, ready to use components</li>
      <li>Whole set of JSF benefits while working with AJAX</li>
      <li>Skinnability mechanism</li>
      <li>Component Development Kit (CDK)</li>
      <li>Dynamic resources handling</li>
      <li>Testing facilities for components, actions, listeners, and pages</li>
      <li>Broad cross-browser support</li>
      <li>Large and active community</li>
    </ul>
  </rich:tab>
  <rich:tab header="JSF 2 and RichFaces 4">
    <p>We are working hard on RichFaces 4.0 which will have full JSF 2 integration. That is not all though, here is
    a summary of updates and features:</p>
    <ul>
      <li>Redesigned modular repository and build system.</li>
      <li>Simplified Component Development Kit with annotations, faces-config extensions, advanced templates
support and more..</li>
      <li>Ajax framework improvements extending the JSF 2 specification.</li>
      <li>Component review for consistency, usability, and redesign following semantic HTML principles where
possible.</li>
      <li>Both server-side and client-side performance optimization.</li>
      <li>Strict code clean-up and review.</li>
    </ul>
  </rich:tab>
</rich:tabPanel>

Build your war and copy/paste it in the webapps directory of eXoPlatform. No need to restart, the portlet is hot redeployed !
You should see the following message in the console :

2 janv. 2012 14:24:01 org.apache.catalina.startup.HostConfig deployWAR
INFO: Déploiement de l’archive JSF2RIPortlet.war de l’application web
2 janv. 2012 14:24:02 com.sun.faces.config.ConfigureListener contextInitialized
INFO: Initialisation de Mojarra 2.1.2 (FCS 20110610) pour le contexte «/JSF2RIPortlet»
2 janv. 2012 14:24:04 org.richfaces.cache.CacheManager getCacheFactory
INFO: Selected fallback cache factory
2 janv. 2012 14:24:04 org.richfaces.cache.lru.LRUMapCacheFactory createCache
INFO: Creating LRUMap cache instance using parameters: {facelets.DEVELOPMENT=false, org.richfaces.resourceMapping.enabled=false, javax.faces.FACELETS_VIEW_MAPPINGS=*.xhtml, org.richfaces.skin=ruby, javax.portlet.faces.RENDER_POLICY=ALWAYS_DELEGATE, javax.faces.STATE_SAVING_METHOD=server, javax.faces.DEFAULT_SUFFIX=.xhtml}
2 janv. 2012 14:24:04 org.richfaces.cache.lru.LRUMapCacheFactory createCache
INFO: Creating LRUMap cache instance of 512 items capacity
2 janv. 2012 14:24:04 org.richfaces.application.InitializationListener onStart
INFO: RichFaces Core Implementation by JBoss, a division of Red Hat, Inc., version v.4.1.0.Final
2 janv. 2012 14:24:04 javax.portlet.faces.GenericFacesPortlet init
INFO: Init GenericFacesPortlet for portlet riPortlet
2 janv. 2012 14:24:04 javax.portlet.faces.GenericFacesPortlet calculateBridgeClassName
INFO: Bridge class name is org.jboss.portletbridge.AjaxPortletBridge

Just reload your portal page, the tabPanel component appears :

Your JSF/RichFaces Portlet is now fully working !

You can notice that the style of the elements on the page has changed. This is due to the skin capabilities of RichFaces. You can easily change the style by changing the value of the context param org.richfaces.skin in the web.xml (you have set ‘ruby’ for this example). RichFaces comes with a set of ready-to-use skins, and you can of course create your owns.

This tutorial showed how to simply create your own portlets using JSF, a standard java web framework.
The next tutorials will show how to leverage eXoPlatform features in your business portlets.

, , ,

2 Comments

REST Services in GateIn / eXoPlatform : add your own !

Mulitple REST services are available out of the box in GateIn / eXoPlatform. They allow to easily interact with the platform’s features and data.

Here we will see how to quickly add your own REST services (valid for eXoPlatform 3.0 or 3.5).

Java REST class

Firstly, we create the Java class which will expose the REST service :

package org.exoplatform.samples.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

import org.exoplatform.services.rest.resource.ResourceContainer;

@Path("/user/")
public class MyRESTService implements ResourceContainer {

    @Path("/name/{name}/")
    @GET
    public Response getName(@PathParam("name") String name) throws Exception {
        return Response.ok("Hello " + name + " !").build();
    }

}

As you can see, this class uses standard JAX-RS annotations to expose its services (javax.ws.rs.*). The only thing to add is the org.exoplatform.services.rest.resource.ResourceContainer interface. This interface is a marker to define this class as a REST services class.

If you use Maven, the org.exoplatform.services.rest.resource.ResourceContainer class is available with the following dependency :

<dependency>
  <groupId>org.exoplatform.ws</groupId>
  <artifactId>exo.ws.rest.core</artifactId>
  <version>2.2.6-GA</version>
  <scope>provided</scope>
</dependency>

Adjust the dependency version according to your Gatein / eXoPlatform version.

Declare your REST service

The second and last thing to do is to declare the REST service. To achieve this, declare your class in the GateIn / eXoPlatform configuration :

<component>
  <type>org.exoplatform.samples.rest.MyRESTService</type>
</component>

This configuration can be put either in the jar containing the java class (in conf/portal/configuration.xml), or in an extension, or even in the external configuration.

Now you just have to deploy your class and your configuration (for example, put the jar containing the class and the configuration in the tomcat’s libs), and restart the server.
Your service is now available at http://<host&gt;:<port>/portal/rest/user/name/<yourname> :

Up to you now to implement your custom REST service !

2 Comments

Follow

Get every new post delivered to your Inbox.