How to integrate Xlloop within JAVA web server? - java

I am trying to integrate XLLoop from a servlet and trying to run in via HTTP protocol. Below is my code:
XlloopServlet.java
#WebServlet(value = "/FunctionServer", name = "FunctionServer", asyncSupported = true)
public class XlloopServlet extends FunctionServlet {
private static final long serialVersionUID = -3845895326255874126L;
#Override
public void init(final ServletConfig config) throws ServletException {
// Create a function information handler to register our functions
FunctionInformationHandler infoHandler = new FunctionInformationHandler();
// Create a reflection function handler and add the required methods
FunctionHandler handler = new FunctionHandler();
infoHandler.add(handler.getFunctions());
// Set the handlers
CompositeFunctionHandler compositeHandler = new CompositeFunctionHandler();
compositeHandler.add(handler);
compositeHandler.add(infoHandler);
// Setting the function handler in the parent servlet
setHandler(compositeHandler);
}
and my FunctionHandler class which registers the functions:
public class FunctionHandler implements IFunctionHandler, FunctionProvider {
private ReflectFunctionHandler rfh;
public FunctionHandler() {
// Create a reflection function handler and add the Math methods
rfh = new ReflectFunctionHandler();
rfh.addMethods("Math.", Math.class);
rfh.addMethods("Math.", Maths.class);
rfh.addMethods("CSV.", CSV.class);
rfh.addMethods("Reflect.", Reflect.class);
}
#Override
public XLoper execute(IFunctionContext arg0, String arg1, XLoper[] arg2) throws RequestException {
return rfh.execute(arg0, arg1, arg2);
}
#Override
public boolean hasFunction(String arg0) {
return rfh.hasFunction(arg0);
}
#Override
public FunctionInformation[] getFunctions() {
return rfh.getFunctions();
}
public ReflectFunctionHandler getReflectFunctionHandler() {
return rfh;
}
}
My XLLoop ini file is as below:
protocol=http
url=http://localhost:8080/MyApp/FunctionServer
Now, when I try to call a function from my excel, I get a call in the servlet class and everything executes, but functions are not getting executed on the excel file.
Anyone having any idea about how to integrate XLLoop plugin on a webserver like tomcat?

I've just implemented this with JAX-RS and a bit of Spring. I use a REST endpoint to populate the xlloop.ini file with the correct server host/port for the running service and then package up my xlsb, xll and ini file in a zip for clients to download. It's not particularly pretty at the moment but the web.xml and Startup snippets are below.
The thing I haven't spent time on yet is memory management. If a lot of users load a lot of data, I'll need to periodically clean that up, so beware of idle session threads!
Web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>com.myapp.web.excel.XLLoopStartup</listener-class>
</listener>
XLLoopStartup.java
public class XLLoopStartup implements ServletContextListener {
public static XLLoopStartup INSTANCE;
private FunctionServer fs;
#Inject
private SomeInjectionThing usefulSpringStuff;
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
#Override
public void contextInitialized(ServletContextEvent sce) {
INSTANCE = this;
// Initialize my Spring stuff
if (sce != null){
WebApplicationContextUtils//
.getRequiredWebApplicationContext(sce.getServletContext())//
.getAutowireCapableBeanFactory()//
.autowireBean(this);
}
Executors.newSingleThreadExecutor().execute(new Runnable() {
#Override
public void run() {
registerConverters();
fs = new FunctionServer(Integer.parseInt(System.getProperty("port.tomcat.xlloop", "10606")));
ReflectFunctionHandler rfh = new ReflectFunctionHandler();
rfh.addMethods(ExcelTrades.CATEGORY, ExcelTrades.class);
rfh.addMethods(ExcelUtils.CATEGORY, ExcelUtils.class);
rfh.addMethods(ExcelPositions.CATEGORY, ExcelPositions.class);
rfh.addMethods(ExcelProducts.CATEGORY, ExcelProducts.class);
// Create a function information handler to register our functions
FunctionInformationHandler firh = new FunctionInformationHandler();
firh.add(rfh.getFunctions());
// Set the handlers
CompositeFunctionHandler cfh = new CompositeFunctionHandler();
cfh.add(rfh);
cfh.add(firh);
DebugFunctionHandler debugFunctionHandler = new DebugFunctionHandler(cfh);
fs.setFunctionHandler(new SecureFunctionHandler(debugFunctionHandler));
try {
fs.run();
}
catch (IOException e) {
e.printStackTrace();
}
}
});
}
// For quick testing
public static void main(String[] args) {
new XLLoopStartup().contextInitialized(null);
}
// Function classes can statically access this instance and get spring things from it
public SomeInjectionThing getThing() {
return usefulSpringStuff;
}
}
ExcelService.java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.io.IOUtils;
import io.swagger.annotations.Api;
#Api("excel")
#Path("/excel")
public class ExcelService {
#Context
UriInfo uri;
#GET
#Path("/download")
#Produces({ MediaType.APPLICATION_OCTET_STREAM })
public Response download() {
StringWriter sw = new StringWriter();
// Create an INI file. We should probably store all default settings in a file and just add the server info to
// it.
sw.write("server=");
sw.write(uri.getBaseUri().getHost());
sw.write(":");
sw.write(System.getProperty("port.tomcat.xlloop", "10605"));
String inifile = sw.toString();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
// Add the ini file to the zip
ZipEntry entry = new ZipEntry("xlloop.ini");
zos.putNextEntry(entry);
zos.write(inifile.getBytes());
zos.closeEntry();
// Add the Excel files
writeFileFromClasspath(zos, "xlloop.xll");
// This is my custom Excel macro sheet with other useful functions for user authentication etc.
writeFileFromClasspath(zos, "xlloop.xlsb");
}
catch (IOException ioe) {
ioe.printStackTrace();
}
return Response.ok(new ByteArrayInputStream(baos.toByteArray()))
.header("Content-Disposition", "attachment; filename=xlloop.zip").build();
}
private void writeFileFromClasspath(ZipOutputStream zos, String filename) throws IOException {
ZipEntry xlFileEntry = new ZipEntry(filename);
zos.putNextEntry(xlFileEntry);
zos.write(IOUtils.toByteArray(ExcelService.class.getClassLoader().getResourceAsStream(filename)));
zos.closeEntry();
}
}

