Raising R from standalone to Web application, finally one tutorial for doing it.
In the previous tutorial, we saw how to install R on a server and how to generate good quality graphs. Today, we'll go one step further by learning how to call our R server from a Java web application, which is said to be technically challenging.
Up to day, there were several tries and methods to provide easy access to R from web applications, and they are listed in the R FAQ. For those seeking a framework, you might like to see Shiny from RStudio. However, if you enjoy putting the hands on and building your own Java code, this tutorial will teach how to reach this goal with simple and reusable technology.
At the end of this tutorial, we will have the same result as in the previous one, a nice pie-chart, served by a production-ready web application hosted on Tomcat.
The first step is extending R with the rJava package. It's so easy :
# wget https://cran.r-project.org/src/contrib/rJava_0.9-7.tar.gz # R CMD INSTALL rJava_0.9-7.tar.gz # R CMD javareconf
In order to make our Tomcat use R, we need to build the R accessibility. We do this this by adding the library package (jar file), and more uncommon to Tomcat developers, setting system environment variables. In the Tomcat setenv.sh file, add this :
LD_LIBRARY_PATH=/usr/local/lib64/R/library/rJava/jri/:/usr/local/lib64/R/lib/ export LD_LIBRARY_PATH R_HOME=/usr/local/lib64/R export R_HOME
In the Tomcat lib directory, add this file :
JRI.jar
Before starting to code anyhting, we must know that the R doesn't support multi-threading as explained in the Java Rengine class documentation. As we are going to make individual calls to R, we must implement a singleton to avoid threading problems.
package com.river_tiger.rdemo; import org.rosuda.JRI.Rengine; public class rInterfaceHL { private static rInterfaceHL theInstance; private final Rengine engine; private rInterfaceHL(Rengine engine) { this.engine = engine; this.engine.idleDelay = 500; Rengine.DEBUG = 10; } public static synchronized rInterfaceHL getInstance() { if (theInstance == null) rInterfaceHL.initialize(""); return theInstance; } public static synchronized void initialize(String loopback) { if (theInstance != null) throw new IllegalStateException("already initialized"); theInstance = new rInterfaceHL( new Rengine( new String[] {"--vanilla"}, false, null ) ); } /* wrapper methods for Rengine */ public synchronized long getVersion() { return Rengine.getVersion(); } public synchronized void eval(String s) { engine.eval(s); } public static synchronized Rengine getMainEngine() { return rInterfaceHL.getMainEngine(); } public static synchronized boolean waitForR() { return rInterfaceHL.waitForR(); } public synchronized void destroy() { engine.end(); } }
We also need some classes to package the graphs building, and it will make things easier later. In order to have light and readable code, we implement a Facade pattern.
Of course, the graphs will be stored as files, and we must use a parameter ('fileout')
rGraphScript.java package com.river_tiger.rdemo; public class rGraphScript { private String rGraphTitle = null; private String[] rGraphScript = null; public rGraphScript(String title, String[] script) { this.rGraphTitle = title; this.rGraphScript = script; } public String getrGraphTitle() { return rGraphTitle; } public String[] getrGraphScript() { return rGraphScript; } } rGraphScriptProducer.java package com.river_tiger.rdemo; import java.util.HashMap; import java.util.Iterator; import java.util.Vector; /* * this class packages the graphs making */ public class rGraphScriptProducer { /* * produces a pie chart of the Top 5 Champions League clubs */ public static rGraphScript getTop5PieChart(String fileout, int[] p) { Vectorv = new Vector (); v.add( "library(Cairo)" ); v.add( "CairoFonts(regular='Verdana:style=regular', bold='Verdana:style=bold')" ); v.add( "Cairo('" + fileout + "', type='jpeg', quality=100, width=500, height=500)" ); v.add( "slices < c(10, 7, 5, 5, 5)" ); v.add( "lbls < c('Real Madrid 10', 'Milano 7', 'Bayern Munich 5', 'Barcelona 5', 'Liverpool 5')" ); v.add( "pie(slices, labels = lbls, main='Champions League top 5', clockwise=TRUE)" ); v.add( "dev.off()" ); return new rGraphScript("Champions League top 5", (String[])v.toArray(new String[0])); } }
Of course, we need a Servlet for handling client requests, and we associate it to a JSP for the rendering. This is pretty easy, just remember that we need to know where graphs are stored.
RServlet.java package com.river_tiger.rdemo; import java.io.IOException; import java.util.HashMap; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.river_tiger.rdemo.rGraphScript; import com.river_tiger.rdemo.rGraphScriptProducer; import com.river_tiger.rdemo.rInterfaceHL; public class RServlet extends HttpServlet { private static rInterfaceHL re = null; private static String R_OUT_FSHOME = null; private static String R_OUT_RDIR = null; /* * initializes the R singleton, * reads the context parameters */ public void init(ServletConfig config) throws ServletException { re = rInterfaceHL.getInstance(); R_OUT_FSHOME = config.getInitParameter("R_OUT_FSHOME"); R_OUT_RDIR = config.getInitParameter("R_OUT_RDIR"); System.out.println("R_OUT_FSHOME:" + R_OUT_FSHOME); System.out.println("R_OUT_RDIR:" + R_OUT_RDIR); } public void destroy() { if(re != null) re.destroy(); } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { int[] top5Champions = new int[]{}; /* * generate R script */ String fileName = "example.jpg"; String rFileOut = R_OUT_FSHOME + R_OUT_RDIR + "/" + fileName; rGraphScript rScript = rGraphScriptProducer.getTop5PieChart(rFileOut, top5Champions); req.setAttribute("R_outfile", R_OUT_RDIR + "/" + fileName); req.setAttribute("R_title", rScript.getrGraphTitle()); /* * generate R image */ for(String rLine : rScript.getrGraphScript()) re.eval(rLine); // send to JSP req.getRequestDispatcher("/render.jsp").forward(req, res); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { this.doGet(req, res); } } render.jsp <html> <head> <title>DASHBOARD</title> </head> <body> <img src="<%=(String)request.getAttribute("R_outfile")%>" alt="<%=(String)request.getAttribute("R_title")%>"> </body> </html>
As for any Java web application, we need a deployment descriptor (web.xml). This is the glue between the application server (Tomcat) and the Java code. Here it is :
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <display-name>rdemo Application</display-name> <description> A scriptable management web application for the Tomcat Web Server; Manager lets you view, load/unload/etc particular web applications. </description> <servlet> <servlet-name>rdemo</servlet-name> <servlet-class>com.river_tiger.rdemo.RServlet</servlet-class> <init-param> <param-name>R_OUT_FSHOME</param-name> <param-value>${catalina.base}/webapps</param-value> </init-param> <init-param> <param-name>R_OUT_RDIR</param-name> <param-value>/rdemo/R_produced</param-value> </init-param> </servlet> <!-- Define the Manager Servlet Mapping --> <servlet-mapping> <servlet-name>rdemo</servlet-name> <url-pattern>/rdemo</url-pattern> </servlet-mapping> </web-app>
Once all this is done, you will get your graphs produced by the web application and stored in the R_produced directory of your webapp.
You can download all the source code from the attachment and use it for any purpose.
Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer