Saturday, January 21, 2012

Google AppEngine Full Text Search with Cloud SQL

Introduction

Many Google AppEngine developers have been waiting for the Full Text Search feature, especially coming from Google the biggest search engine on the Web. I was quite happy to see that Google team is working on it as you can check in the Google I/O 2011 session : Full Text Search by Bo Majewski, Ged Ellis . As far as I know the very promising indexing service is not yet available.

In this article I will explain how you can provide some kind of full text search in your application using services available App Engine services.

In my specific use case I do not ask for a lot of feature, I just need to have simple search a string in various attributes of my entities independently of the case, and possible special characters (such as è,é, ... ). I am far of being an expert of Google Datastore API but I did not find any simple way to achieve this directly using the Java API. What I have done to solve this issue is to duplicate a part of my data into the Google Cloud SQL to use the MySQL fulltext search capabilities.

Prerequisites
To achieve the following tasks you need to :


Content

In the following paragraphs I will explain the basics of the integration of Cloud SQL for full text search, but you can, if you want, jump to :




1. Create Articles Entities

Start by creating some simple entities with some attributes for example, an entity name Article, with title and body attributes.


import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
ahhtraceroute: unknown host identity.exoplatform.org

//...
//...

  Entity article = new Entity("Article");
  article.setProperty("title", "MySQL Tutorial");
  article.setProperty("body", "DBMS stands for DataBase ...");
  datastore.put(article);

  article = new Entity("Article");
  article.setProperty("title", "Datastore Index Selection and Advanced Search");
  article.setProperty("body", "Learn how recent improvements to the query planner ... function in your application");
  datastore.put(article); 

If you look in the Datastore API, or even JDO or JPA you have no simple way to look for all the articles that are related to Triathlon, or Database, or Entities. Google DataStore does not support  clause where with a "OR" between different fields; and I do not want to mention the fact that it is not possible to ignore the text case in a simple way.

This is why we need to have some full text features. Some of you are surely thinking about using Apache Lucene to do the trick, and yes it is possible. You can use for example the GAELucene project : http://code.google.com/p/gaelucene/. I use another approach, may be less advanced in term of "indexing/searching" options but sufficient for my use case:
  • I store the text values on which I want to do some search in Google Cloud SQL and use the Full Text features of MySQL.


2. Create a SQL Table to store Text values (in development environment)

When using Google AppEngine, the Cloud SQL instances are accessed using a specific driver and configuration that we will see later. For now, we are still in development environment, this is where you have to use your local MySQL instance.

In this specific use case we will copy in a table the two fields and add a new unique key based on the entity key.  So here the SQL to create this:

CREATE SCHEMA search_values DEFAULT CHARACTER SET utf8 ;

USE search_values;


CREATE TABLE articles  (
  entity_key varchar(250),
  title text,
  body text,
  PRIMARY KEY RESULTS_PK (entity_key),
  FULLTEXT (title,body)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


Lines 1 and 3 are here to create the database schema and use it; then the script create a table that will contain a copy of the title and body from the entity. 

3. Configure your development environment 

This section is a short explanation of the Cloud SQL Documentation : Getting Started: Java

  1. Copy the MySQL JDBC driver into your Google App Engine SDK directory, under /lib/impl/. You can download the MySQL JDBC driver here.
  2. In Eclipse, select your Java package.
  3. Click Run > Run Configurations.
  4. Expand the Web Application menu item.
  5. Add the following lines into the VM Arguments pane:
    -Drdbms.server=local
    -Drdbms.driver=com.mysql.jdbc.Driver
    -Drdbms.url=jdbc:mysql://localhost:3306/search_values?user=username&password=password
    
  6. Click the Classpath tab.
  7. Select your project and click Add External JARs...
  8. Navigate to the Google App Engine SDK directory, then lib/impl, and select the JDBC driver JAR file. Click Open. The driver JAR is listed under User Entries.
  9. Click Apply.
Your development environment is now ready to use your local MySQL database. Let's now, use this database.

4. Use your MySQL table and copy the text values from Google Datastore to MySQL Table

Copying the data from Datastore entity to the table is quite easy:

  Connection conn = null;
  try {
   DriverManager.registerDriver(new AppEngineDriver());
   conn = DriverManager.getConnection("jdbc:google:rdbms://[your db instance]/search_values");
   conn.setAutoCommit(false);  
   String statement = "REPLACE INTO articles (entity_key, title, body) VALUES( ? , ? , ? )";
   PreparedStatement stmt = conn.prepareStatement(statement);

   DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
   Query q = new Query("Article");   
   PreparedQuery pq = datastore.prepare(q);

   // loop on each entity and insert the values in the SQL Table
   for (Entity result : pq.asIterable()) {
    stmt.setString(1,  KeyFactory.keyToString(result.getKey())   );
    stmt.setString(2,  result.getProperty("title").toString() );
    stmt.setString(3,  result.getProperty("body").toString() );
    stmt.executeUpdate();
    conn.commit();
   }



  } catch (SQLException e) {
   e.printStackTrace();
  } finally {
   if (conn != null)
    try {
     conn.close();
    } catch (SQLException ignore) {}
  }



Some specials things here, compare to standard Java Web Development:
  • I manage the connection directly in my code (I have not looked yet if I can use datasources/connection pool in the context of Google AppEngine)
  • Line #3: registering the AppEngine driver that is responsible of managing the connection, expecially work in development -local MySQL- or production mode -CloudSQL-.
  • Line #4 : Get the connection. It is interesting to mention that in development the connection URL is grabbed from the environment variable Drdbms.url you have set previously. We will see later how we move this to the cloud. This is the magical part of the AppEngineDriver that manages different connection types Local MySQL or CloudSQL depending of the context
  • After these lines, the code is quite simple :
    • Get all the Articles entities from the datastore and loop
    • "Upsert" the database record (REPLACE INTO syntax)
  • Line #15 is storing the Key of the entity in a safe string using the KeyFactory.keyToString() method.

If you want to test this code just put this lines in a servlet to "sycnhronize" the data from datastore into the MySQL table. Obviously this code is just here for learning propose and should be integrated in a better way in a real application; starting with pushing the data in the database when entities are created/updated (and deleted ;) ). The sample code available from GitHub contains these methods.


