Kohei Nozaki's blog 

Entries tagged [javaee]

jQuery DataTables with JAX-RS


Posted on Monday Mar 24, 2014 at 05:21PM in Technology


  • jQuery DataTables plugin can delegate some data processing to server-side with interaction with JSON[2].
    • It's efficient way when we have to go with large data sets.
  • So I have tried it with JAX-RS.

Environment

  • DataTables 1.9.4
  • WildFly 8.0.0.Final
  • Oracle JDK8
  • PostgreSQL 9.2.4

Sample data

  • In this example, we use some tables on PostgreSQL as the data source.
    • These tables are parts of the job repository of jBeret (jBatch implementation of WildFly).
  • We will execute a SQL which returns data set like this:
 jobexecutionid | jobname |        starttime        |         endtime         | batchstatus 
----------------+---------+-------------------------+-------------------------+-------------
           2167 | myjob   | 2014-03-19 14:31:13.343 |                         | STARTING
           2166 | myjob   | 2014-03-19 14:14:38.158 | 2014-03-19 14:14:59.388 | FAILED
           2165 | myjob   | 2014-03-19 14:13:24.104 | 2014-03-19 14:14:25.59  | STOPPED
           2164 | myjob   | 2014-03-19 14:11:32.238 |                         | STARTING
           2163 | myjob   | 2014-03-19 14:03:41.07  | 2014-03-19 14:10:29.391 | STOPPED
           2162 | myjob   | 2014-03-19 13:54:41.017 | 2014-03-19 13:56:55.365 | STOPPED
           2161 | myjob   | 2014-03-19 13:54:26.902 | 2014-03-19 13:54:38.077 | STOPPED
           2160 | myjob   | 2014-03-19 13:53:49.291 | 2014-03-19 13:54:22.496 | STOPPED
(8 rows)

jbatch=# 

Resources

JobExecutionReportService.java

  • Make sure a datasource named “java:jboss/jdbc/JBatchDS” configured correctly on the application server.
package org.nailedtothex.jaxrs_datatables;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.sql.DataSource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.sql.*;
import java.text.Format;
import java.text.MessageFormat;
import java.util.*;

@Stateless
@Path("/JobExecutionReportService")
public class JobExecutionReportService {
    private static final String SQL_COUNT = "SELECT COUNT(1) FROM job_execution";
    private static final String SQL_FETCH = "SELECT\n" +
            "    e.jobexecutionid,\n" +
            "    i.jobname,\n" +
            "    e.starttime,\n" +
            "    e.endtime,\n" +
            "    e.batchstatus\n" +
            "FROM\n" +
            "    job_execution e\n" +
            "    LEFT JOIN job_instance i ON e.jobinstanceid = i.jobinstanceid\n" +
            "ORDER BY\n" +
            "    e.jobexecutionid {0}\n" +
            "LIMIT ?\n" +
            "OFFSET ?";
    private static final Set<String> VALID_SORT_DIR = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("asc", "desc")));

    @Resource(lookup = "java:jboss/jdbc/JBatchDS")
    private DataSource ds;
    private Format sqlFormat;

    @PostConstruct
    protected void init() {
        sqlFormat = new MessageFormat(SQL_FETCH);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public DataTablesBean getJobExecutionReport(
            @QueryParam("sEcho") Integer sEcho,
            @QueryParam("iDisplayLength") Integer iDisplayLength,
            @QueryParam("iDisplayStart") Integer iDisplayStart,
            @QueryParam("sSortDir_0") String sSortDir_0) throws SQLException {

        final Long count;
        try (Connection cn = ds.getConnection();
             Statement st = cn.createStatement();
             ResultSet rs = st.executeQuery(SQL_COUNT);) {
            rs.next();
            count = rs.getLong(1);
        }

        if (sSortDir_0 == null || !VALID_SORT_DIR.contains(sSortDir_0)) {
            throw new IllegalArgumentException(sSortDir_0);
        }

        final String sql = sqlFormat.format(new Object[]{sSortDir_0});
        final List<List<String>> aaData = new ArrayList<>();

        try (Connection cn = ds.getConnection();
             PreparedStatement ps = cn.prepareStatement(sql)) {
            ps.setInt(1, iDisplayLength);
            ps.setInt(2, iDisplayStart);
            try (ResultSet rs = ps.executeQuery()) {
                final int columns = rs.getMetaData().getColumnCount();
                while (rs.next()) {
                    List<String> data = new ArrayList<>(columns);
                    for (int i = 1; i <= columns; i++) {
                        data.add(rs.getString(i));
                    }
                    aaData.add(data);
                }
            }
        }

        final DataTablesBean bean = new DataTablesBean();
        bean.setsEcho(sEcho);
        bean.setiTotalDisplayRecords(String.valueOf(count));
        bean.setiTotalRecords(String.valueOf(count));
        bean.setAaData(aaData);
        return bean;
    }

}

