Feeds:
Posts
Comments

Autocomplete or word completion

is a feature that presents predictive matching words as you type. This feature saves time, and reduces spelling errors, because you don’t have to type the whole word and can instead select from a list of possible choices.

This demo will show you how to add HTML autocomplete to your Java Web application using JPA, Hibernate, REST, CXF, JAXB, jQuery, Spring, JSON and Maven. I have included download links to the source code and WAR file. The application assumes that you have created a MySQL database called DEMO_DB; the database table is created automatically (see persistence.xml) when the application is deployed. MySQL isn’t required but to select an alternative database you will have to change the database URL and driver class name in applicationContext.xml, the hibernate dialect in persistence.xml and the driver dependency in pom.xml.

language1language2blanguage3

index.html

We begin with a Web page. This page imports jQuery and jQueryUI libraries. The page also has two JavaScript functions, an anonymous function that provides the autocomplete functionality and another called addData. The autocomplete function is instantiated on page load and bound to the form element “language”. The jQuery autocomplete function creates an event handler for the “language” element that is invoked upon every keypress, passing the typed characters to the REST service’s getLanguages(root) method, URL(“/languages”). The addData function provides a jQuery form post that allows the user to add new data by calling the service’s addLanguage(language) method, URL(“/language”).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type"
        content="text/html; charset=UTF-8" />
  <link rel="stylesheet"
      href="http://code.jquery.com/ui/1.9.0/themes/base/jquery-ui.css" />

  <script src="http://code.jquery.com/jquery-1.8.2.js"
          type="text/javascript"></script>
  <script src="http://code.jquery.com/ui/1.9.0/jquery-ui.js"
          type="text/javascript"></script>

<script type="text/javascript">

function addData(method, data) {
  $.ajax({
    type: 'POST',
    contentType: 'application/json',
    url: "http://localhost:8080/jsonDemo-1.1.0/services/jsonDemo/" + method,
    dataType: "json",
    data: data,
    success: function(data, textStatus, jqXHR){
    },
    error: function(jqXHR, textStatus, errorThrown){
    }
  });
}

$(function() {
  $("#language").autocomplete({
    source: function( request, response ) {
      $.ajax({
        url: "http://localhost:8080/jsonDemo-1.1.0/services/jsonDemo/languages",
        dataType: "json",
        data: {
          root: request.term
        },
         success: function( data ) {
           response( $.map( data.language, function( item ) {
             return {
               label: item.value,
               value: item.value
           }
         }));
        }
      });
    },
    minLength: 1
  });
});
</script>
</head>
<body>

<form id="languageForm">
  <div class="ui-widget">
    <label for="language">Language: </label>
    <input id="language" />
      <button id="btnLanguageSave">Save</button>
  </div>
</form>

<script type="text/javascript">

$('#btnLanguageSave').click(function() {
  var data =
    JSON.stringify({"language":{"value":$('#language').val()}});
  addData("language", data);
  return false;
});

</script>

</body>
</html>

applicationContext.xml

This is our spring application context file. Here is where we define the data source, reference the persistence unit, define the entity manager, transaction manager, service bean and expose the CXF REST service interface. By defining the objects in the application context file Spring will instantiate them and place then in the application context so that we can reference them for dependency injection.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://cxf.apache.org/core
    http://cxf.apache.org/schemas/core.xsd
    http://cxf.apache.org/jaxrs
    http://cxf.apache.org/schemas/jaxrs.xsd">

  <bean id="dataSource"
      class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName"
              value="com.mysql.jdbc.Driver"/>
    <property name="url"
              value="jdbc:mysql://localhost/DEMO_DB?useUnicode=true"/>
    <property name="username" value="root"/>
    <property name="password" value="blaze"/>
    <property name="validationQuery"
              value="select 1 from mysql.user"/>
  </bean>

  <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName"
              value="myPersistenceUnit"/>
    <property name="persistenceXmlLocation"
              value="classpath*:META-INF/persistence.xml"/>
    <property name="dataSource" ref="dataSource"/>
  </bean>

  <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory"
              ref="entityManagerFactory"/>
  </bean>

  <!-- autowired support -->
  <context:component-scan base-package="com.johnderinger"/>

  <!-- transaction support -->
  <tx:annotation-driven />

  <bean id="jsonDemoService"
        class="com.johnderinger.service.JsonDemoService"/>

  <jaxrs:server id="jsonDemo" address="/jsonDemo">
    <jaxrs:serviceBeans>
      <ref bean="jsonDemoService" />
    </jaxrs:serviceBeans>

    <jaxrs:features>
      <cxf:logging/>
    </jaxrs:features>
  </jaxrs:server>