5. Implement a search method

The goal is simple return a list of entities returned by a simple search criteria :
  • public Iterable searchEntities(String query)

The logic is here quite simple:
  1. Execute a SQL query
  2. For each result, get the Entity using the Key
  3. Return the list of Entities

 public Iterable searchEntity(String query) {
  List  results = new ArrayList();
  Connection conn = null;
  try {
   DriverManager.registerDriver(new AppEngineDriver());
   conn = DriverManager.getConnection("jdbc:google:rdbms://[your db instance]/search_values");
   String statement = "SELECT entity_key FROM articles WHERE MATCH (title,body) AGAINST (? WITH QUERY EXPANSION);";
   PreparedStatement stmt = conn.prepareStatement(statement);
   stmt.setString(1, query);
   ResultSet rs = stmt.executeQuery();
   while (rs.next()) {
    String keyAsString = rs.getString(1);    
    Entity article = DatastoreServiceFactory.getDatastoreService().get( KeyFactory.stringToKey(keyAsString)  );
    results.add(article);
   }

  } catch (SQLException e) {
   e.printStackTrace();
  } catch (EntityNotFoundException e) {
   e.printStackTrace();
  } finally {
   if (conn != null)
    try {
     conn.close();
    } catch (SQLException ignore) {}
  }
  return results;
 }


In this method, the system connect to the database and then execute a query to search data using any type of SQL/MySQL query. In this exampe I am using the full text function with the "WITH QUERY EXPANSION". You can obviously use any type of SQL queries for example simple LIKE statement if this is enough four your application.

With this approach when I search for :
  • "database" : the method returns all the articles concerning database, mysql, RDBMS independently of the case.
  • "index" " the method returns all the articles talking about indexing/indexes or search.

6.  Deploy to GAE 


Once you have created your application, and activated and configure your CloudSQL instance (here), you can deploy your application and enjoy an easy way of using Full Text Search with GAE.


Conclusion

In this article I explained how you can use Google Cloud SQL to easily supports Full Text Search queries, based on the Full Text support of MySQL.

The code snippets that I have shared in this article are really basic and not ready for real life usage but still a good starting point. For example I have been using this in my application with GAE Queues to manage my indexes on larger volume of data.

As said before, you can test the application online at http://gae-fulltext-search.appspot.com/ and the source code is available on GitHub : https://github.com/tgrall/gae-full-text-search

Thursday, January 19, 2012

eXo Platform : Integrate Twitter and eXo Activity Stream

eXo Platform 3.5 provides many extension points and API for developers, allowing them to create very cool stuff.