DataTablesBean.java

package org.nailedtothex.jaxrs_datatables;

import java.util.List;

public class DataTablesBean {
    private Integer sEcho;
    private String iTotalRecords;
    private String iTotalDisplayRecords;
    private List<List<String>> aaData;

    public String getiTotalRecords() {
        return iTotalRecords;
    }

    public void setiTotalRecords(String iTotalRecords) {
        this.iTotalRecords = iTotalRecords;
    }

    public String getiTotalDisplayRecords() {
        return iTotalDisplayRecords;
    }

    public void setiTotalDisplayRecords(String iTotalDisplayRecords) {
        this.iTotalDisplayRecords = iTotalDisplayRecords;
    }

    public List<List<String>> getAaData() {
        return aaData;
    }

    public void setAaData(List<List<String>> aaData) {
        this.aaData = aaData;
    }

    public Integer getsEcho() {
        return sEcho;
    }

    public void setsEcho(Integer sEcho) {
        this.sEcho = sEcho;
    }
}

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css"
          href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css">
    <script type="text/javascript" charset="utf8"
            src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
    <script type="text/javascript" charset="utf8"
            src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>
    <script>
        $(document).ready(function () {
            $('#example').dataTable({
                "bFilter": false,
                "sPaginationType": "full_numbers",
                "aaSorting": [
                    [ 0, "desc" ]
                ],
                "aoColumns": [
                    { "bSortable": true },
                    { "bSortable": false },
                    { "bSortable": false },
                    { "bSortable": false },
                    { "bSortable": false }
                ],
                "bProcessing": true,
                "bServerSide": true,
                "sAjaxSource": "webapi/JobExecutionReportService"
            });
        });
    </script>
    <title></title>
</head>
<body>
<table id="example">
    <thead>
    <tr>
        <th>id</th>
        <th>jobName</th>
        <th>startTime</th>
        <th>endTime</th>
        <th>batchStatus</th>
    </tr>
    </thead>
    <tbody>
    </tbody>
</table>
</body>
</html>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>org.nailedtothex</groupId>
    <artifactId>jaxrs-datatables</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>
    </dependencies>

</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_3_0.xsd"
         version="3.0">
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

In action

  • Pagination and sorting with id are working correctly.

References

  1. The Java EE 7 Tutorial:Building RESTful Web Services with JAX-RS | Java EE Documentation
  2. DataTables - Usage
  3. DataTables example


Manage application config with Commons Configuration


Posted on Wednesday Feb 26, 2014 at 01:53PM in Technology


Environment

  • Commons Configuration 1.10
  • WildFly 8.0.0.Final
  • Oracle JDK7u51

Why go this way?

Various portable ways are available for manage configurations of the Java EE application. such as:

  • Configuration table in DB
    • I guess it's not bad, but I feel that is bit tedious work to coding.
    • There's a way to use this mechanism via Commons Configuration, but I haven't tried it yet.
  • Custom JNDI resources
    • I thought that it's most efficient at first, but negative points here:
      • Badness of lookup performance.
      • Editing JNDI resources is very annoying.

So sometimes I'm thinking of any better way, and I found Commons Configurations[1]. notable capabilities of Commons Configuration are:

  • Simple API
  • Auto-reloading and saving mechanism provided
  • Support of various formats and placement styles