</beans>

persistence.xml

The persistence.xml file is a standard configuration file in JPA. It has to be included in the META-INF directory inside the JAR file that contains the entity beans. The persistence.xml file must define a persistence-unit with a unique name in the current scoped classloader. The provider attribute specifies the underlying implementation of the JPA EntityManager.[1] Note that this information can now be defined in Java code. Our provider, i.e. JPA implementation, is Hibernate. Here we can specify Hibernate specific properties including the hibernate.dialect, “MySQLInnoDBDialect”. Note the property hibernate.hbm2ddl.auto, here we have a value of “create-drop” which means that our database table LANGUAGE will be dropped and re-created on re-deployment. “create-drop” is good for development and testing but once your entities are stable you will want to change this value to “validate”.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="1.0">

  <persistence-unit
      name="myPersistenceUnit"
      transaction-type="RESOURCE_LOCAL">

    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>com.johnderinger.persistence.Language</class>

<!--
hibernate.hbm2ddl.auto: validate | update | create | create-drop
-->
    <properties>
      <property name="hibernate.dialect"
                value="org.hibernate.dialect.MySQLInnoDBDialect"/>
      <property name="hibernate.connection.driver_class"
                value="com.mysql.jdbc.Driver"/>
      <property name="hibernate.connection.autoReconnect"
                value="true"/>
      <property name="hibernate.hbm2ddl.auto"
                value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
    </properties>

  </persistence-unit>
</persistence>

Language.java

This is our entity bean, it is mapped to the database table LANGUAGE. @XmlRootElement; this annotation is required to serialize our entity to XML or JSON. @Entity specifies that this is an entity bean. @Table maps the entity to the LANGUAGE table and defines a unique constraint for the VALUE column, this ensures that we will not have any duplicate values in our table. @Configurable exposes this bean for dependency injection. In this class I have implemented the Comparable interface and the compareTo method, this is not required but provides the ability to add this entity to a Java Set. On the getValue() method we have defined two attributes, @Basic which simply defines the the method as a persistence method/column and is optional. If not provided JPA will assume @Basic. The other annotation is @Column, here we are mapping our entity method to a database table column and are specifying it’s length and that it is not nullable. Since the attribute is a String Hibernate will generate a VARCHAR data type for the database column, this can be overridden to map to nearly any type that your database supports. Now the getId() method, this is our primary key. @Id defines the method/attribute as the primary key column and @GeneratedValue specifies that MySQL will generate a unique value whenever a new row is inserted into the database. Note that the underling database must support auto generation of keys to use this JPA strategy.

package com.johnderinger.persistence;

import javax.persistence.*;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;

@XmlRootElement
@Entity(name = "Language")
@Table(name = "LANGUAGE", uniqueConstraints = {
    @UniqueConstraint(columnNames = {
        "VALUE"
    })
})
@Configurable
public class Language
    implements Serializable, Comparable<Language> {

  protected String value;
  protected Long id;

  /**
   * Retrieve the language String value
   * @return A String containing the language value
   */
  @Basic
  @Column(name = "VALUE", length = 100, nullable = false)
  public String getValue() {
    return value;
  }

  /**
   * Set the language String value
   * @param A String containing the language value
   */
  public void setValue(String value) {
    this.value = value;
  }

  /**
   * Primary key
   * @return A auto-generated primary key of type Long
   */
  @Id
  @Column(name = "LANGUAGE_ID", scale = 0)
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Language language = (Language) o;

    if (value != null ? !value.equals(language.value) :
                                      language.value != null) {
      return false;
    }

    return true;
  }

  @Override
  public int hashCode() {
    return value != null ? value.hashCode() : 0;
  }

  @Override
  public int compareTo(Language language) {
    return getValue().compareTo(language.getValue());
  }

}

JsonDemoDao.java

