Pesquisa personalizada

2009/04/04

woodstock.xhr.post: Do you see Google? See?!

How to post a JSON data via woodstock.xhr.post?

Woodstock Festival

Sometimes simple things may be hard to do... but I'm one of 'the last Woodstock survives
- No, I wasn't at Woodstock Festival - but, I wanted to.

I learned the Woodstock JSF Components (now officially abandoned by Sun) and I'm using it in at least two projects. Damn! I love Dojo! And Woodstock was all built with Dojo. But, now, after a long time learning (reading, trying, testing, experiencing and going mad), after finally to know what and how to do with Woodstock, the project was buried. I'm desolated!

I did a good work using Woodstock, but now I should migrate to Icefaces (acording Sun), but I don't want and I can't rewrite all my work to another technology.

Several troubles I had using Woodstock and nearly ten times, several troubles I solved with it.

AJAX using Woodstock

When I need to use AJAX with Woodstock pages, I don't use Dynafaces. Instead, I used submit or refresh standard Woodstock component feature to do a postback or I use AJAX request with servlet.

To do a AJAX request using the methdo GET is easy and well documented by Woodstock. Bellow, see example showing how to get customer data via AJAX using only the first and last customer names:

   1:function getCustomerByNames(lastName, firstName) {
   2:    var props = {
   3:        async: true,
   4:
   5:        onError: function(xhr) {
   6:            window.alert("An error occurs while sending an AJAX request. See: " + xhr)
   7:        },
   8:
   9:        onReady: function(xhr) {
  10:            var resp = eval('(' + xhr.responseText + ')');
  11:            window.alert("Customer is " + resp.name + " <" + resp.email + ">");  
  12:        },
  13:
  14:        url: "/AjaxBridge?a=Customer&t=getCustomerByCode"
  15:    };
  16:
  17:    props.url += "&lastName=" + lastName;
  18:    props.url += "&firstName=" + firstName;
  19:
  20:    woodstock.xhr.get(props);
  21:}
  22:

From the server-side, each AJAX "Agent" is a subclass of:

   1:package br.com.trilha21.web.store.ajax;
   2:
   3:import java.io.IOException;
   4:import java.io.PrintWriter; 
   5:import java.lang.reflect.Method;
   6:import java.util.logging.Level;
   7:import java.util.logging.Logger;
   8:import javax.servlet.ServletException;
   9:import javax.servlet.http.HttpServletRequest;
  10:import javax.servlet.http.HttpServletResponse;
  11:import net.marciowb.poison.web.jsf.JSFUtil;
  12:import org.json.JSONException;
  13:import org.json.JSONObject;
  14:
  15:/**
  16: *
  17: * @author Marcio Wesley Borges
  18: */
  19:public class AjaxAgent {
  20:    private static final Logger logger = Logger.getLogger(AjaxAgent.class.getName());
  21:    
  22:    final String task;  
  23:    protected final HttpServletRequest request;
  24:    protected final HttpServletResponse response;
  25:    protected final JSONObject mainObj;
  26:    
  27:    protected AjaxAgent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  28:        this.request = request;
  29:        this.response = response;
  30:        response.setBufferSize(16384);
  31:        task = getParam("t");
  32:        try {
  33:            mainObj = new JSONObject();        
  34:            mainObj.put("agent", getClass().getSimpleName());
  35:            mainObj.put("by", task);
  36:        } catch (JSONException ex) {
  37:            throw new ServletException(ex);
  38:        }
  39:    }
  40:    
  41:    public String getTask() {
  42:        return task;
  43:    }
  44:    
  45:    protected static JSONObject toJSONObject(Object bean) {
  46:        try {
  47:            return JSFUtil.buildJSONObject(bean);
  48:        } catch (Exception ex) {
  49:            logger.log(Level.SEVERE, null, ex);
  50:        }
  51:        return null;
  52:    }
  53:    
  54:    private void outputJson() throws IOException, JSONException {
  55:        response.setContentType("text/json;charset=UTF-8");
  56:        response.setHeader("Cache-Control", "no-cache");        
  57:        final PrintWriter writer = response.getWriter();
  58:        writer.print(mainObj.toString());
  59:        writer.close();
  60:    } 
  61:    
  62:    protected String getParam(String name) {
  63:        return request.getParameter(name);
  64:    }
  65:
  66:    protected Long getParamAsLong(String name) {
  67:        final String p = getParam(name);
  68:        return Long.valueOf(p);
  69:    }
  70:
  71:    public final void exec() throws ServletException, IOException {
  72:        try {
  73:            final Method m = getClass().getMethod(task);
  74:            
  75:            m.invoke(this);
  76:            outputJson();
  77:        } catch (NoSuchMethodException ex) {
  78:            response.sendError( HttpServletResponse.SC_NOT_IMPLEMENTED );
  79:        } catch (Exception ex) {
  80:            logger.log(Level.SEVERE, "Error while executing an AJAX request.", ex);
  81:            response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.toString() );
  82:        }
  83:    }
  84:
  85:}
  86:

The servlet to attend AJAX request is like:

   1:package br.com.trilha21.web.store.ajax;
   2:
   3:import java.io.IOException;
   4:import java.lang.reflect.Constructor;
   5:import java.util.Map;
   6:import java.util.WeakHashMap;
   7:import java.util.logging.Level;
   8:import java.util.logging.Logger;
   9:import javax.servlet.ServletException;
  10:import javax.servlet.http.HttpServlet;
  11:import javax.servlet.http.HttpServletRequest;
  12:import javax.servlet.http.HttpServletResponse;
  13:
  14:/**
  15: * @author Marcio Wesley Borges
  16: */
  17:public class AjaxBridgeServlet extends HttpServlet {
  18:    private static final Logger logger = Logger.getLogger(AjaxBridgeServlet.class.getName());
  19:    
  20:    private static final String PCKG = AjaxBridgeServlet.class.getPackage().getName() + ".agents.";
  21:    private static final Map<String, Class<? extends AjaxAgent>> agents = new WeakHashMap<String, Class<? extends AjaxAgent>>();
  22:
  23:    private static final String AGENT_NAME_SUFIX = "Agent";
  24:   
  25:    private <T extends AjaxAgent> Class<T> getAgent(String agentName) {
  26:        if (!agentName.endsWith(AGENT_NAME_SUFIX))
  27:            agentName+=AGENT_NAME_SUFIX;
  28:
  29:        Class<T> agentClass = (Class<T>)agents.get(agentName);
  30:        if (agentClass==null) {
  31:            try {
  32:                agentClass = (Class<T>) Class.forName( PCKG + agentName );
  33:            } catch (ClassNotFoundException ex) {
  34:                return null;
  35:            }
  36:            agents.put(agentName, agentClass);
  37:        }
  38:        return agentClass;
  39:    }
  40:    
  41:    /** 
  42:    * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
  43:    * @param request servlet request
  44:    * @param response servlet response
  45:    */
  46:    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  47:    throws ServletException, IOException {
  48:        try {
  49://        response.setContentType("text/html;charset=UTF-8");
  50:            final String agentName = request.getParameter("a");
  51:            final Class<? extends AjaxAgent> agentClass = getAgent(agentName);
  52:            if (agentClass == null) {
  53:                response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
  54:                return;
  55:            }
  56:
  57:            final Constructor agentConstructor = agentClass.getConstructors()[0];
  58:            final AjaxAgent agent = (AjaxAgent)agentConstructor.newInstance(request, response);
  59:            agent.exec();
  60:            return;
  61:            
  62:        } catch (Throwable ex) {
  63:            logger.log(Level.SEVERE, "Error in AjaxBridgeServlet while processing the request: " + request, ex);
  64:        }
  65:        
  66:        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  67:    } 
  68:
  69:    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  70:    /** 
  71:    * Handles the HTTP <code>GET</code> method.
  72:    * @param request servlet request
  73:    * @param response servlet response
  74:    */
  75:    protected void doGet(HttpServletRequest request, HttpServletResponse response)
  76:    throws ServletException, IOException {
  77:        processRequest(request, response);
  78:    } 
  79:
  80:    /** 
  81:    * Handles the HTTP <code>POST</code> method.
  82:    * @param request servlet request
  83:    * @param response servlet response
  84:    */
  85:    protected void doPost(HttpServletRequest request, HttpServletResponse response)
  86:    throws ServletException, IOException {
  87:        processRequest(request, response);
  88:    }
  89:
  90:    /** 
  91:    * Returns a short description of the servlet.
  92:    */
  93:    public String getServletInfo() {
  94:        return "Short description";
  95:    }// </editor-fold>
  96:
  97:}
  98:

And, finally, the implementantion of the AJAX Agent (who is attending the AJAX requests) is like:

   1:package br.com.trilha21.web.store.ajax.agents;
   2:
   3:import br.com.trilha21.web.store.ajax.AjaxAgent;
   4:import br.com.trilha21.web.store.dao.Customer;
   5:import br.com.trilha21.web.store.ejb.CustomerLocal;
   6:import java.io.IOException;
   7:import javax.servlet.ServletException;
   8:import javax.servlet.http.HttpServletRequest;
   9:import javax.servlet.http.HttpServletResponse;
  10:import net.marciowb.poison.ejb.EJBUtil;
  11:import org.json.JSONException;
  12:
  13:/** 
  14: * @author Marcio Wesley Borges
  15: */
  16:public final class CustomerAgent extends AjaxAgent {
  17:    
  18:    final CustomerLocal customerBean = EJBUtil.lookupLocalBean(CustomerLocal.class);
  19:    
  20:    public CustomerAgent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  21:        super(request,response);
  22:    }
  23:    
  24:    public void getCustomerByDoc() throws ServletException, IOException, JSONException {
  25:        final String customerDoc = getParam("customerDoc");
  26:        
  27:        final Customer customer = customerBean.get(customerDoc);
  28:        mainObj.put("customer", toJSONObject(customer));
  29:    }
  30:    
  31:    public void getCustomerByNames() throws ServletException, IOException, JSONException {
  32:        final String last = getParam("lastName");
  33:        final String first = getParam("firstName");
  34:        
  35:        final Customer customer = customerBean.getCustomerByNames(last,first);
  36:        mainObj.put("customer", toJSONObject(customer));
  37:    }
  38:}
  39:

The code above, works to HTTP requests (via GET method), but it doesn't works to post data (via POST method). To post data using woodstock.xhr, you must use woodstock.xhr.post instead of woodstock.xhr.get

Posting: woodstock.xhr.post

After quickly googling for "woodstock.xhr.post", I cried, 'cause nothing was returned! So, how to post (and recover) data via "woodstock.xhr.post" stuff?

Seeing the woodstock JS documentation, you will find the mention to the parameter content. All post data must be 'posted' via this parameter. So, the idea to pass complex data (as objects with several kinds of properties) is to encode the data in client side and decode in server side. As example, you can pass a object using JSON and decoding it using Java JSON library at server side.

Bellow you see (Object.toJSON is a Prototype utility method) the equivalent post data of the previous client side code supplied:

   1:function getCustomerByNames(lastName, firstName) {
   2:    var props = {
   3:        async: true,
   4:
   5:        onError: function(xhr) {
   6:            window.alert("An error occurs while sending an AJAX request. See: " + xhr)
   7:        },
   8:
   9:        onReady: function(xhr) {
  10:            var resp = eval('(' + xhr.responseText + ')');
  11:            window.alert("Customer is " + resp.name + " <" + resp.email + ">");  
  12:        },
  13:
  14:        url: "/AjaxBridge?a=Customer&t=getCustomerByCode",
15: content: Object.toJSON({ 16: 'lastName': lastName, 17: 'firstName': firstName 18: })
19: }; 20: 21: woodstock.xhr.post(props); 22:} 23:

To read the content parameter passed above in the servlet, you can do:

   1:public final class CustomerAgent extends AjaxAgent {
   2:    
   3:    final CustomerLocal customerBean = EJBUtil.lookupLocalBean(CustomerLocal.class);
   4:    
   5:    public CustomerAgent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   6:        super(request,response);
   7:    }
   8:    
   9:    public void getCustomerByDoc() throws ServletException, IOException, JSONException {
  10:        final String customerDoc = getParam("customerDoc");
  11:        
  12:        final Customer customer = customerBean.get(customerDoc);
  13:        mainObj.put("customer", toJSONObject(customer));
  14:    }
  15:
16: public void getCustomerByNames() throws ServletException, IOException, JSONException { 17: final String data = IOUtil.readText(request.getInputStream());//Reads the 'content' parameter - it's equivalent to the post body. 18: final JSONObject json = new JSONObject(data); 19: final String lastName = json.getString("lastName"); 20: final String firstName = json.getString("firstName"); 21: final Customer customer = customerBean.getCustomerByNames(lastName,firstName); 22: mainObj.put("customer", toJSONObject(customer)); 23: }
24:} 25:

Good Icefaces look! I'll still with Woodstock for now.

Labels: , , ,