Related

How to download zip files from webdav server using sardine?

I am using below java class which uses sardine , i am getting only resources or zip files list in the directory, what should i use to download zip files?
package com.download;
import java.util.List;
import org.mule.api.MuleEventContext;
import org.mule.api.lifecycle.Callable;
import com.github.sardine.DavResource;
import com.github.sardine.Sardine;
import com.github.sardine.SardineFactory;
public class filesdownload implements Callable{
#Override
public Object onCall(MuleEventContext eventContext) throws Exception {
Sardine sardine = SardineFactory.begin("***","***");
List<DavResource> resources = sardine.list("http://hfus.com/vsd");
for (DavResource res : resources)
{
System.out.println(res);
}
return sardine;
}
You need to use sardine.get() method. Method documentation
Don't forget to use absolute path to your file. For example: http://hfus.com/vsd/file.zip.
Code sample:
package com.download;
import java.util.List;
import org.mule.api.MuleEventContext;
import org.mule.api.lifecycle.Callable;
import com.github.sardine.DavResource;
import com.github.sardine.Sardine;
import com.github.sardine.SardineFactory;
//TODO: add missing imports
public class filesdownload implements Callable{
#Override
public Object onCall(MuleEventContext eventContext) throws Exception {
Sardine sardine = SardineFactory.begin("***","***");
List<DavResource> resources = sardine.list(serverUrl()+"/vsd");
for (DavResource res : resources) {
if(res.getName().endsWith(".zip")) {
downloadFile(res);
}
}
return sardine;
}
private void downloadFile(DavResource resource) {
try {
InputStream in = sardine.get(serverUrl()+resource.getPath());
// TODO: handle same file name in subdirectories
OutputStream out = new FileOutputStream(resource.getName());
IOUtils.copy(in, out);
in.close();
out.close();
} catch(IOException ex) {
// TODO: handle exception
}
}
private String serverUrl() {
return "http://hfus.com";
}
}

How can to get content of file from local computer in GXT GWT in server part? (com.sencha.gxt.ui.GXT)

I have GXT GWT project (<inherits name='com.sencha.gxt.ui.GXT'/>). I need upload file from computer and get bytes array of file.
I wrote upload file in client part and all good, but I need added server part with get content of file, How can do it?
my client part:
public class FileUpload implements IsWidget {
private static FileUploadUiBinder fileUploadUiBinder = GWT.create(FileUploadUiBinder.class);
#UiTemplate("FileUpload.ui.xml")
interface FileUploadUiBinder extends UiBinder<Component, FileUpload> {
}
#UiField
FileUploadField uploadedFile;
private FieldLabel label;
#Override
public Widget asWidget() {
if (label == null) {
label = (FieldLabel) fileUploadUiBinder.createAndBindUi(this);
uploadedFile.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent event) {
Info.display("File Changed", "You selected " + uploadedFile.getValue());
}
});
}
return label;
}
}
and FileUpload.ui.xml:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder
xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns="urn:import:com.sencha.gxt.widget.core.client.container"
<form:FieldLabel>
<form:widget>
<form:FileUploadField ui:field="uploadedFile" name="metadataFile"/>
</form:widget>
</form:FieldLabel>
</ui:UiBinder>
so: I need added server part: How can to get bytes array (content of file) in server part? I need a example :) Maybe somebody give me link with working example? Help me, please.
Unfortunately a lot of changes should be done to your code in order to show some example of how file data could be sent to server side. And I should mention that it's only one of possible solutions. Client-server request here goes to HttpServlet rather than through standard GWT RPC though. Here are my changes:
1) Changed FileUpload.ui.xml:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns="urn:import:com.sencha.gxt.widget.core.client.container"
>
<form:FormPanel>
<form:FieldLabel>
<form:widget>
<form:FileUploadField ui:field="uploadedFile"
name="metadataFile" />
</form:widget>
</form:FieldLabel>
</form:FormPanel>
</ui:UiBinder>
2) Changed FileUpload.java:
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.widget.core.client.Component;
import com.sencha.gxt.widget.core.client.event.SubmitCompleteEvent;
import com.sencha.gxt.widget.core.client.form.FileUploadField;
import com.sencha.gxt.widget.core.client.form.FormPanel;
import com.sencha.gxt.widget.core.client.info.Info;
public class FileUpload implements IsWidget {
private static FileUploadUiBinder fileUploadUiBinder = GWT.create(FileUploadUiBinder.class);
#UiTemplate("FileUpload.ui.xml")
interface FileUploadUiBinder extends UiBinder<Component, FileUpload> {
}
#UiField
FileUploadField uploadedFile;
private FormPanel formPanel;
#Override
public Widget asWidget() {
if (formPanel == null) {
formPanel = (FormPanel) fileUploadUiBinder.createAndBindUi(this);
formPanel.setEncoding(FormPanel.Encoding.MULTIPART);
formPanel.setMethod(FormPanel.Method.POST);
formPanel.setAction("file.upload"); // This action should
// point to servlet registered in WEB_INF/web.xml
formPanel.addSubmitCompleteHandler(new SubmitCompleteEvent.SubmitCompleteHandler() {
public void onSubmitComplete(SubmitCompleteEvent event) {
String resultHtml = event.getResults();
Info.display("Upload Response", resultHtml);
}
});
uploadedFile.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent event) {
Info.display("File Changed", "You selected " +
uploadedFile.getValue());
formPanel.submit(); // It's better to do it by
// clicking on submitting button or something.
}
});
}
return formPanel;
}
}
3) web.xml (only added parts, use your own package instead of home.test.uploadtest):
<... other servlet blocks ...>
<servlet>
<servlet-name>fileUploadServlet</servlet-name>
<servlet-class>home.test.uploadtest.server.FileUploadServlet</servlet-class>
</servlet>
<... other servlet-mapping blocks ...>
<servlet-mapping>
<servlet-name>fileUploadServlet</servlet-name>
<url-pattern>/file.upload</url-pattern>
</servlet-mapping>
4) FileUploadServlet.java (mostly dummy example of server-side operation):
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class FileUploadServlet extends HttpServlet {
private static final long serialVersionUID = -1L;
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
File tempDir = new File(System.getProperty("java.io.tmpdir"));
FileItemFactory factory = new DiskFileItemFactory(1000000, tempDir);
ServletFileUpload upload = new ServletFileUpload(factory);
File target = null;
try {
try {
List<?> items = upload.parseRequest(request);
Iterator<?> it = items.iterator();
while (it.hasNext()) {
FileItem item = (FileItem) it.next();
if (item.isFormField()) {
throw new ServletException("Unsupported non-file property [" +
item.getFieldName() + "] with value: " + item.getString());
} else {
target = File.createTempFile("temp_", ".data", tempDir);
item.write(target);
item.delete();
System.out.println("Temp file: " + target.getAbsolutePath());
}
}
} catch (ServletException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException(e);
}
response.getWriter().write("File length: " + target.length());
response.getWriter().close();
} finally {
if (target != null && target.exists())
target.delete();
}
}
}
So as you can see there is a dependency to https://commons.apache.org/proper/commons-fileupload/
PS: I think that because of large size of code I might forget something. In this case just let me know.

