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:

1ajaxToSpringBeanService.do?beanName=BEANNAME&beanMethod=METHODREQUESTED
2...
3ajaxToSpringBeanService.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:

001package com.ho.servlet;
002 
003import java.io.IOException;
004import java.io.PrintWriter;
005import java.lang.reflect.Method;
006import java.util.HashMap;
007import java.util.Map;
008 
009import javax.servlet.ServletException;
010import javax.servlet.http.HttpServletRequest;
011import javax.servlet.http.HttpServletResponse;
012 
013import net.sf.json.JSONObject;
014 
015import org.apache.commons.lang.exception.ExceptionUtils;
016import org.apache.commons.logging.Log;
017import org.apache.commons.logging.LogFactory;
018import org.springframework.beans.BeansException;
019import org.springframework.context.ApplicationContext;
020import org.springframework.context.ApplicationContextAware;
021import org.springframework.web.HttpRequestHandler;
022 
023/**
024 * Creation of the ajax servlet for access to spring bean in application context (Only public methods without parameter and returning a object)
025 * <p>
026 * This servlet is used to get the languages configured in database directly from a client side via Ajax request:
028 * </p>
029 * @author Huseyin OZVEREN
030 */
031public class AjaxToSpringBeanServlet implements HttpRequestHandler, ApplicationContextAware {
032     
033    // ------------------------------------------- CONSTANTS
034    // -------------------------------------------- LOG4J
035    private final static Log logger = LogFactory.getLog(AjaxToSpringBeanServlet.class);
036 
037    // ------------------------------------------------- PRIVATE ATTRIBUTES
038    // Application Context automatically Injected
039    private ApplicationContext context;
040     
041    // --------------------------------------------- PRIVATE METHODS
042    private boolean isValidParameters(HttpServletRequest req, HttpServletResponse res) {
043        boolean output = true;
044 
045        String beanName = req.getParameter("beanName");
046        String beanMethod = req.getParameter("beanMethod");
047        if (null == beanName || "".equals(beanName) || null == beanMethod || "".equals(beanMethod)) {
048            output = false;
049            //
050            StringBuffer msg = new StringBuffer("");
051            msg.append("<b>Usage : </b><br/>");
052            msg.append("ajaxToSpringBeanService.do?beanName=BEANNAME&beanMethod=METHODREQUESTED<br/><br/>");
053            msg.append("The parameter 'beanName' is the name of bean requested in the Spring context;<br/>");
054            msg.append("The parameter 'beanMethod' is the name of method requested in the bean;<br/>");
055            msg.append("This remote service allows the requests to only public methods without parameter and returning a object;<br/>");
056            //
057            // Data to convert to JSON
058            Map<String, Object> model = new HashMap<String, Object>();
059            model.put("failure", "true");
060            model.put("errorMessage", msg.toString());
061             
062            sendJSONReponseToClientJSON(res, model);
063        }// end-if
064 
065        return output;
066    }
067 
068    /**
069     * Convert the model Map to JSON format
070     *  and send the reponse to client.
071     * @param model
072     */
073    private void sendJSONReponseToClientJSON(HttpServletResponse res, Map<String, Object> model){
074        try{
075            //
076            // Convert POJO to JSON
077            JSONObject jsonObj = new JSONObject();
078            jsonObj.putAll(model);
079            String dataJSON = org.mortbay.util.ajax.JSON.toString(jsonObj);
080            //
081            // Write in RESPONSE
082            PrintWriter out = res.getWriter();
083            out.println(dataJSON);
084 
085        } catch (IOException e) {
086            logger.error("checkValidParameters : an exception occured" , e);
087        } // end-try
088 
089    }
090 
091     
092    // --------------------------------------------- PUBLIC METHODS
093    @Override
094    public void handleRequest(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
095         
096        if (isValidParameters(req, res)) {
097            String beanName = req.getParameter("beanName");
098            String beanMethod = req.getParameter("beanMethod");
099 
100            // Content-type JSON
101            res.setContentType("text/html");
102            // Data to convert to JSON
103            Map<String, Object> model = new HashMap<String, Object>();
104            model.put("failure", "true");
105 
106            try {
107                Object result = null;
108                // Reflection
109                {
110                    Object obj = (Object) context.getBean(beanName);
111                    Class mClass = obj.getClass();
112                    // Only public methods without parameter and returning a object
113                    Method m = mClass.getMethod(beanMethod, null);
114                    result = m.invoke(obj,null);
115                }
116                Object docModel = result;
117 
118                if(docModel!=null){
119                    model = new HashMap<String, Object>();
120                    model.put("success", "true");
121                    model.put("data", docModel);
122                }
123                 
124            } catch (Throwable e) {
125                model.put("errorMessage", ExceptionUtils.getStackTrace(e));
126            }
127 
128            sendJSONReponseToClientJSON(res, model);
129        }
130    }
131 
132     
133    // ------------------------------------------------ GET/SET TERS
134    @Override
135    public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
136        this.context = applicationcontext;
137    }
138}

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:
    1@Override
    2public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
    3    this.context = applicationcontext;
    4}
  • 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.
    1JSONObject jsonObj = new JSONObject();
    2jsonObj.putAll(model);
    3String 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;

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

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

    1Class mClass = obj.getClass();
    2// Only public methods without parameter and returning a object
    3Method m = mClass.getMethod(beanMethod, null);
    4result = 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:

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

So, the web.xml file will be:

01[...]
02    <servlet>
03        <servlet-name>myHUODispatcherServlet</servlet-name>
04        <servlet-class>
05            org.springframework.web.servlet.DispatcherServlet
06        </servlet-class>
07        <load-on-startup>1</load-on-startup>
08        <!-- Configuration file of dispatcher servlet is: myHUODispatcherServlet-servlet.xml  -->
09    </servlet>
10 
11    <servlet-mapping>
12        <servlet-name>myHUODispatcherServlet</servlet-name>
13        <url-pattern>*.do</url-pattern>
14    </servlet-mapping>
15[...]

and myHUODispatcherServlet-servlet.xml file:

01[...]
02    !-- ################### SPRING MVC CONTROLLER XML  ################### -->
03    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
04        <property name="mappings">
05            <value>
06                /loginSecure.do=loginSecureController
07                /document.do=documentController
08                /ajaxToSpringBeanService.do=ajaxToSpringBeanServlet
09                        </value>
10        </property>
11    </bean>
12 
13    <!-- ################### AJAX request to Spring bean ################### -->
14    <bean id="ajaxToSpringBeanServlet" class="com.ho.servlet.AjaxToSpringBeanServlet"/>
15[...]

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

01{
02    id: 'languagesChkBxsContainerID',
03    xtype: 'fieldcontainer',
04    width: 400,
05    maintainFlex: true,
06    defaults: {
07        hideLabel: false,
08        border:false,
09        bodyStyle:'padding:0 0 0 0'
10    },
11    layout: {
12        type: 'column'
13    },
14    fieldLabel: '',
15    items: [
16    ]
17}

… and the Ajax request to our service:

01// request the server to check if the filled exist
02Ext.Ajax.request({
03   async  : false,
04   url    : 'ajaxToSpringBeanService.do?beanName=languageService&beanMethod=getAllLanguages',
05   success: function(request, resp) {
06      var result = Ext.JSON.decode(request.responseText);
07      if (result.success) {
08         <!-- Deleting of all buttons -->
09         Ext.getCmp('languagesChkBxsContainerID').removeAll();
10         Ext.getCmp('languagesChkBxsContainerID').doLayout(); // important
11 
12         for(var i = 0; i < result.data.length; i++){
13            var languageTmp = result.data[i];
14            <!-- Create and add new button -->
15            var newChkBox = new Ext.form.field.Checkbox({
16               xtype: 'checkboxfield',
17               id: 'languageChkBox'+languageTmp.isoCode+'ID',
18               name: 'languageChkBox'+languageTmp.isoCode+'ID',
19               boxLabel: languageTmp.isoCode,   
20               inputValue: 'true',   
21               uncheckedValue: 'false',
22               width: 50
23            });
24            Ext.getCmp('languagesChkBxsContainerID').add(newChkBox);
25         }//end-for
26 
27         Ext.getCmp('languagesChkBxsContainerID').doLayout(); // important
28 
29      } else { NO success field in reponse => (failure=true)
30    if(result.errorMessage === ''){
31              Ext.MessageBox.alert('my title', 'data not found');
32    }else{
33          Ext.MessageBox.alert('my title', result.errorMessage);
34    }
35      }
36     
37   }, failure: function() { // Timeout
38      Ext.MessageBox.alert('my title', 'sorry, timeout!!!!!!');
39   }
40});

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
    1success: function(request, resp) {
  • failure: if the reponse is not correctly returned by the server (timeout)
    1failure: 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:

1model.put("success", "true");
1model.put("failure", "true");
01var result = Ext.JSON.decode(request.responseText);
02if (result.success) {
03      [...]
04 
05} else { // NO success field in reponse => (failure=true)
06      if(result.errorMessage === ''){
07            Ext.MessageBox.alert('my title', 'data not found');
08      }else{
09            Ext.MessageBox.alert('my title', result.errorMessage);
10      }
11}

…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’.

01for(var i = 0; i < result.data.length; i++){
02            var languageTmp = result.data[i];
03            <!-- Create and add new button -->
04            var newChkBox = new Ext.form.field.Checkbox({
05               xtype: 'checkboxfield',
06               id: 'languageChkBox'+languageTmp.isoCode+'ID',
07               name: 'languageChkBox'+languageTmp.isoCode+'ID',
08               boxLabel: languageTmp.isoCode,   
09               inputValue: 'true',   
10               uncheckedValue: 'false',
11               width: 50
12            });
13            Ext.getCmp('languagesChkBxsContainerID').add(newChkBox);
14}//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:

01package com.ho.data.common;
02 
03public class Language {
04    // ---------------------------------- ATTRIBUTS
05    private String id = java.util.UUID.randomUUID().toString();
06    private Integer version = null;
07    private String isoCode = null;
08    private String description = null;
09    private boolean ojLanguage = false;
10    private boolean relayLanguage = false;
11    private boolean sourceLanguage = false;
12    private boolean epLanguage = false;
13    // ----------------------------------- GET/SET TERS
14    public  String getIsoCode() { return isoCode; }
15    public  void setIsoCode(String isoCode) { this.isoCode = isoCode;}
16    public  String getDescription() { return description; }
17    public  void setDescription(String description) { this.description = description; }
18    public  boolean isOjLanguage() { return ojLanguage; }
19    public  void setOjLanguage(boolean ojLanguage) { this.ojLanguage = ojLanguage; }
20    public  boolean isRelayLanguage() { return relayLanguage; }
21[...]
22}

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