Friday, July 19, 2013

Monitor full page, non AJAX, requests to be notified

Recently, working on new charts and chart "exporting service" in JSF, I've faced a quite common problem. When you execute a long-running task (action), you would like to show a status "Please wait ..." dialog on start and close this dialog at the end, when the response arrives. This is not a problem for AJAX requests, but it is problematic for non AJAX requests which stream some binary content to the browser. To solve this issue, we can use the same technique as for instance the FileDownload component in PrimeFaces uses. We can set a special cookie in response and check this cookie periodically on the client-side if it is set. When this cookie is set, we can close the opened before dialog. The cookie can be set in JSF as
// set cookie to be able to ask it and close status dialog for example
FacesContext.getCurrentInstance().getExternalContext().addResponseCookie(
             "cookie.chart.exporting", "true", Collections.<String, Object>emptyMap());
Non AJAX button executes an JavaScript function, say monitorExporting, with two parameters which represent another JavaScript functions to be called at the beginning and the end of the request / response lifecycle.
<p:commandButton value="Export to PNG" ajax="false" action="#{combiChartController.exportToPng}"
             onclick="monitorExporting(start, stop)"/>
PrimeFaces already provides methods to check if cookies are enabled, to get and delete them. So, we can write
function monitorExporting(start, complete) {
    if(PrimeFaces.cookiesEnabled()) {
        if(start) {
           start();
        }
            
        window.chartExportingMonitor = setInterval(function() {
            var exportingComplete = PrimeFaces.getCookie('cookie.chart.exporting');
 
            if(exportingComplete === 'true') {
                if(complete) {
                    complete();
                }
                     
                clearInterval(window.chartExportingMonitor);
                PrimeFaces.setCookie('cookie.chart.exporting', null);
            }
        }, 150);
    }
}
The cookie is asked periodically with the setInterval(...). The function start shows the "Please wait ..." dialog and the function stop closes it.
<script type="text/javascript">
/* <![CDATA[ */
    function start() {
        statusDialog.show();
    }

    function stop() {
        statusDialog.hide();
    }
/* ]]> */
</script>
If cookies are disabled, nothing is shown of course, but it is normally a rare case.

Tuesday, July 2, 2013

Proper decoding of URL parameters on the server-side in JBoss

I spent many hours today to figure out how to force a proper decoding of encoded characters in JSF applications running on JBoss (using JBoss 7 Final). The problem occurs when you have e.g. chinese characters passed through URL. Assume you have 指事, encoded as %E6%8C%87%E4%BA%8B. Surprise, but these characters arrive you on the server-side as 指事. They are decoded by server automatically with ISO-8859-1. So, it doesn't matter if you try to decode it by yourself like
FacesContext fc = FacesContext.getCurrentInstance();
String param = fc.getExternalContext().getRequestParameterMap().get(name);
String decodedParam = java.net.URLDecoder.decode(param, "UTF-8");
It doesn't help because characters were already wrong decoded and you get them already wrong decoded from the request parameter map. It doesn't help too if you have on the page
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
To overcome this bug you need two things: a special character encoding filter and a configuration in JBoss' standalone.xml. The filter should set the configured encoding for both request and response.
public class CharacterEncodingFilter implements Filter {

    /** The default character encoding to set for request / response. */
    private String encoding = null;

    /** The filter configuration object. */
    private FilterConfig filterConfig;

    /** Should a character encoding specified by the client be ignored? */
    private boolean ignore = true;

    public void destroy() {
        encoding = null;
        filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
        ServletException {
        // conditionally select and set the character encoding to be used
        if ((ignore || (request.getCharacterEncoding() == null)) && (encoding != null)) {
            request.setCharacterEncoding(encoding);
            response.setCharacterEncoding(encoding);
        }

        // pass control on to the next filter
        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        this.encoding = filterConfig.getInitParameter("encoding");

        String value = filterConfig.getInitParameter("ignore");

        this.ignore = ((value == null) || value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
    }
}
Note: It will not help if you only set the encoding for request. You should also set it for response by response.setCharacterEncoding(encoding). Configuration in web.xml looks like
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>xyz.mypackage.CharacterEncodingFilter</filter-class>
    <init-param>
        <description>override any encodings from client</description>
        <param-name>ignore</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <description>the encoding to use</description>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>*.jsf</url-pattern>
</filter-mapping>
Now you have to add following system properties to the standalone.xml, direct after the closing <extensions> tag:
<system-properties>
    <property name="org.apache.catalina.connector.URI_ENCODING" value="UTF-8"/>
    <property name="org.apache.catalina.connector.USE_BODY_ENCODING_FOR_QUERY_STRING" value="true"/>
</system-properties>
From the docu:
  • org.apache.catalina.connector.URI_ENCODING specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
  • org.apache.catalina.connector.USE_BODY_ENCODING_FOR_QUERY_STRING specifies if the encoding specified in contentType should be used for URI query parameters, instead of using the org.apache.catalina.connector.URI_ENCODING. This setting is present for compatibility with Tomcat 4.1.x, where the encoding specified in the contentType, or explicitely set using Request.setCharacterEncoding method was also used for the parameters from the URL. The default value is false.
JBoss looks now set character encoding for response and decodes URL parameters with it. I hope this info will help you to save your time.