Communication Java-Javascript with http requests

Lately i have been trying to make communication between minecraft server (running with Java) and scratch (running with JavaScript).
I have written the code in java already:
package me.yotam180;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Bukkit;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class HttpProcessor {
public MainClass plugin;
public HttpProcessor (MainClass plug) throws IOException {
plugin = plug;
plugin.getLogger().info("CREATED HTTTP PROCESSOR");
HttpServer server = HttpServer.create(new InetSocketAddress(9090), 0);
server.createContext("/pollplayer", new PollPlayerHandler());
server.createContext("/killplayer", new KillPlayerHandler());
plugin.getLogger().info("STARTED HTTTP SERVER");
server.setExecutor(null); // creates a default executor
server.start();
}
static class PollPlayerHandler implements HttpHandler {
#SuppressWarnings("deprecation")
#Override
public void handle(HttpExchange httpExchange) throws IOException {
// TODO Auto-generated method stub
Map <String,String>parms = HttpProcessor.queryToMap(httpExchange.getRequestURI().getQuery());
StringBuilder response = new StringBuilder();
response.append(Bukkit.getPlayer(parms.get("name")).getLocation().toString());
HttpProcessor.writeResponse(httpExchange, response.toString());
}
}
static class KillPlayerHandler implements HttpHandler {
#SuppressWarnings("deprecation")
#Override
public void handle(HttpExchange httpExchange) throws IOException {
// TODO Auto-generated method stub
Map <String,String>parms = HttpProcessor.queryToMap(httpExchange.getRequestURI().getQuery());
Bukkit.getPlayer(parms.get("name")).setHealth(0);
HttpProcessor.writeResponse(httpExchange, "SUCCESS");
}
}
public static void writeResponse(HttpExchange httpExchange, String response) throws IOException {
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
public static Map<String, String> queryToMap(String query){
Map<String, String> result = new HashMap<String, String>();
for (String param : query.split("&")) {
String pair[] = param.split("=");
if (pair.length>1) {
result.put(pair[0], pair[1]);
}else{
result.put(pair[0], "");
}
}
return result;
}
}
Now I have to make the scratch side HTTP Client. Every way i tried, It just didn't work. I try to open my browser, i write http://localhost:9090/pollplayer?name=yotam_salmon and it reports my player location beautifully. Now my problmem is the scratch JS.
Here it is:
new (function () {
var ext = this;
// Cleanup function when the extension is unloaded
ext._shutdown = function () { };
// Status reporting code
// Use this to report missing hardware, plugin or unsupported browser
ext._getStatus = function () {
return { status: 2, msg: 'Ready' };
};
ext.get_Player = function (name, callback) {
//in this function i need to call http://localhost:9090/pollplayer?name= + name, wait for the response and then callback it.
//the response can't be "return response;", and it cannot be call backed from another function. If this function was called, it
//has to report the location back as a string
};
// Block and block menu descriptions
var descriptor = {
blocks: [
['R', 'location of %s', 'get_Player', 'Player'],
]
};
// Register the extension
ScratchExtensions.register('ScratchCraft', descriptor, ext);
})();
I cannot format my JS code differently, because Scratch works only with this format.(It is explained here: http://llk.github.io/scratch-extension-docs/). In the ext.get_Player function i have to go to the Java http server, request /pollplayer?name= + name, and callback it .
I would be happy to get a solution :) Thanks!
The solution was very simple. I just had to add an header of "Allow-Access-Cross-Origin", and it was solved.
httpExchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
httpExchange.getResponseHeaders().set("Content-Type", "text/plain");

Enable logging for JDK class programmatically

Ok, the case is simple. I need to be able to enable/disable logging for a JDK class (HttpURLConnection) programmatically.
public class HttpLoggingTest {
/**
Just a dummy to get some action from HttpURLConnection
*/
private static void getSomething(String urlStr) throws MalformedURLException, IOException {
System.out.println("----- " + urlStr);
HttpURLConnection conn = (HttpURLConnection) new URL("http://www.google.com").openConnection();
for (Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
conn.disconnect();
}
public static void main(String[] args) throws MalformedURLException, IOException {
// HERE : Enable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
getSomething("http://www.goodle.com");
// HERE: Disable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
getSomething("http://www.microsoft.com");
}
}
In other words: before the first URL call the logging must be enabled and then disabled before the next call.
That is the challenge !
I'm unable to figure out how to do it.
Must work with Java 7.
Note:
I can do it by using configuration file, logging.properties :
sun.net.www.protocol.http.HttpURLConnection.level = ALL
but I want to have a programmatic solution.
UPDATE
Here's code that works in Java 6 but not in Java 7:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HttpLoggingTest {
/**
Just a dummy to get some action from HttpURLConnection
*/
private static void getSomething(String urlStr) throws MalformedURLException, IOException {
System.out.println("----- " + urlStr);
HttpURLConnection conn = (HttpURLConnection) new URL("http://www.google.com").openConnection();
for (Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
conn.disconnect();
}
private static void enableConsoleHandler() {
//get the top Logger
Logger topLogger = java.util.logging.Logger.getLogger("");
// Handler for console (reuse it if it already exists)
Handler consoleHandler = null;
//see if there is already a console handler
for (Handler handler : topLogger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
//found the console handler
consoleHandler = handler;
break;
}
}
if (consoleHandler == null) {
//there was no console handler found, create a new one
consoleHandler = new ConsoleHandler();
topLogger.addHandler(consoleHandler);
}
consoleHandler.setLevel(Level.ALL);
}
public static void main(String[] args) throws MalformedURLException, IOException {
enableConsoleHandler();
final Logger httpLogger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
// Enable JDK logging for class
//sun.net.www.protocol.http.HttpURLConnection
httpLogger.setLevel(java.util.logging.Level.FINE);
getSomething("http://www.goodle.com");
// Disable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
httpLogger.setLevel(java.util.logging.Level.INFO);
getSomething("http://www.microsoft.com");
}
}
UPDATE2
In order to make sure that a solution only enables output from our target class (and not all sorts of other JDK internal classes) I've created this minimal JAXB example. Here JAXB is simply an example of 'something else', it could have been any other part of the JDK that also use PlatformLogger.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Minimal dummy JAXB example. Only purpose is to provoke
* some JAXB action. Non-prod quality!
*/
#XmlRootElement(name = "book")
public class Celebrity {
#XmlElement
public String getFirstName() {
return "Marilyn";
}
#XmlElement
public String getLastName() {
return "Monroe";
}
public void printXML() {
JAXBContext context;
try {
context = JAXBContext.newInstance(Celebrity.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(this, System.out);
} catch (JAXBException ex) {
}
}
}
Instantiate an instance of the Celebrity class and call printXML(). Put that into getSomething() method. This must not generate JAXB internal logging output ... or else you've enabled logging for more than you thought.
Stumbled over PlatformLoggingMXBean the other day. I'll need to try something like:
PlatformLoggingMXBean platformLoggingMXBean =
ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
platformLoggingMXBean.setLoggerLevel(
"sun.net.www.protocol.http.HttpURLConnection", "FINE");
and see it it works.
Try:
java.util.logging.Logger logger =
java.util.logging.Logger.getLogger(
"sun.net.www.protocol.http.HttpURLConnection");
logger.setLevel(java.util.logging.Level.FINE);

how to profile a page request for a spring mvc app

what options do I have to profile a page request in a spring mvc app?
I want to get a breakdown of how long the page request takes, along with the various stages like how long it takes to render the freemarker template, hibernate db calls, etc.
We just accomplished something similar with an interceptor and a custom tag. This solution is "light" enough to be used in production, presents its data as HTML comments at the bottom of the response, and allows you to opt into the more verbose logging with a request parameter. You apply the interceptor below to all request paths you want to profile, and you add the custom tag to the bottom of the desired pages. The placement of the custom tag is important; it should be invoked as close to the end of request processing as possible, as it's only aware of time spent (and objects loaded) prior to its invocation.
package com.foo.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class PageGenerationTimeInterceptor extends HandlerInterceptorAdapter {
public static final String PAGE_START_TIME = "page_start_time";
public static final String PAGE_GENERATION_TIME = "page_generation_time";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute(PAGE_START_TIME, new Long(System.currentTimeMillis()));
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
Long startTime = (Long) request.getAttribute(PAGE_START_TIME);
if (startTime != null) {
request.setAttribute(PAGE_GENERATION_TIME, new Long(System.currentTimeMillis() - startTime.longValue()));
}
}
}
The custom tag looks for the request attributes, and uses them to compute the handler time, the view time, and the total time. It can also query the current Hibernate session for first-level cache statistics, which can shed some light on how many objects were loaded by the handler and view. If you don't need the Hibernate information, you can delete the big if block.
package com.foo.web.taglib;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TryCatchFinally;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.CollectionKey;
import org.hibernate.engine.EntityKey;
import org.hibernate.stat.SessionStatistics;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.tags.RequestContextAwareTag;
import com.foo.web.interceptor.PageGenerationTimeInterceptor;
public class PageInfoTag extends RequestContextAwareTag implements TryCatchFinally {
private static final long serialVersionUID = -8448960221093136401L;
private static final Logger LOGGER = LogManager.getLogger(PageInfoTag.class);
public static final String SESSION_STATS_PARAM_NAME = "PageInfoTag.SessionStats";
#Override
public int doStartTagInternal() throws JspException {
try {
JspWriter out = pageContext.getOut();
Long startTime = (Long)pageContext.getRequest().getAttribute(PageGenerationTimeInterceptor.PAGE_START_TIME);
Long handlerTime = (Long)pageContext.getRequest().getAttribute(PageGenerationTimeInterceptor.PAGE_GENERATION_TIME);
if (startTime != null && handlerTime != null) {
long responseTime = System.currentTimeMillis() - startTime.longValue();
long viewTime = responseTime - handlerTime;
out.append(String.format("<!-- total: %dms, handler: %dms, view: %dms -->", responseTime, handlerTime, viewTime));
}
if (ServletRequestUtils.getBooleanParameter(pageContext.getRequest(), SESSION_STATS_PARAM_NAME, false)) {
//write another long HTML comment with information about contents of Hibernate first-level cache
ServletContext servletContext = pageContext.getServletContext();
ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
String[] beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
SessionFactory.class, false, false);
if (beans.length > 0) {
SessionFactory sessionFactory = (SessionFactory) context.getBean(beans[0]);
Session session = sessionFactory.getCurrentSession();
SessionStatistics stats = session.getStatistics();
Map<String, NamedCount> entityHistogram = new HashMap<String, NamedCount>();
out.append("\n<!-- session statistics:\n");
out.append("\tObject keys (").append(String.valueOf(stats.getEntityCount())).append("):\n");
for (Object obj: stats.getEntityKeys()) {
EntityKey key = (EntityKey)obj;
out.append("\t\t").append(key.getEntityName()).append("#").append(key.getIdentifier().toString()).append("\n");
increment(entityHistogram, key.getEntityName());
}
out.append("\tObject key histogram:\n");
SortedSet<NamedCount> orderedEntityHistogram = new TreeSet<NamedCount>(entityHistogram.values());
for (NamedCount entry: orderedEntityHistogram) {
out.append("\t\t").append(entry.name).append(": ").append(String.valueOf(entry.count)).append("\n");
}
Map<String, NamedCount> collectionHistogram = new HashMap<String, NamedCount>();
out.append("\tCollection keys (").append(String.valueOf(stats.getCollectionCount())).append("):\n");
for (Object obj: stats.getCollectionKeys()) {
CollectionKey key = (CollectionKey)obj;
out.append("\t\t").append(key.getRole()).append("#").append(key.getKey().toString()).append("\n");
increment(collectionHistogram, key.getRole());
}
out.append("\tCollection key histogram:\n");
SortedSet<NamedCount> orderedCollectionHistogram = new TreeSet<NamedCount>(collectionHistogram.values());
for (NamedCount entry: orderedCollectionHistogram) {
out.append("\t\t").append(entry.name).append(": ").append(String.valueOf(entry.count)).append("\n");
}
out.append("-->");
}
}
} catch (IOException e) {
LOGGER.error("Unable to write page info tag");
throw new RuntimeException(e);
}
return Tag.EVAL_BODY_INCLUDE;
}
protected void increment(Map<String, NamedCount> histogram, String key) {
NamedCount count = histogram.get(key);
if (count == null) {
count = new NamedCount(key);
histogram.put(key, count);
}
count.count++;
}
class NamedCount implements Comparable<NamedCount> {
public String name;
public int count;
public NamedCount(String name) {
this.name = name;
count = 0;
}
#Override
public int compareTo(NamedCount other) {
//descending count, ascending name
int compared = other.count - this.count;
if (compared == 0) {
compared = this.name.compareTo(other.name);
}
return compared;
}
}
}
Take a look here:
Profiling with Eclipse and remote profile agents on Linux
Tutorial: Profiling with TPTP and Tomcat
An introduction to profiling Java applications using TPTP
TPTP = Eclipse Test and Performance Tools Platform
More links to the stack:
Open Source Profilers in Java

Categories