JavaBlog.fr / Java.lu DEVELOPMENT,Java,Sencha,Spring,WEB Spring: Expose all Spring beans as HTTP/AJAX service returning content in JSON format in Spring MVC application

Spring: Expose all Spring beans as HTTP/AJAX service returning content in JSON format in Spring MVC application

Here, a homemade “securized” solution (questionable ??!!!) allowing the generic calls to Spring bean on server side from the client side with Ajax requests on the format:

ajaxToSpringBeanService.do?beanName=BEANNAME&beanMethod=METHODREQUESTED
...
ajaxToSpringBeanService.do?beanName=languageService&beanMethod=getAllLanguages

In fact, this solution exposes the Spring BEANs as a HTTP/AJAX SERVICE, returning a content like JSON objects in a Spring MVC application.

This solution uses several technologies:

  • Spring: the HttpRequestHandler interface for the creation of coordinator servlet, the ApplicationContextAware interface for the access of coordinator servlet to the Spring application context;
  • Java Reflection (see my last post on Java: Reflection, javap): used in the method’s calls on Spring bean on server side;
  • JSON: JSON will be the format of response from server to client;
  • SENCHA/ExtJs: used by the Ajax requests and the JSON response’s handling on client side;

I). Server Side components
First, Following the codes of coordinator servlet AjaxToSpringBeanServlet:

package com.ho.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.HttpRequestHandler;

/**
 * Creation of the ajax servlet for access to spring bean in application context (Only public methods without parameter and returning a object)
 * <p>
 * This servlet is used to get the languages configured in database directly from a client side via Ajax request:
 * http://localhost:8080/..../ajaxToSpringBeanService.do?beanName=languageService&beanMethod=getAllLanguages
 * </p>
 * @author Huseyin OZVEREN
 */
public class AjaxToSpringBeanServlet implements HttpRequestHandler, ApplicationContextAware {
	
	// ------------------------------------------- CONSTANTS
	// -------------------------------------------- LOG4J
	private final static Log logger = LogFactory.getLog(AjaxToSpringBeanServlet.class);

	// ------------------------------------------------- PRIVATE ATTRIBUTES
	// Application Context automatically Injected 
	private ApplicationContext context;
	
	// --------------------------------------------- PRIVATE METHODS
	private boolean isValidParameters(HttpServletRequest req, HttpServletResponse res) {
		boolean output = true;

		String beanName = req.getParameter("beanName");
		String beanMethod = req.getParameter("beanMethod");
		if (null == beanName || "".equals(beanName) || null == beanMethod || "".equals(beanMethod)) {
			output = false;
			// 
			StringBuffer msg = new StringBuffer("");
			msg.append("<b>Usage : </b><br/>");
			msg.append("ajaxToSpringBeanService.do?beanName=BEANNAME&beanMethod=METHODREQUESTED<br/><br/>");
			msg.append("The parameter 'beanName' is the name of bean requested in the Spring context;<br/>");
			msg.append("The parameter 'beanMethod' is the name of method requested in the bean;<br/>");
			msg.append("This remote service allows the requests to only public methods without parameter and returning a object;<br/>");
			//
			// Data to convert to JSON
			Map<String, Object> model = new HashMap<String, Object>();
			model.put("failure", "true");
			model.put("errorMessage", msg.toString());
			
			sendJSONReponseToClientJSON(res, model);
		}// end-if

		return output;
	}

	/**
	 * Convert the model Map to JSON format 
	 * 	and send the reponse to client.
	 * @param model
	 */
	private void sendJSONReponseToClientJSON(HttpServletResponse res, Map<String, Object> model){
		try{
			//
			// Convert POJO to JSON
			JSONObject jsonObj = new JSONObject();
			jsonObj.putAll(model);
			String dataJSON = org.mortbay.util.ajax.JSON.toString(jsonObj);
			//
			// Write in RESPONSE
			PrintWriter out = res.getWriter();
			out.println(dataJSON);

		} catch (IOException e) {
			logger.error("checkValidParameters : an exception occured" , e);
		} // end-try

	}

	
	// --------------------------------------------- PUBLIC METHODS
	@Override
	public void handleRequest(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		
		if (isValidParameters(req, res)) {
			String beanName = req.getParameter("beanName");
			String beanMethod = req.getParameter("beanMethod");

			// Content-type JSON
			res.setContentType("text/html"); 
			// Data to convert to JSON
			Map<String, Object> model = new HashMap<String, Object>();
			model.put("failure", "true");

			try {
				Object result = null;
				// Reflection
				{
					Object obj = (Object) context.getBean(beanName);
					Class mClass = obj.getClass();
					// Only public methods without parameter and returning a object 
					Method m = mClass.getMethod(beanMethod, null);
					result = m.invoke(obj,null);
				}
				Object docModel = result;

				if(docModel!=null){
					model = new HashMap<String, Object>(); 
					model.put("success", "true");
					model.put("data", docModel);
				}
				
			} catch (Throwable e) {
				model.put("errorMessage", ExceptionUtils.getStackTrace(e));
			}

			sendJSONReponseToClientJSON(res, model);
		}
	}

	
	// ------------------------------------------------ GET/SET TERS
	@Override
	public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
		this.context = applicationcontext;
	}
}

Explanations:

  • This class AjaxToSpringBeanServlet implements the interface HttpRequestHandler which is a plain handler interface for components that process HTTP requests, analogous to a Servlet. In this example, because we use this solution in a Web application based on Spring MVC, we will use the option recommended by the Spring (2) documentation for the way of exposing an HttpRequestHandler:

    (1) The easiest way to expose an HttpRequestHandler bean in Spring style is to define it in Spring’s root web application context and define an HttpRequestHandlerServlet in web.xml, pointing at the target HttpRequestHandler bean through its servlet-name which needs to match the target bean name.

    (2) Supported as a handler type within Spring’s DispatcherServlet, being able to interact with the dispatcher’s advanced mapping and interception facilities. This is the recommended way of exposing an HttpRequestHandler, while keeping the handler implementations free of direct dependencies on a DispatcherServlet environment.

  • More, this class AjaxToSpringBeanServlet implements also the interface setApplicationContext in order to have a direct access to the Spring Application context which is injected by the following method:
    	@Override
    	public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
    		this.context = applicationcontext;
    	}
    
  • The method sendJSONReponseToClientJSON converts a model Map to JSON format and send this result to the client. To convert a POJO to JSON, I have use the class org.mortbay.util.ajax.JSON from the library “jetty-util-6.1.9.jar”. It is possible to use another Java libraries like Google Gson that can be used to convert Java Objects into their JSON representation.
    			JSONObject jsonObj = new JSONObject();
    			jsonObj.putAll(model);
    			String dataJSON = org.mortbay.util.ajax.JSON.toString(jsonObj);
    
  • The method isValidParameters checks the presence of “beanName” and “beanMethod” parameters; and returns to the client a error message concerning the service’s usage.
  • And the last method handleRequest contains the logical business i.e.:
    – retrieve the parameters “beanName” and “beanMethod” from ajax request;
  • – get the Spring bean due to the “beanName” parameter;

    Object obj = (Object) context.getBean(beanName);
    

    – use the reflection to invoke the bean’s method requested:

    	Class mClass = obj.getClass();
    	// Only public methods without parameter and returning a object 
    	Method m = mClass.getMethod(beanMethod, null);
    	result = m.invoke(obj,null);
    

    – create a Map model object which will be converted to JSON format and sent to client. The keys of this Map are “failure” flag used in error/failure cases; “errorMessage” containing the error message; “success” flag set in normal cases and “data” containing the data object which is the result returned by the call of Spring bean’s method:

    model.put("failure", "true");
    model.put("errorMessage", ExceptionUtils.getStackTrace(e));
    model.put("success", "true");
    model.put("data", docModel);

So, the web.xml file will be:

[...]
	<servlet>
		<servlet-name>myHUODispatcherServlet</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
		<!-- Configuration file of dispatcher servlet is: myHUODispatcherServlet-servlet.xml  -->
	</servlet>

	<servlet-mapping>
		<servlet-name>myHUODispatcherServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
[...]

and myHUODispatcherServlet-servlet.xml file:

[...]
	!-- ################### SPRING MVC CONTROLLER XML  ################### -->
	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<value>
				/loginSecure.do=loginSecureController
				/document.do=documentController
				/ajaxToSpringBeanService.do=ajaxToSpringBeanServlet
            			</value>
		</property>
	</bean>

	<!-- ################### AJAX request to Spring bean ################### -->
	<bean id="ajaxToSpringBeanServlet" class="com.ho.servlet.AjaxToSpringBeanServlet"/>
[...]

II). Client Side
Here, an example of a SENCHA/ExtJs interface containing a panel:

{
	id: 'languagesChkBxsContainerID',
	xtype: 'fieldcontainer',
	width: 400,
	maintainFlex: true,
	defaults: {
		hideLabel: false,
		border:false,
		bodyStyle:'padding:0 0 0 0'
	},
	layout: {
		type: 'column'
	},
	fieldLabel: '',
	items: [
	]
}

… and the Ajax request to our service:

// request the server to check if the filled exist
Ext.Ajax.request({
   async  : false,
   url    : 'ajaxToSpringBeanService.do?beanName=languageService&beanMethod=getAllLanguages',
   success: function(request, resp) {
      var result = Ext.JSON.decode(request.responseText);
      if (result.success) {
         <!-- Deleting of all buttons -->
         Ext.getCmp('languagesChkBxsContainerID').removeAll(); 
         Ext.getCmp('languagesChkBxsContainerID').doLayout(); // important 

         for(var i = 0; i < result.data.length; i++){
            var languageTmp = result.data[i];
            <!-- Create and add new button -->
            var newChkBox = new Ext.form.field.Checkbox({
               xtype: 'checkboxfield',
               id: 'languageChkBox'+languageTmp.isoCode+'ID',
               name: 'languageChkBox'+languageTmp.isoCode+'ID',
               boxLabel: languageTmp.isoCode,    
               inputValue: 'true',    
               uncheckedValue: 'false',
               width: 50
            });
            Ext.getCmp('languagesChkBxsContainerID').add(newChkBox); 
         }//end-for

         Ext.getCmp('languagesChkBxsContainerID').doLayout(); // important 

      } else { NO success field in reponse => (failure=true)
	if(result.errorMessage === ''){
              Ext.MessageBox.alert('my title', 'data not found');
	}else{
	      Ext.MessageBox.alert('my title', result.errorMessage);
	}
      }
	
   }, failure: function() { // Timeout
      Ext.MessageBox.alert('my title', 'sorry, timeout!!!!!!');
   }
});

