Big Data/Analytics Zone is brought to you in partnership with:

I started my software adventure with GWBasic programming language. I met with Visual Basic language after QuicBasic and I developed many applications with it until 2000. I stepped into the world of web with PHP. After that, my path crossed with Java! I have been developing enterprise applications with Java EE Technologies since 2005. JavaServer Faces and Spring frameworks are in my area of expertise. I’m trying to specialize in NoSQL Technologies. Hüseyin has posted 15 posts at DZone. You can read more from them at their website. View Full User Profile

Java Web Application and ElasticSearch (Video)

10.06.2013
| 4238 views |
  • submit to reddit
Today I am demonstrating the usage of ElasticSearch, which is an open source, distributed and scalable full text search engine and a data analysis tool in a Java web application. 

First of all, I want to share with you a video recording. I made this video with the aim of visually reinforcing the issues that I dealt in a series of articles about ElasticSearch. 

Now we can continue where we left off. 

Our data will be stored in a database management system in an enterprise application where there is high traffic and where simultaneously incoming requests can be expressed in hundreds or even in thousands. However, routing all these data to the database may pose a bottleneck risk. The main objective in this tutorial is minimizing the database I/O operations, which are most likely to create a bottleneck risk (especially when working with big data) in a web application with a tool like ElasticSearch. 

The application (the introduction takes place in the video) with Java API designed for this purpose performs some operations, such as sending the post data to ElasticSearch, getting, searching (full text), as well as updating and deleting operations. ElasticSearch is also functioning as a data store in this application. The hypothetical example that we will follow is about recording the published articles in kodcu.com, which was in the previous articles. 

Tools and technologies I used in the sample application: 

  • ElasticSearch version 0.90.3
  • JSF version 2.2
  • PrimeFaces version 3.5
  • Jetty 7.x Maven Plugin
  • Maven version 3.0.4
  • JDK version 1.7

1. The structure of project directory

Screen Shot 2013-08-27 at 12.43.22 AM

2.Dependencies

      <dependency>
          <groupId>org.glassfish</groupId>
          <artifactId>javax.faces</artifactId>
          <version>${jsf.version}</version>
      </dependency>
      
      <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>${primeFaces.version}</version>
      </dependency>
        
      <dependency>
          <groupId>org.primefaces.themes</groupId>
          <artifactId>bootstrap</artifactId>
          <version>${primeFacesTheme.version}</version>
      </dependency>

      <dependency>
          <groupId>commons-fileupload</groupId>
	  <artifactId>commons-fileupload</artifactId>
	  <version>${commonsFileUpload.version}</version>
      </dependency>

      <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>${elastic.version}</version>
    </dependency>
    
    <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-j2ee</artifactId>
        <version>${jbossJ2ee.version}</version>
    </dependency>

3. ClientProvider.java

public class ClientProvider {
    
    private static ClientProvider instance = null;
    private static Object lock      = new Object();
    
    private Client client;
    private Node node;

    public static ClientProvider instance(){
        
        if(instance == null) { 
            synchronized (lock) {
                if(null == instance){
                    instance = new ClientProvider();
                }
            }
        }
        return instance;
    }

    public void prepareClient(){
        node   = nodeBuilder().node();
        client = node.client();
    }

    public void closeNode(){
        
        if(!node.isClosed())
            node.close();

    }
    
    public Client getClient(){
        return client;
    }
    
    
    public void printThis() {
        System.out.println(this);
    }
    
}

We will get the client object once again, and we will carry out all operations on ElasticSearch from a client verifier that was encoded by singleton pattern approach, rather than getting again and again before each operation. 

4. ElasticSearchSystemEventListener.java

@Override
    public void processEvent(SystemEvent event) throws AbortProcessingException {
        if(event instanceof PostConstructApplicationEvent){

            /* Preparing the ElasticSearch Client */
            System.out.println("*********************************************");
            System.out.println("Preparing the ElasticSearch Client");
            ClientProvider.instance().prepareClient();
            System.out.println("The ElasticSearch Client was prepared");
            System.out.println("*********************************************");
        }
        
        if(event instanceof PreDestroyApplicationEvent){

            /* ElasticSearch node is closing */
            System.out.println("*********************************************");
            System.out.println("ElasticSearch Node is closing");
            ClientProvider.instance().closeNode();
            System.out.println("ElasticSearch Node was closed");
            System.out.println("*********************************************");
            
        }
        
    }