The DAO interface, defining our data access methods.

package com.johnderinger.persistence;

import java.util.List;

public interface JsonDemoDao {

  /**
   * Retrieve all language entity objects
   * @return A List of Language entities
   */
  List<Language> getAllLanguages();

  /**
   * Retrieve language entities that begin with the root argument
   * @param root A String used to search for language values
   * @return A List of Language entities
   */
  List<Language> findLanguages(String root);

  /**
   * Add a new language entity
   * @param language A Language entity to persist
   * @return The newly created Language entity
   */
  Language addLanguage(Language language);

  /**
   * Find a language entity whose value equals the name argument
   * @param A String containing the value for search
   * @return The found Language entity, or null
   */
  Language findLanguageByName(String name);
}

JsonDemoDaoImpl.java

The DAO class that contains the queries and specifies transaction demarcation.
@Repository(“jsonDemoDao”) exposes the DAO class for dependency injection and identifies the class as a Data Access Object. @Transactional(readOnly = true) assigns a default propagation for all methods in the class as ‘read only’. @PersistenceContext injects the entityManager defined in applicationContext.xml, with the values defined in persistence.xml, into setEntityManager(). On the addLanguage(…) method, @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) overrides the default transaction propagation and demarcates the method as read/write. If there is an exception persisting the data the transaction will be rolled-back.

package com.johnderinger.persistence;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository("jsonDemoDao")
@Transactional(readOnly = true)
public class JsonDemoDaoImpl implements JsonDemoDao {

  private EntityManager entityManager;
  private Logger logger =
              LoggerFactory.getLogger(this.getClass());

  @PersistenceContext
  public void setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

 /**
  * @inheritDoc
  */
  @Override
  @SuppressWarnings("unchecked")
  public List<Language> getAllLanguages() {
    logger.info("Retrieving all languages");
    List<Language> languages;
    try {
      languages =
        entityManager
          .createQuery("select o from Language o")
            .getResultList();

        logger.info("Found [" +
          (languages != null ? languages.size() : 0) +
            "] languages");
    } catch (Exception e) {
      throw new RuntimeException("Error retrieving Language", e);
    }
    return languages;
  }

 /**
  * @inheritDoc
  */
  @Override
  @SuppressWarnings("unchecked")
  public List<Language> findLanguages(String root) {
    logger.info(
        "Retrieving all languages using root [" + root + "]");
    List<Language> languages = new ArrayList<Language>();
    if (root != null) {
      try {
        languages =
          entityManager.createQuery(
            "select o from Language o where o.value like ?")
                   .setParameter(1, root + "%").getResultList();

        logger.info("Found [" +
          (languages != null ? languages.size() : 0) +
            "] languages");
      } catch (Exception e) {
        throw new RuntimeException(
          "Error retrieving Language", e);
      }
    } else {
     logger.warn(
       "Unable to findLanguages using value [" + root + "]");
    }
    return languages;
  }

 /**
  * @inheritDoc
  */
  @Override
  @Transactional(readOnly = false,
                 propagation = Propagation.REQUIRES_NEW)
  public Language addLanguage(Language language) {
    logger.info("Add language [" +
      (language != null ? language.getValue() : null) + "]");
    if (language != null && language.getValue() != null) {
      entityManager.merge(language);
      language = findLanguageByName(language.getValue());
    } else {
      logger.warn("Unable to add language will null value ["
        + (language != null ? language.getValue() : null) + "]");
    }
    return language;
  }

 /**
  * @inheritDoc
  */
  @Override
  public Language findLanguageByName(String name) {
    logger.info("find language [" + name + "]");
    Language language = null;
    if (name != null) {
      try {
        @SuppressWarnings("unchecked")
        List<Language>languages =
          entityManager.createQuery(
            "select o from Language o where o.value = ?")
              .setParameter(1, name).getResultList();
        if (languages != null && !languages.isEmpty()) {
          language = languages.get(0);
        } else {
          logger.debug("Unable to find language [" + name + "]");
        }
      } catch (Exception e) {
        throw new RuntimeException(
                  "Error retrieving Language", e);
      }
    } else {
      logger.warn(
        "Unable to find language using value [" + name + "]");
    }
    return language;
  }
}

JsonDemoRestInterface.java