I have developed a small extension that allows any user to associate his Twitter account to his eXo Platform account. This extension simply post on your Twitter account when you write a message with a special hashtag (#tw).

This is a very quick development that I have done while waiting for my kids, so I still have things to integrate to provide complete feature, but this is a good example to show how you can extend the platform.

You can view it in action in this video:




You can download the source code and the binaries from this GitHub project. I hope to find some time to complete the feature, and document this use case.

Enjoy!


Monday, December 5, 2011

How to watch YouTube videos offline (on OS X)?

Lately I have been traveling a lot, and I was not able to access the internet all the time; but I still want to look at some YouTube video, for example the greate Google I/O Sessions...

My needs are simple :

  • running on OS X (Lion)
  • download the video easily
  • no need to convert the file (to be able to read the file as soon as possible)
  • free
After some basic research, I found an easy way to achieve this using the following softwares:
  Enjoy!

Sunday, November 20, 2011

Installing Memcached on Mac OS X and using it in Java


Introduction

In this article I will explain how you can:

  1. Install and Configure Memcached on Mac OS X
  2. Use Memcached in your Java Application

I won't go in too much detail about the benefits of using a distributed cache in your applications, but let's at least provide some use cases for applications that are running in the context of an enterprise portal, eXo Platform in my case - surprising isn't? And I will show this in another post.

We have many reasons to use a cache (distributed or not), in the context of enterprise portal, let's take a look to some of these reasons:

  • A portal is used to aggregate data in a single page. These data could come from different sources : Web Services, Database, ERP, ..... and accessing the data in real time could be costly. So it will be quite interesting to cache the result of the call when possible.
  • If the portal is used to aggregate many data from many sources, it is sometime necessary to jump into another application to continue some operation. A distributed and shared cache could be used to manage some context between different applications running in different processes (JVM or even technologies)
These are two example where a shared cache could be interesting for your portal based applications, we can find many other reason.


Note that the Portlet API (JSR-286) contains already a cache mechanism that cache the HTML fragment, and that eXo Platform also provide a low level cache, based on JBoss Cache.


Installation and Configuration

Installing Memcached from sources

You can find some information about Memcached installation on the Memcached Wiki. The following steps are the steps that I have used on my environment.

As far as I know, Memached is not available as package for Mac OS X. I am still on Snow Leopard (10.6.8), and I have installed XCode and all development tools. I have use the article "Installing memcached 1.4.1 on Mac OS X 10.6 Snow Leopard" from wincent.com. For simplicity reason I have duplicate the content and updated to the latest releases.

1. Create a working directory :

$ mkdir memcachedbuild
$ cd memcachebuild

 2. Install libevent that is mandatory for memcached

$ curl -O http://www.monkey.org/~provos/libevent-1.4.14-stable.tar.gz
$ tar xzvf libevent-1.4.14-stable.tar.gz
$ cd libevent-1.4.14-stable
$ ./configure
$ make
$ make verify
$ sudo make install  

3. Install memcached

Go back to your install directory (memcachedbuild)

$ curl -O http://memcached.googlecode.com/files/memcached-1.4.10.tar.gz
$ tar xzvf memcached-1.4.10.tar.gz
$ cd memcached-1.4.10
$ ./configure
$ make
$ make test
$ sudo make install 
You are now ready to use memcached that is available at /usr/local/bin/memcached

This allows you to avoid changing to the pre-installed memcached located in /usr/bin, if you want to replace it instead of having you own install, just run the configure command with the following parameter:  ./configure --prefix=/usr

Starting and testing Memcached

Start the memcached server, using the following command line:

$ /usr/local/bin/memcached -d -p 11211

This command starts the memcached server as demon (-d parameter), on the TCP port 11211 (this is the default value). You can find more about the memcached command using man memcached.

It is possible to connect and test your server using a telnet connection. Once connected you can set and get object in the cache, take a look to the following paragraph.

$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to tgrall-server.
Escape character is '^]'.
set KEY 0 600 16
This is my value
STORED
get KEY
VALUE KEY 0 16
This is my value
END

 
The set command allows you to put a new value in the cache using the following syntax:

set <key>  <flags> <expiration_time>  <number_of_bytes> [noreply] \n\n

<value>
  • key : the key used to store the data in the cache
  • flags : a 32 bits unsigned integer that memcached stored with the data
  • expiration_time : expiration time in seconds, if you put 0 this means no delay
  • number_if_bytes : number of bytes in the data block
  • noreply : option to tell the server to not return any value
  • value : the value to store and associate to the key.
    This is a short view of the documentation located in your source directory /memcachedbuild/memcached-1.4.10/doc/protocol.txt .


    The get command allows you to access the value that is associated with the key.

    You can check the version of memcahed you are running by calling the stats command in your telnet session.


    Your memcached server is up and running, you can now start to use it inside your applications.


    Simple Java Application with Memcached

    The easiest way to use memcached from your Java applications is to use a client library. You can find many client libraries. In this example I am using spymemcached developped by the people from Couchbase.

    1. Adding SpyMemcached to your Maven project

    Add the repository to you pom.xml (or you setting.xml)

    <repository>
        <id>spy</id>
        <name>Spy Repository</name>
        <layout>default</layout>
        <url>http://files.couchbase.com/maven2/</url>
    </repository> 

    then the dependency to your pom.xml

    <dependency>
        <groupid>spy</groupid>
        <artifactid>spymemcached</artifactid>
        <version>2.7.3</version>
    </dependency>
    
    
    

    2. Use SpyMemcache client in your application

    The following code is a simple Java class that allows you to enter the key and the value and set it in the cache.


    package com.grallandco.blog;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.Console;
    import java.io.InputStreamReader;
    import java.util.Date;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import net.spy.memcached.AddrUtil;
    import net.spy.memcached.MemcachedClient;
    
    public class Test {
    
        public static void main(String[] args) {
           try {
               
               System.out.print("Enter the new key : ");
               BufferedReader reader = new BufferedReader( new InputStreamReader(System.in));
               String key = null;
               key = reader.readLine();
               
               System.out.print("Enter the new value : ");
               String value = null;
               value = reader.readLine();
               
                MemcachedClient cache = new MemcachedClient(AddrUtil.getAddresses("127.0.0.1:11211"));
                
                // read the object from memory
                System.out.println("Get Object before set :"+ cache.get(key)  );
    
                // set a new object            
                cache.set(key, 0, value );
    
                System.out.println("Get Object after set :"+ cache.get(key)  );
                
    
            } catch (IOException ex) {
                Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                System.exit(0);
            }
    
           
            System.exit(0);
           
        }
    }
    
    
    

    So when executing the application you will see something like :

    Enter the new key : CITY
    Enter the new value : Paris, France
    2011-11-16 15:22:09.928 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/127.0.0.1:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
    2011-11-16 15:22:09.932 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@5b40c281
    Get Object before set :null
    Get Object after set :Paris, France
    

    You can also access the object from a Telnet session:
    get CITY
    VALUE CITY 0 13
    Paris, France
    END
    


    You can use any Java class in your application, the only thing to do is to make this class serializable.

    This is it for the first post about memcached and Java,  I am currently working on a small example integrating Web Services call, Portlets and memcached.

    Friday, September 2, 2011

    JAX-RS: Jersey and JSON single element arrays

    Last week I have been struggling with a small issue while developing a service using Jersey. The goal of this service is to provide JSON object to my Web application, so called directly from the browser. This service returns in a JSON array a list of Employees, something like:

    {"employee":[
     {"email":"jdoe@example.com","firstName":"John","lastName":"Doe"},
     {"email":"mmajor@example.com","firstName":"Mary","lastName":"Major"}
    ]}
    
    So an "employee" array, this is perfect and expected, but when my service returns a single element the returned object looks like:
    {"employee":{"email":"jdoe@example.com","firstName":"John","lastName":"Doe"}}
    
    As you can see brackets [...] are missing around the employee item. This is an issue since your client code is expecting an array.

    A solution...

    My application is using Jersey, the JAX-RS Reference Implementation, and JAXB for the serialization of Java Objects to JSON, as I have explained in a previous blog post. I found a solution to this by creating a new JAXB Context Resolver. In this resolver I can control how the JSON object should be generated, here is my implementation :
    import com.grallandco.employee.service.converter.EmployeeConverter;
    import javax.ws.rs.ext.ContextResolver;
    import javax.ws.rs.ext.Provider;
    import javax.xml.bind.JAXBContext;
    
    import com.sun.jersey.api.json.JSONConfiguration;
    import com.sun.jersey.api.json.JSONJAXBContext;
    
    @Provider
    public class JAXBContextResolver implements ContextResolver < JAXBContext > {
    
        private JAXBContext context;
        private Class[] types = {EmployeeConverter.class};
    
        public JAXBContextResolver() throws Exception {
            this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("employee").build(),
                    types);
    
        }
    
        public JAXBContext getContext(Class objectType) {
            for (Class type : types) {
                if (type == objectType) {
                    return context;
                }
            }
            return null;
        }
    }
    
    
    First of all I declare this new class as a @Provider to say that it this class is of interest to the JAX-RS runtime. I put in the types array the list of the Java classes that are concerned by the serialization (line#13). Then I create the ContextResolved with the different options that fulfill my requirements. You can take a look to the JAXBContextResolver Javadoc to see all the possible options available. With this class, the service now returned the following JSON String:
    {"employee":[{"email":"jdoe@example.com","firstName":"John","lastName":"Doe"}]}
    
    You can find a complete example (NetBeans project) here.

    Wednesday, August 31, 2011

    How to create a new Content List Template for eXo Platform 3

    Introduction

    eXo Platform provide many powerful features to manipulate and expose any type of content on a page. This is due to the fact that eXo stores the all the content in its Java Content Repository (JCR) and render the content on a page using Groovy Templates.

    In this how to you will learn how you can create and use template that are used in the "Content List" portlet. For example in the ACME sample site you can show the content in 1 or 2 columns just by selecting different templates.


    One Column View

    Two Columns View

    Creating a new Content List Template

    In this section you will learn how to create a new template using the eXo IDE. Before writing the new template it is important to learn where the templates are stored.

    eXo Content Service: Template Storage

    Like many things inside eXo Platform, the eXo JCR is used to stored the templates. Templates are just a special type of content. This allows developers to easily write and test code without having complex deployment process, but also it make the life easy to export a running configuration to another one. For this you just need to use the standard JCR export/import features.
    All the template and eXo Content Service configurations are stored inside a specific JCR workspace named : dms-system.
    Each template type (Document Type, Content List, ....) is stored in a specific location. In our case we want to work on the "Content List" portlet so the template are stored inside the following folder:
    • /exo:ecm/views/templates/content-list-viewer/list/

    Just for the fun of it, let's inspect this folder using the eXo CRaSH utility. If you are not interested you can jump to the next section. CRaSH is a shell for Java Content Repositories, the source of CRaSH is available on Google Code. So in a terminal window:
    1. Connect to CRaSH using telnet client:
      telnet 127.0.0.1 5000

    2. Connect to the JCR workspace using the following command:
      connect -u root -p gtn -c portal dms-system
      Where: -u is the user, -p the password, -c the Portal Container, and dms-system the workspace to use

    3. Move the the folder that contains all the templates for the Content List portlet:
      cd /exo:ecm/views/templates/content-list-viewer/list/

    4. List all the templates using the ls command


    You can see the list of all the templates available for the Content List portlet.

    Create a new template using the eXo IDE

    Let's now create a new template using the IDE. For this be sure you are connected with a user that is part of the /Developer group. For simplicity reason I am using the root user.

    The first important step is to go the the template location using the following steps:
    1. Access the IDE: click on My Spaces > IDE.

    2. Switch dms-system workspace: In the IDE menu click My Spaces >on Window > Select Workspace. And select the dms-system location in the dialog box and click OK.

    3. In the file structure on the left navigate to the template location :
      /exo:ecm/views/templates/content-list-viewer/list/

    4. Create a new template : In the IDE Menu click on File > New > Groovy Template

    5. Save the file as "MyNewTemplate.gtmpl"

    6. Enter some basic code:
      <h1>This is my template</h1>
      The date is <= new Date()>
      


    7. Save the template

    8. Go back to the Home page of the Acme Site

    9. Switch to Edit more by selecting Edit in the top right drop down list.

    10. Move you mouse at the top of the list of news and click on the preference button:

    11. In the list of templates, select the "MyNewTemplate", and click save.
    We have created our new template, and use it on a page. We should now add some more interesting code to the template to really loop over the content based on the portlet configuration. But before this it is important to talk about caching and code modification.

    eXo Template and Cache

    To improve performances and a running system, the compiled version of the template is by default cached. This is why when you are modifying a template you do not see any change. We have two ways to work around this:
    • Run eXo Platform in Debug mode, in this case nothing is cached
    • Invalidate the cache manually using JMX
    Since working with no cache at all is not an option, here is the MBean you have to use to invalidate the Template Service cache:
    • exo:portal="portal",service=cache,name="TemplateService", then call the clearCache operation on it
    I do use JConsole for this, but you can use any method to call your MBeans operation.

    Do not forget to call this operation each time you modify your template to be sure eXo recompile the template.

    Accessing Content in the template

    The current code of the template is really simple, you need now to add code to print the content in the page. For this we will be using some eXo Content programming, once again in the IDE.
    If you are not interested to have detailed explanation of the code you can go to the complete source code here.
    The template used by the Content List portlet is based on the following Java class org.exoplatform.wcm.webui.clv.UICLVPresentation, this class is responsible to set the complete context that you can use in the template such as:
    • The folder or category that contains the content to show. The "Folder Path" field in the preference screen
    • The display settings: title, number of documents, elements to show, ...
    Here is the code to access these preferences:
        // import all the classes need in the template
        import javax.jcr.Node;
        import org.exoplatform.wcm.webui.paginator.UICustomizeablePaginator;
        import org.exoplatform.wcm.webui.clv.UICLVPortlet;
        import org.exoplatform.wcm.webui.Utils;
        import org.exoplatform.services.wcm.core.NodeLocation;
    
        // get the portlet preferences
        def header = uicomponent.getHeader();
        def isShowRssLink = uicomponent.isShowRssLink();
        def isShowHeader = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_HEADER);
        def isShowRefresh = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_REFRESH_BUTTON);      
    
        def isShowTitle = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_TITLE);
        def isShowDate = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_DATE_CREATED);
        def isShowLink = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_LINK);
        def isShowReadmore = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_READMORE);
        def isShowImage = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_ILLUSTRATION) ;
        def isShowSummary = uicomponent.isShowField(UICLVPortlet.PREFERENCE_SHOW_SUMMARY);   
    
    
    The uicomponent object is defined by the container class of the portlet that calls the template. This class contains many utility methods. In the code above I retrieve all the preferences of the portlet, since the name are self-explanatory it is not necessary to detail them, especially when you look at the preferences screen below:


    Now that the template has all the preferences, it is time to loop on the content on print the information.
    The eXo Content Service provides API to manipulate the content, including pagination of content. The idea behind this is to let the Content Service manage the JCR query, sorting, caching and pagination of data. So in your template you will mainly manage two classes to loop through the content to show:
    • uicomponent.getUIPageIterator() a paginator object that is configured based on the portlet preferences
    • uicomponent.getCurrentPageData() contains a list of the content (JCR Nodes) that should print on the current page
    So let's print all the content of the page as a simple HTML list:
      <% for (viewNode in uicomponent.getCurrentPageData()) { def title = viewNode.getProperty("exo:title").getString(); print("
    • $title
    • "); } %>
    Just copy this code in your template, save it, refresh the cache... and go to your page. You should see the list of the content in a simple HTML list.
    On each content (Node), eXo Content API provides some helper method to easily manipulate the content and avoid using the JCR API directly. In the following code you can see the most important methods accessing content properties:
            def itemName = viewNode.getName();
            def itemLink = uicomponent.getURL(viewNode);  
            def webdDavLink = uicomponent.getWebdavURL(viewNode);    
            def itemDateCreated = uicomponent.getCreatedDate(viewNode);
            def itemModifiedDate = uicomponent.getModifiedDate(viewNode);
            def itemAuthor = uicomponent.getAuthor(viewNode);     
            def imgSrc = uicomponent.getIllustrativeImage(viewNode);
            def itemTitle = uicomponent.getTitle(viewNode);
            def itemSummary = uicomponent.getSummary(viewNode);
    
    One important point is the fact that these methods are responsible of many things, for example: formatting dates, returning complete URLs depending of the context of the portlet.
    Based on these methods you can now work on the presentation of the information on the page. Let's for example print the image, the title and allow user to click on the title to go in the detail view of the article. This is done simply using the following code:
    <%
      for (viewNode in uicomponent.getCurrentPageData()) {
        def itemName = viewNode.getName();
        def itemLink = uicomponent.getURL(viewNode);  
        def webdDavLink = uicomponent.getWebdavURL(viewNode);    
        def itemDateCreated = uicomponent.getCreatedDate(viewNode);
        def itemModifiedDate = uicomponent.getModifiedDate(viewNode);
        def itemAuthor = uicomponent.getAuthor(viewNode);     
        def imgSrc = uicomponent.getIllustrativeImage(viewNode);
        def itemTitle = uicomponent.getTitle(viewNode);
        def itemSummary = uicomponent.getSummary(viewNode);
        
        %> 
    

    $itemTitle

    $itemSummary
    <% } %>
    For simplicity reason, this code does not manage any null value. Also the template do not deal with the portlet preferences such as the "Header", "RSS" links and so on, do not hesitate to do it if you want. The Web site should look like the following image:


    The last important point is to add the support for the in context editing that allows the user to edit the content directly from the template. Once again this is done with a method of the uicomponent object, that create a DIV around the content. Let's add this to the template:
    <%
      for (viewNode in uicomponent.getCurrentPageData()) {
        def itemName = viewNode.getName();
        def itemLink = uicomponent.getURL(viewNode);  
        def webdDavLink = uicomponent.getWebdavURL(viewNode);    
        def itemDateCreated = uicomponent.getCreatedDate(viewNode);
        def itemModifiedDate = uicomponent.getModifiedDate(viewNode);
        def itemAuthor = uicomponent.getAuthor(viewNode);     
        def imgSrc = uicomponent.getIllustrativeImage(viewNode);
        def itemTitle = uicomponent.getTitle(viewNode);
        def itemSummary = uicomponent.getSummary(viewNode);
        
        %> 
    
    <%=uicomponent.addQuickEditDiv("MyTemplateContentEditor", viewNode)%>

    $itemTitle

    $itemSummary < /div>
    <% } %>
    The lines 15 and 19 are new in this template and provide support for Quick Edit feature.

    Done! We have created a new template for eXo Platform Content Service using embedded IDE.

    Conclusion

    In this article you have learned how to create a new template for eXo Content Service, with some basic steps:

    • Create a new Groovy Template using the eXo IDE
    • Edit this template using the eXo Java Content API
    • Configure your Content List portlet instance on a page to select the new template

    You can now create your own templates and use your imagination to add cool features to your site (for example the carrousels you see on the eXo site are using custom CLV template.)

    Monday, April 11, 2011

    How to protect your REST service and Gadget in eXo Platform

    During a partner workshop I was showing to the developers how the eXo IDE can help them to develop new features quickly and push them to the users in few minutes. A person asked me if it is possible to put some restriction in services and gadgets based on user profile.
    As you can guess the answer is YES WE CAN!

    • How to access the security context in a REST service
    • How to check is a user is member of a group and manage permission from this information
    • How to consume this service in a gadget and leverage the security to protect resources
    Not-authorized
    Authorized
    If you are not interested to follow steps by step the explanations you can directly jump to the complete REST Service code or download the full eXo IDE Project from GitHub

    Access the User Profile from your REST Service

    As you probably know eXo Platform uses JAX-RS as API to develop and deploy REST Services. eXo developers can create REST services using their favorite Java IDE, but here I am using the eXo IDE package with eXo Platform.

    To access the security and user information in your service method it is possible to use the SecurityContext class of the JAX-RS API. Your method signature will look like:
    import javax.ws.rs.Path
    import javax.ws.rs.GET
    import javax.ws.rs.PathParam
    import javax.ws.rs.core.Response
    import javax.ws.rs.core.MediaType
    import javax.ws.rs.Produces
    import javax.ws.rs.core.SecurityContext
    import javax.ws.rs.core.Context
    
    @Path("/system")
    @Produces("application/json")
    public class SystemInformationService {
    
      @GET
      @Path("information")
      public Response getSystemInfo(@Context SecurityContext sc) {
        sc.getUserPrincipal();   
        return Response.ok("foo", MediaType.APPLICATION_JSON).build();    
      }
      
    }
     
    
    In lines 7 and 8, I import the classes needed to inject the security context in the method getSystemInfo() in line 16. For now let's forget about the other part of the code.
    With the Security Context object you can now access many things in your code. Two methods are quite interesting for this example: getUserPrincipal() and isUserInRole(), since our goal is to check if a user is allowed to execute or not a part of the business logic.
    It is important here to remember that we cannot directly use the isUserInRole() method since this method uses the logical JavaEE roles that are defined at the Java application level. In our case we are interested to know if a user is present in a "eXo User Identity" Group, for example member of the /platform/administrators group. This information is populated during the login process and comes from the user provider that could be LDAP, the eXo Database or JCR, or any other source since developers can extend this API to plug their own provider.
    Let's create an helper method that check, using the eXo Identity Service, if the user that executes the method is present in a group.
    ...
    import org.exoplatform.container.ExoContainer;
    import org.exoplatform.container.ExoContainerContext;
    import org.exoplatform.container.component.ComponentPlugin;
    import org.exoplatform.services.security.Identity;
    import org.exoplatform.services.security.IdentityRegistry;
    ...
    ...
    
      private boolean isMemberOf(String username,String group) {
        ExoContainer container = ExoContainerContext.getCurrentContainer();
        IdentityRegistry identityRegistry = (IdentityRegistry) container.getComponentInstanceOfType(IdentityRegistry.class);
        Identity identity = identityRegistry.getIdentity(username);
        return identity.isMemberOf( group );
      }
    
    So this method is quite simple, it takes as parameter:
    • the name of the user, that you can get from the UserPrincipal.getName() method
    • the eXo Group you want to check, for example /platform/administrator
    You can now call this method from your resource to check the user, and code the "permission business logic". The method could now looks like:

    ...
    
      @GET
      @Path("information")
      public Response getSystemInfo(@Context SecurityContext sc) {
        String groupToCheck = "/platform/administrators";
        String response = "";
        if (sc.getUserPrincipal() == null || !this.isMemberOf(sc.getUserPrincipal().getName(), groupToCheck) ) {
          response = "NOT-ALLOWED";
        } else {
          response = "ALLOWED";
        }   
        return Response.ok(  response   , MediaType.APPLICATION_JSON).build();
      }
    
    ...
    
    In this example for simplicity reason I have hard coded the group to check, you can obviously use smarter code to user external configuration to inject a list of group to check for example. I manage the security logic of my method using simple if statement and return a string. You can also depending of your needs, manage the status of your response and use HTTP Code for example return an HTTP 403. For this you just need to return a different response using following code:
        return Response.status(Response.Status.FORBIDDEN).build();
    
    For simplicity reason I will stay with a single Response status (OK) and manage the permission in my client code.

    Complete REST Service

    Let's take a look to the full service now, this service allows administrators to get the list of the System Properties, other users get an status string "NOT-ALLOWED":
    import javax.ws.rs.Path
    import javax.ws.rs.GET
    import javax.ws.rs.PathParam
    import javax.ws.rs.core.Response
    import javax.ws.rs.core.MediaType
    import javax.ws.rs.core.CacheControl
    import javax.ws.rs.Produces
    import javax.ws.rs.core.SecurityContext
    import javax.ws.rs.core.Context
    
    import org.exoplatform.container.ExoContainer;
    import org.exoplatform.container.ExoContainerContext;
    import org.exoplatform.container.component.ComponentPlugin;
    import org.exoplatform.services.security.Identity;
    import org.exoplatform.services.security.IdentityRegistry;
    
    @Path("/system")
    @Produces("application/json")
    public class SystemInformationService {
      
     
      @GET
      @Path("information")
      public Response getSystemInfo(@Context SecurityContext sc) {
        String groupToCheck = "/platform/administrators";
        SimpleResponseWrapper response = new SimpleResponseWrapper();
        String status = "";
        if (sc.getUserPrincipal() == null || !this.isMemberOf(sc.getUserPrincipal().getName(), groupToCheck) ) {
          response.status = "NOT-ALLOWED";
          } else {
            response.status = "OK";
            response.data = System.getProperties();
         
         }  
      
           CacheControl cacheControl = new CacheControl();
           cacheControl.setNoCache(true);
           cacheControl.setNoStore(true);
           return Response.ok(  response   , MediaType.APPLICATION_JSON).cacheControl(cacheControl).build();    
          }
             
          private boolean isMemberOf(String username,String role) {
            ExoContainer container = ExoContainerContext.getCurrentContainer();
            IdentityRegistry identityRegistry = (IdentityRegistry) container.getComponentInstanceOfType(IdentityRegistry.class);
            Identity identity = identityRegistry.getIdentity(username);
            return identity.isMemberOf( role );
          }
          
        }
     
    public class SimpleResponseWrapper {
          String status;
          Object data;
    }
          
    
    To summarize:
    • Line 24 : the SecurityContext is injected to the method
    • Line 26 : Initialization of a simple ResponseWrapper defined on line 51, that contains a status and data. That will be serialized in JSON by the eXo REST engine.
    • Line 28 : the method check if a user is connected and member of /platform/administrator. If not it send response with the status NO-ALLOWED.
    • Line 31/32 : The response object is sent. This response contains an OK status and the data (system properties list)
    • Line 42 : Using the eXo Identity Service, the method check if the connected user is member of a specific group.

    Consume the service into a Gadget

    I can now take this service and consume it into an Gadget. I also develop this Gadget using the eXo IDE.
    The following code shows the Javascript part of the Gadget that calls the service, check the security and push the response content in Gadget body. For productivity I use JQuery framework.
        
    
    Here some quick explanation about this code:
    • Line 23: To call the REST service, I use the $.getJSON() method. This method is really easy to use when you are executing the Gadget is in the same container than the portal that consumes it. When you are using the gadget.io.MakeRequest is interesting to proxy a request and you need to re-authenticate, for example using oAuth.
    • Line 3 : This is the call back method, as you can see in this method I use the ResponseWrapper to check the code in the status attribute. Depending of the status OK or not I do print the value.

    Conclusion


    In this how-to you have learned how to:
    • Get the security context in your REST Service
    • Check the membership of a user using the eXo Identity Service
    • Create a gadget that consume this service and expose only data to user with correct profile
    • Download the full project from GitHub