Because the application has been built with JSF, we used the SystemEventListener implementation, which is the optimal option to create an embedded node when the application arises, request a client from this node, and then close it when the application ends. 

5. Listing data in DataTable

In order to take full advantage of the functionality provided by the DataTable component of PrimeFaces, documents which were acquired with Search API need to be converted to POJO. This operation is performed manually. It is possible to use the jackson-databind library for this purpose.  

public void prepareDocumentList(){

        wildCardQuery = "";
        ClientProvider.instance().getClient()
                .admin().indices().prepareRefresh().execute().actionGet();

        try {

            SearchResponse response = ClientProvider.instance().getClient()
                    .prepareSearch(INDEX_NAME)
                    .setTypes(TYPE_NAME)
                    .setQuery(matchAllQuery())
                    .execute()
                    .actionGet();

            articleList.clear();

            Article temporary = null;
            String[] tags     = null;

            if (response != null) {
                for (SearchHit hit : response.getHits()) {

                    try {

                        tags = hit.getSource().get("tags").toString().split(",");
                        temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()),
                                hit.getSource().get("title").toString(),
                                hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()),
                                hit.getSource().get("author").toString(), tags);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    articleList.add(temporary);
                }
            }

            collectionSort();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }
    }

6. Registry and Update

public void saveArticle() {

        Long ID = 1l;
        try {

            CountResponse countResponse= ClientProvider.instance().getClient()
                                     .prepareCount(INDEX_NAME)
                                     .setQuery(termQuery("_type", TYPE_NAME))
                                     .execute().actionGet();
            ID += countResponse.getCount();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }

        String[] postTags = tag.split(",");

        try {
            
            if(null == selectArticle)
                ClientProvider.instance().getClient().prepareIndex(INDEX_NAME, TYPE_NAME, ID.toString())
                    .setSource(putJsonDocument(ID, article.getTitle(), article.getContent(),
                            article.getPostDate(), postTags,
                            article.getAuthor())).execute().actionGet();
            else {
                
                Map<String, Object> updateObject = new HashMap<String, Object>();
                
                updateObject.put("title", selectArticle.getTitle());
                updateObject.put("content", selectArticle.getContent());
                updateObject.put("postDate", selectArticle.getPostDate());
                updateObject.put("author", selectArticle.getAuthor());
                updateObject.put("tags", postTags);


                ClientProvider.instance().getClient().prepareUpdate(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString())
                        .setScript("ctx._source.title=title; ctx._source.content=content; "
                                + "ctx._source.postDate=postDate; ctx._source.author=author; "
                                + "ctx._source.tags=tags")
                               .setScriptParams(updateObject).execute().actionGet();
            }
            
        } catch (Exception ex){
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString()));
        }

        prepareDocumentList();
        initArticle();
        
    }

7. Delete

public void removeArticle(){

        try {
            ClientProvider.instance().getClient().prepareDelete(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString()).execute().actionGet();
        } catch (Exception ex){
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString()));
        }

        prepareDocumentList();
        initArticle();
    }

8. Full text source and wildcard

public void fullTextSearch(){

        articleList.clear();
        Article temporary = null;
        String[] tags     = null;

        try {
            QueryBuilder queryBuilder = QueryBuilders.queryString("*"+wildCardQuery+"*").field("_all");
            SearchRequestBuilder searchRequestBuilder = ClientProvider.instance().getClient().prepareSearch(INDEX_NAME);
            searchRequestBuilder.setTypes(TYPE_NAME);
            searchRequestBuilder.setSearchType(SearchType.DEFAULT);
            searchRequestBuilder.setQuery(queryBuilder);
            searchRequestBuilder.setFrom(0).setSize(60).setExplain(true);

            SearchResponse response = searchRequestBuilder.execute().actionGet();

            if (response != null) {

                for (SearchHit hit : response.getHits()) {

                    try {

                        tags = hit.getSource().get("tags").toString().split(",");
                        temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()),
                                hit.getSource().get("title").toString(),
                                hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()),
                                hit.getSource().get("author").toString(), tags);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    articleList.add(temporary);
                }
            }

            collectionSort();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }
    }

queryString, which uses query parser in order to parse content, allows you to specify the field where queryString will be operated, and it also allows you to use wildcard. For QueryString, the field is index.query.default_field and "_all" is set by default, and the query string as such is operated in all fields. "*" wildcard refers to any character string, including empty ones. 

9. Demo application

Application: ElasticSearch
Published at DZone with permission of its author, Hüseyin Akdoğan. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)