Explanations:
In our example, the client sends a request to server to collect all languages via the Spring bean named “languageService” and its method “getAllLanguages” (see the parameters in URL “beanName=languageService&beanMethod=getAllLanguages”).

In Sencha/ExtJs Ajax request, there are several callbacks for:

  • success: if the reponse is correctly returned by the server
     success: function(request, resp) {
    
  • failure: if the reponse is not correctly returned by the server (timeout)
     failure: function() { // Timeout
    

In the ‘failure’ case i.e. no reponse received, a timeout message is displayed to client/user.

In the ‘success’ case, the response received, is decoded via EXTJS utility, and a check is done on the ‘success’ field in JSON object corresponding to the Java code:

model.put("success", "true"); 
 model.put("failure", "true"); 
var result = Ext.JSON.decode(request.responseText);
if (result.success) {
      [...]

} else { // NO success field in reponse => (failure=true)
      if(result.errorMessage === ''){
            Ext.MessageBox.alert('my title', 'data not found');
      }else{
            Ext.MessageBox.alert('my title', result.errorMessage);
      }
}

…if there is not ‘success’ field in response (failure=true), the error message recevied is displayed to client/user.
…otherwise, we range all “data” received which in our case corresponding to a list of ‘Language’ containing at least the ‘isoCode’ property. For each element receveid, we create a ‘checkboxfield’ in the previous panel with the ID ‘languagesChkBxsContainerID’.

 for(var i = 0; i < result.data.length; i++){
            var languageTmp = result.data[i];
            <!-- Create and add new button -->
            var newChkBox = new Ext.form.field.Checkbox({
               xtype: 'checkboxfield',
               id: 'languageChkBox'+languageTmp.isoCode+'ID',
               name: 'languageChkBox'+languageTmp.isoCode+'ID',
               boxLabel: languageTmp.isoCode,    
               inputValue: 'true',    
               uncheckedValue: 'false',
               width: 50
            });
            Ext.getCmp('languagesChkBxsContainerID').add(newChkBox); 
}//end-for

… the above codes will produce the below result:

III). Tests and results
Example of results with a list of following POJO, converted to JSON and sent by server to client:

package com.ho.data.common;

public class Language {
	// ---------------------------------- ATTRIBUTS
	private String id = java.util.UUID.randomUUID().toString();
	private Integer version = null;
	private String isoCode = null;
	private String description = null;
	private boolean ojLanguage = false;
	private boolean relayLanguage = false;
	private boolean sourceLanguage = false;
	private boolean epLanguage = false;
	// ----------------------------------- GET/SET TERS
	public  String getIsoCode() { return isoCode; }
	public  void setIsoCode(String isoCode) { this.isoCode = isoCode;}
	public  String getDescription() { return description; }
	public  void setDescription(String description) { this.description = description; }
	public  boolean isOjLanguage() { return ojLanguage; }
	public  void setOjLanguage(boolean ojLanguage) { this.ojLanguage = ojLanguage; }
	public  boolean isRelayLanguage() { return relayLanguage; }
[...]
}

Some examples:

  • No parameters: ajaxToSpringBeanService.do
  • The beanName parameter is missing: ajaxToSpringBeanService.do?beanName=languageService
  • The beanMethod parameter is missing: ajaxToSpringBeanService.do?beanMethod=getAllLanguages
  • OK: ajaxToSpringBeanService.do?beanName=languageService&beanMethod=getAllLanguages

IV). Conclusion
In this post, we have studied a “securized” homemade solution (questionable ??!!!) exposing the Spring BEANs as a HTTP/AJAX SERVICE, returning a content like JSON objects in a Spring MVC application. However, several comments:

  • This solution exposes all Spring BEANs transactionnal or not.
  • This solution is “securized” like a normal SPRING MVC controller.
  • Currently, this solution allows the calls to public methods without parameters and returning a object of the Spring beans (so “securized”???).
  • Yet, it could easily (due to Reflection) expose the private methods, fields, methods with arguments of the Spring beans.
  • This solution accepts the calls from all clients which use JSON.
  • There are a lot of others Java libraries like DWR that coupled with Spring, enables Java on the server and JavaScript in a browser to interact and call each other.
  • More, this homemade solution is easily customized.

Huseyin OZVEREN

Leave a Reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

Related Post