This is the service interface, which is exposed as a “Service” in applicationContext.xml. @GET; this annotation specifies that the HTTP request for the method must be an HTTP GET. @Path(“/…”) specifies the relative URL path used to access the method. @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) specifies the media type of the HTTP response, in this case JSON. @POST; specifies that the HTTP request for the method must be an HTTP POST. @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) specifies the media type of the HTTP request, in this case JSON.

package com.johnderinger.service;

import com.johnderinger.persistence.Language;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;

/**
 * @author <a href="mailto:john.deringer@sri.com">John Deringer</a>
 *         Date: 8/28/12
 */
public interface JsonDemoRestInterface {

  /**
   * Retrieve all languages
   * @return A List of Language objects
   */
  @GET
  @Path("/allLanguages")
  @Produces({ MediaType.APPLICATION_JSON,
              MediaType.APPLICATION_XML })
  List<Language> getLanguages();

  /**
   * Retrieve all Languages that match the 'LIKE*' comparison
   * @param root A String to use for a 'LIKE%' search
   * @return A List of Language objects
   */
  @GET
  @Path("/languages")
  @Produces({ MediaType.APPLICATION_JSON,
              MediaType.APPLICATION_XML })
  List<Language> getLanguages(@QueryParam("root") String root);

  /**
   * Add a new Language entity
   * @param language, Populate the value attribute, id will be populated by the database
   * @return The newly created Language Entity
   */
  @POST
  @Path("/language")
  @Consumes({ MediaType.APPLICATION_JSON,
              MediaType.APPLICATION_XML })
  @Produces({ MediaType.APPLICATION_JSON,
              MediaType.APPLICATION_XML })
  Language addLanguage(Language language);
}

JsonDemoService.java

The service implementation class. @Autowired uses dependency injection to set an instance of the JsonDemoDao class into the JsonDemoService.

package com.johnderinger.service;

import com.johnderinger.persistence.JsonDemoDao;
import com.johnderinger.persistence.JsonDemoDaoImpl;
import com.johnderinger.persistence.Language;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class JsonDemoService implements JsonDemoRestInterface {

  @Autowired
  private JsonDemoDao jsonDemoDao;

 /**
  * @inheritDoc
  */
  @Override
  public List<Language> getLanguages() {
    return jsonDemoDao.getAllLanguages();
  }

 /**
  * @inheritDoc
  */
  @Override
  public List<Language> getLanguages(String root) {
    return jsonDemoDao.findLanguages(root);
  }

 /**
  * @inheritDoc
  */
  @Override
  public Language addLanguage(Language language) {
    return jsonDemoDao.addLanguage(language);
  }
}

pom.xml

Maven; this is the build file that defines the packaging type as “war” and list the project dependencies. Maven will use the list of dependencies to import 3rd party libraries into the project’s, maven generated, target directory.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.johnderinger</groupId>
    <artifactId>jsonDemo</artifactId>
    <version>1.1.0</version>
    <packaging>war</packaging>
    <name>jsonDemo</name>
    <description>jsonDemo</description>

    <properties>
        <basedir>.</basedir>
        <java.version>1.7</java.version>
        <spring.version>3.1.0.RELEASE</spring.version>
        <dbcp.version>1.4</dbcp.version>
        <mysql-connector.version>5.1.6</mysql-connector.version>
        <hibernate.jpa.version>1.0.0.Final</hibernate.jpa.version>
        <hibernate-entitymanager.version>
            3.6.0.CR2
        </hibernate-entitymanager.version>
        <jaxb2.version>2.1</jaxb2.version>
        <cxf.version>2.5.2</cxf.version>
        <commons-lang.version>2.6</commons-lang.version>
        <slf4j.version>1.6.1</slf4j.version>
        <log4j.version>1.2.16</log4j.version>
        <intellij.annotations.version>9.04</intellij.annotations.version>
        
    </properties>

    <dependencies>

        <!-- CXF -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Persistence -->
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>${hibernate.jpa.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate-entitymanager.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>${dbcp.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>${commons-lang.version}</version>
        </dependency>

        <!-- JAXB -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>${jaxb2.version}</version>
        </dependency>

        <!-- logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>
</project>

References

(1) JBoss The persistence.xml file

Download

Source

WAR