So I'd try it.

Prepare sample project and related resources

Create a system property

  • Create a system property which specifies the path of configuration file.
  • I'm using WildFly so go with jboss-cli:
[standalone@localhost:9990 /] /system-property=app.config:add(value=/Users/kyle/app.properties)
{"outcome" => "success"}
[standalone@localhost:9990 /] 

Create the properties file

  • Location is same as “app.config” system property.
hoge=fuge

pom.xml

<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>org.nailedtothex</groupId>
    <artifactId>config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.10</version>
        </dependency>
    </dependencies>
</project>

Create an application-scoped bean

package org.nailedtothex.config;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;

@Named
@ApplicationScoped
public class AppConfigBean {

    private Configuration config;

    @PostConstruct
    void init() throws ConfigurationException {
        String path = System.getProperty("app.config");
        PropertiesConfiguration propConfig = new PropertiesConfiguration(path); 
        propConfig.setReloadingStrategy(new FileChangedReloadingStrategy());
        config = propConfig;
    }

    public Configuration getConfig(){
        return config;
    }
}
  • If you want to read the config at startup of application, I recommend that use of @javax.ejb.Singleton and @javax.ejb.Startup[2].

Create a servlet

  • Get a configuration variable from singleton bean.
package org.nailedtothex.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/*")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    @Inject
    private AppConfigBean bean;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try (PrintWriter pw = response.getWriter()) {
            pw.write(bean.getConfig().getString("hoge"));
        }
    }
}

Access via browser

  • It needs only very small and clean code.

Check runtime reloading

Edit app.properties:

kyle-no-MacBook:~ kyle$ echo hoge=FUGE > app.properties
kyle-no-MacBook:~ kyle$ 

Reload browser:

  • I just reloaded the browser and I didn't do any other operations, but config was updated immediately.

Collaboration with CDI

  • There's a way to inject properties to CDI managed bean at [2]. that must be useful.

XMLConfiguration

  • XMLConfiguration is good for hierarchical configuration.
  • It depended on Commons Collection so don't forget to add the dependency.

pom.xml

        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.10</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

XmlTest.java

package org.nailedtothex.config;

import java.util.Iterator;
import java.util.List;

import org.apache.commons.configuration.XMLConfiguration;

public class XmlTest {

    public static void main(String[] args) throws Exception {
        XMLConfiguration config = new XMLConfiguration();
        config.load(XmlTest.class.getResourceAsStream("/config.xml"));

        String backColor = config.getString("colors.background");
        String textColor = config.getString("colors.text");
        String linkNormal = config.getString("colors.link[@normal]");
        String defColor = config.getString("colors.default");
        int rowsPerPage = config.getInt("rowsPerPage");
        List<Object> buttons = config.getList("buttons.name");

        System.out.println(backColor);
        System.out.println(textColor);
        System.out.println(linkNormal);
        System.out.println(defColor);
        System.out.println(rowsPerPage);
        System.out.println(buttons);

        System.out.println();

        Object o;
        Iterator it = config.getKeys("colors.");
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

config.xml

<?xml version="1.0" encoding="UTF-8"?>
<gui-definition>
  <colors>
    <background>#808080</background>
    <text>#000000</text>
    <header>#008000</header>
    <link normal="#000080" visited="#800080"/>
    <default>${colors.header}</default>
  </colors>
  <rowsPerPage>15</rowsPerPage>
  <buttons>
    <name>OK,Cancel,Help</name>
  </buttons>
  <numberFormat pattern="###\,###.##"/>
</gui-definition>

Log

#808080
#000000
#000080
#008000
15
[OK, Cancel, Help]

colors.background
colors.text
colors.header
colors.link[@normal]
colors.link[@visited]
colors.default

References

  1. Commons Configuration - Java Configuration API
  2. CDIでアプリケーション設定をインジェクション - 見習いプログラミング日記
  3. Apache commonsが便利な件(commons-configuration編) - 都元ダイスケ IT-PRESS
  4. Commons Configuration - Hierarchical configurations and XML Howto