Pesquisa personalizada

2009/11/07

Icefaces+Facelets: Customizing the basic html (XHTML) tags using a simple Render Kit extension

Alright, the life, at least my life, isn't easy. But after fourteen hours, finally, I know how to do a simple task: insert the xmlns attribute into the final code of the html tag generated by Icefaces+Facelets couple.

I know that are several ways to insert this attribute, but I just want to play it using the JSF Render Kit. I don't want to filter the stream, or output the tag, or anything else. The render kit should be the way.

I just want to write:
   1:<?xml version='1.0' encoding='UTF-8' ?> 
   2:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3:<html xmlns="http://www.w3.org/1999/xhtml"
   4:      xmlns:ui="http://java.sun.com/jsf/facelets"
   5:      xmlns:c="http://java.sun.com/jstl/core"
   6:      xmlns:h="http://java.sun.com/jsf/html"
   7:      xmlns:ice="http://www.icesoft.com/icefaces/component"
   8:      >
   9:    <body>
  10:
  11:        <ui:composition template="/WEB-INF/tmp/template.xhtml">
  12:
  13:            <ui:param name="hasLeftSection" value="false"/>
  14:            <ui:param name="pageDescription" value="${ArtistPage.pageDescription}"/>
  15:
  16:            <ui:define name="pageTitle">${ArtistPage.pageTitle}</ui:define>
  17:            <ui:define name="extraHeader">
  18:                <link rel="canonical" href="${ArtistPage.canonicalUrl}" />
  19:            </ui:define>
  20:
  21:            <ui:define name="body">
But the xmlns="http://www.w3.org/1999/xhtml" attribute is always trimmed by icefaces/facelets (maybe it's a simple thing to configure using com.icesoft.faces.facelets.D2DFaceletViewHandler or com.sun.facelets.compiler.Compiler - But I didn't investigate them to know).

I inspected the Icefaces code and I saw that it uses the renderer from com.icesoft.faces.renderkit.dom_html_basic.XMLRenderer to write out (to render) the most xhtml tags, like html, body, head, title etc. So, after a little test, was easy to write my own render to add the attribute that I need (or any other, also). The render code is:

   1:package com.solvoj.sondaletra.faces;
   2:
   3:import com.icesoft.faces.component.UIXhtmlComponent;
   4:import com.icesoft.faces.renderkit.dom_html_basic.XMLRenderer;
   5:import java.io.IOException;
   6:import java.util.Iterator;
   7:import java.util.Map;
   8:import javax.faces.component.UIComponent;
   9:import javax.faces.context.FacesContext;
  10:import javax.faces.context.ResponseWriter;
  11:
  12:/**
  13: * 
  14: * @author Marcio Wesley Borges
  15: */
  16:public class MyXMLRenderer extends XMLRenderer {
  17:
  18:    @Override
  19:    public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException {
  20:        final UIXhtmlComponent xhtmlComponent = (UIXhtmlComponent) uiComponent;
  21:        final ResponseWriter writer = facesContext.getResponseWriter();
  22:        final String tag = xhtmlComponent.getTag();
  23:        writer.startElement(tag, xhtmlComponent);
  24:
25: if ("html".equals(tag)) { 26: writer.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml", null); 27: }
28: 29: final Iterator attributeIterator = xhtmlComponent.getTagAttributes().entrySet().iterator(); 30: while (attributeIterator.hasNext()) { 31: Map.Entry attribute = (Map.Entry) attributeIterator.next(); 32: writer.writeAttribute((String) attribute.getKey(), attribute.getValue(), null); 33: } 34: } 35: 36:} 37:
Also, we need to configure the application to use this render above while Icefaces will be playing with xhtml tags, so just add the following lines at the faces-config.xml file of your web application:
   1:<?xml version='1.0' encoding='UTF-8'?>
   2:<faces-config version="1.2"  xmlns="http://java.sun.com/xml/ns/javaee"
   3:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
   4:    <application>
   5:        <locale-config>
   6:            <default-locale>pt_BR</default-locale>
   7:            <supported-locale>pt_BR</supported-locale>
   8:            <supported-locale>en</supported-locale>
   9:        </locale-config>
  10:        <message-bundle>com.solvoj.sondaletra.web.Bundle</message-bundle>
  11:        <view-handler>com.icesoft.faces.facelets.D2DFaceletViewHandler</view-handler>
  12:    </application>
13: <render-kit> 14: <render-kit-id>ICEfacesRenderKit</render-kit-id> 15: <render-kit-class>com.icesoft.faces.renderkit.D2DRenderKit</render-kit-class> 16: <renderer> 17: <component-family>com.icesoft.faces.XhtmlComponent</component-family> 18: <renderer-type>com.icesoft.domXhtml</renderer-type> 19: <renderer-class>com.solvoj.sondaletra.faces.MyXMLRenderer</renderer-class> 20: </renderer> 21: <renderer> 22: <component-family>com.icesoft.faces.XhtmlComponent</component-family> 23: <renderer-type>com.icesoft.faces.Xhtml</renderer-type> 24: <renderer-class>com.solvoj.sondaletra.faces.MyXMLRenderer</renderer-class> 25: </renderer> 26: </render-kit>
It's enough to produce the resultant html page that I expect:
   1:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2:<html id="document:html" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
   3:<head>
The same principe can be used to add or change any attribute that you need to any xhtml tag using Icefaces/Facelets.

Labels: , , , , , ,

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: , , ,

2009/03/12

Coding: Avoiding unnecessary locking

The code bellow, extracted from java.util.regex.Pattern, avoids unnecessary locking. Look the statement if (!compiled) {, it appears twice times because in the first time, we don't need to create a lock point, but if the code isn't "compiled" (!compiled evaluates true), so it creates a lock point and because the condition of variable compiled can be changed from the first time that it was evaluated (eg.: now !compiled can be evaluated to false, because it was "compiled" by another thread, between the first evaluation and the lock point). It avoids unnecessary retention in the case of the first evaluation was false, because in the entire lifecicly of the object there is only a transition of the variable compiled, from false to true, so only one time in the entire lifecicly, the lock point will be reached. It's a good technique! Think about it!
   1:    /**
   2:     * Creates a matcher that will match the given input against this pattern.
   3:     * </p>
   4:     *
   5:     * @param  input
   6:     *         The character sequence to be matched
   7:     *
   8:     * @return  A new matcher for this pattern
   9:     */
  10:    public Matcher matcher(CharSequence input) {
11: if (!compiled) { 12: synchronized(this) { 13: if (!compiled) 14: compile(); 15: } 16: }
17: Matcher m = new Matcher(this, input); 18: return m; 19: } 20:

Labels: , ,