Wednesday, December 19, 2007

Action dependent requireness

Introduction

There are scenarios where you want to depend the "requireness" (between quotes, this word doesn't occur in an English dictionary) of an UIInput element on the UICommand action invoked. There are also a lot of hacks and workarounds written about it, e.g. using immediate="true" and valueChangeListener or binding attributes to retain the value (which gives you only two scenario cases) or moving the validation to the backing bean actions (which gives you endless scenario cases), etcetera.

Those workarounds require extra logic in the backing bean. It would be great if JSF offers an default possibility to let the requireness of the UIInput depend on the UICommand action invoked. Having an attribute like requiredActions which accepts a commaseparated string of the ID's of the buttons would be great. Unfortunately such an attribute isn't available. But don't feel disappointed, there is a possibility to use the request parameter map to determine which UICommand action invoked. Its client ID is namely available in the request parameter map. So just checking the presence of the client ID in the #{param} ought to be enough. KISS! ;)

Back to top

Basic JSF example

Here is a sample form. It represents a login form with two input fields and two buttons. The username as well as the password fields are required when you press the "Login" button, while the password field isn't required when you press the "Forget password" button. This is done by checking the presence of the client ID of the "Login" button in the required attribute of the password field. You can also check the absence of the client ID of the "Forget password" button as well.

The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra).

<h:form id="form">
    <h:panelGrid columns="3">
        <h:outputLabel for="username" value="Username" />
        <h:inputText id="username" value="#{myBean.username}"
            required="true" />
        <h:message for="username" style="color: red;" />

        <h:outputLabel for="password" value="Password" />
        <h:inputSecret id="password" value="#{myBean.password}"
            required="#{!empty param['form:login']}" />
        <h:message for="password" style="color: red;" />

        <h:panelGroup />
        <h:panelGroup>
            <h:commandButton id="login" value="Login" action="#{myBean.login}" />
            <h:commandButton id="forget" value="Forget password" action="#{myBean.forget}" />
        </h:panelGroup>
        <h:message for="form" style="color: green;" />
    </h:panelGrid>
</h:form>

The appropriate test backing bean (request scoped) look like:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private String username;
    private String password;

    // Actions ------------------------------------------------------------------------------------

    public void login() {

        // Just for debug. Don't do this in real! Hash the password, compare in DB and forget it ;)
        System.out.println("Login username: " + username);
        System.out.println("Login password: " + password);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("form", new FacesMessage("Login succesful!"));
    }

    public void forget() {

        // Just for debug.
        System.out.println("Forget username: " + username);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("form", new FacesMessage("New password sent!"));
    }

    // Getters ------------------------------------------------------------------------------------

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Now, when you hit the "Login" button, it will validate the username as well as the password fields, but when you hit the "Forget password" button, it will validate the username field only!

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) December 2007, BalusC

Friday, December 14, 2007

Set focus in JSF

The power of a PhaseListener

The following example shows how to use a PhaseListener to set focus to the first next input element which has a faces message (which can be caused by a validation or conversion error or any other custom reason). It is relatively simple, it costs effectively only a few lines inside the beforePhase of the RENDER_RESPONSE and a small Javascript function.

Here is a sample form. Note the Javascript which should be placed at the very end of the HTML body, at least after the input element which should be focused. The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra).

<h:form id="someForm">
    <h:panelGrid columns="3">
        <h:outputLabel for="input1" value="Enter input 1" />
        <h:inputText id="input1" value="#{myBean.input1}" required="true" />
        <h:message for="input1" style="color: red;" />

        <h:outputLabel for="input2" value="Enter input 2" />
        <h:inputText id="input2" value="#{myBean.input2}" required="true" />
        <h:message for="input2" style="color: red;" />

        <h:outputLabel for="input3" value="Enter input 3" />
        <h:inputText id="input3" value="#{myBean.input3}" required="true" />
        <h:message for="input3" style="color: red;" />

        <h:panelGroup />
        <h:commandButton value="Submit" action="#{myBean.doSomething}" />
        <h:message for="someForm" style="color: green;" />
    </h:panelGrid>
</h:form>

<script>
    setFocus('${focus}');
</script>

Here is how the Javascript function setFocus() look like:

function setFocus(id) {
    var element = document.getElementById(id);
    if (element && element.focus) {
        element.focus();
    }
}

And now the PhaseListener which sets the focus to the first next input element which has a faces message:

/*
 * net/balusc/webapp/SetFocusListener.java
 * 
 * Copyright (C) 2007 BalusC
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if
 * not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package net.balusc.webapp;

import java.util.Iterator;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

/**
 * This phase listener checks if there is a client ID with message and will set the client ID
 * as ${focus} in the request map.
 * <p>
 * This phase listener should be configured in the faces-config.xml as follows:
 * <pre>
 * &lt;lifecycle&gt;
 *     &lt;phase-listener&gt;net.balusc.webapp.SetFocusListener&lt;/phase-listener&gt;
 * &lt;/lifecycle&gt;
 * </pre>
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/12/set-focus-in-jsf.html
 */
public class SetFocusListener implements PhaseListener {

    // Actions ------------------------------------------------------------------------------------

    /**
     * @see javax.faces.event.PhaseListener#getPhaseId()
     */
    public PhaseId getPhaseId() {

        // Listen on render response phase.
        return PhaseId.RENDER_RESPONSE;
    }

    /**
     * @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
     */
    public void beforePhase(PhaseEvent event) {

        // Init.
        FacesContext facesContext = event.getFacesContext();

        // Get an iterator over all client ID's with messages.
        Iterator<String> clientIdsWithMessages = facesContext.getClientIdsWithMessages();

        // If there is a client ID with message ..
        if (clientIdsWithMessages.hasNext()) {
            // .. obtain the first client ID with message.
            String firstClientIdWithMessage = clientIdsWithMessages.next();

            // Set the focus to this client ID. The value will be available in ${focus} in JSP.
            facesContext.getExternalContext().getRequestMap().put("focus", firstClientIdWithMessage);
        }
    }

    /**
     * @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
     */
    public void afterPhase(PhaseEvent event) {
        // Do nothing.
    }

}

Define the SetFocusListener as follows in the faces-config.xml:


    <lifecycle>
        <phase-listener>net.balusc.webapp.SetFocusListener</phase-listener>
    </lifecycle>

That's all, folks!

Back to top

Copyright - GNU General Public License

(C) December 2007, BalusC

Tuesday, December 11, 2007

Validator for multiple fields

Introduction

Validators in JSF are nice. They, however, have its shortcomings. They will by default validate only one field at once. There is no standard way to attach one validator to multiple fields. Although there are some situations where you want this kind of functionality. For example validating the password confirmation field, validating the range of two numeric values (e.g. the one have to be lesser than the other), validating correctness of day, month and year fields, etcetera.

The cleanest solution would be to create a custom component which renders two or more components and use a specific validator for that, but that would involve more work. The easiest solution is to attach the validator to the last component of the group (components are rendered, validated, converted and updated in the same order as you define them in the JSF view) and pass the client ID of the other component(s) as unique f:attribute facet(s) along the last component. Then in the validator you can get the desired component(s) by the client ID using UIViewRoot#findComponent().

Back to top

Basic example

This example demonstrates a basic registration form with one username field and two password fields. The value of the second password field should equal to the value of the first password field before the action method may be invoked. The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra by the way!).

Here is the relevant JSF code. Note the f:attribute of the last password field, its value should contain the client ID of the first password field. Also note that the last password field doesn't have any valuebinding to the backing bean as this is unnecessary in this specific case.

<h:form id="register">
    <h:panelGrid columns="3">
        <h:outputLabel for="username" value="Username" />
        <h:inputText id="username" value="#{myBean.username}" required="true" />
        <h:message for="username" style="color: red;" />

        <h:outputLabel for="password" value="Password" />
        <h:inputSecret id="password" value="#{myBean.password}" required="true" />
        <h:message for="password" style="color: red;" />

        <h:outputLabel for="confirm" value="Confirm password" />
        <h:inputSecret id="confirm" required="true">
            <f:validator validatorId="passwordValidator" />
            <f:attribute name="passwordId" value="register:password" />
        </h:inputSecret>
        <h:message for="confirm" style="color: red;" />

        <h:panelGroup />
        <h:commandButton value="Register" action="#{myBean.register}" />
        <h:message for="register" style="color: green;" />
    </h:panelGrid>
</h:form>

And now the validator code:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class PasswordValidator implements Validator {

    // Actions ------------------------------------------------------------------------------------

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        // Obtain the client ID of the first password field from f:attribute.
        String passwordId = (String) component.getAttributes().get("passwordId");

        // Find the actual JSF component for the client ID.
        UIInput passwordInput = (UIInput) context.getViewRoot().findComponent(passwordId);

        // Get its value, the entered password of the first field.
        String password = (String) passwordInput.getValue();

        // Cast the value of the entered password of the second field back to String.
        String confirm = (String) value;

        // Check if the first password is actually entered and compare it with second password.
        if (password != null && password.length() != 0 && !password.equals(confirm)) {
            throw new ValidatorException(new FacesMessage("Passwords are not equal."));
        }

        // You can even validate the minimum password length here and throw accordingly.
        // Or, if you're smart, calculate the password strength and throw accordingly ;)
    }

}

The appropriate test backing bean look like:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private String username;
    private String password;

    // Actions ------------------------------------------------------------------------------------

    public void register() {

        // Just for debug. Don't do this in real! Hash the password, save to DB and forget it ;)
        System.out.println("Username: " + username);
        System.out.println("Password: " + password);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("register", new FacesMessage("Succes!"));
    }

    // Getters ------------------------------------------------------------------------------------

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Finally the relevant part of the faces-config.xml:


    <validator>
        <validator-id>passwordValidator</validator-id>
        <validator-class>mypackage.PasswordValidator</validator-class>
    </validator>
    
    <managed-bean>
        <managed-bean-name>myBean</managed-bean-name>
        <managed-bean-class>mypackage.MyBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

That's all, folks!

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) December 2007, BalusC

Saturday, December 1, 2007

WhitespaceFilter

Whitespace

Whitespace is used everywhere. It covers spaces, tabs and newlines. It is used to distinguish lexical tokens from each other and also to keep the source code readable for the developer. But in case of HTML over network, whitespace costs bandwidth and therefore in some circumstances also money and/or performance. If you care about the bandwidth usage and/or the money and/or performance, then you can consider to trim off all whitespace of the HTML response. The only con is that it makes the HTML source code at the client side almost unreadable.

You can trim whitespace right in the HTML files (or JSP or JSF or whatever view you're using, as long as it writes plain HTML response), but that would make the source code unreadable for yourself. Better way is to use a Filter which trims the whitespace from the response.

Back to top

Replace response writer

Here is how such a WhitespaceFilter can look like. It is relatively easy, it actually replaces the writer of the HttpServletResponse with a customized implementation of PrintWriter. This implemetation will trim whitespace off from any strings and character arrays before writing it to the response stream. It also take care of any <pre> tags and keep the whitespace of its contents unchanged. However it doesn't care about the CSS white-space: pre; property, because it would involve too much work to check on that (parse HTML, lookup CSS classes, sniff the appropriate style and parse it again, etc). It isn't worth that effort. Just use <pre> tags if you want to use preformatted text ;)

Note that this filter only works on requests which are passed through a servlet which writes the response to the PrintWriter, e.g. JSP and JSF files (parsed by JspServlet and FacesServlet respectively) or custom servlets which uses HttpServletResponse#getWriter() to write output. This filter does not work on requests for plain vanilla CSS, Javascript, HTML files and images and another binary files which aren't written through the PrintWriter, but through the OutputStream. If you want to implement the same thing for the OutputStream, then you'll have to check the content type first if it starts with "text" or not, otherwise binary files would be screwed up. Unfortunately in real (at least, in Tomcat 6.0) the content type is set after the output stream is acquired, thus we cannot determine the content type during acquiring the output stream.

The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1, JSTL 1.2 and JSF 1.2_06.

/*
 * net/balusc/webapp/WhitespaceFilter.java
 * 
 * Copyright (C) 2007 BalusC
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if
 * not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package net.balusc.webapp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 * This filter class removes any whitespace from the response. It actually trims all leading and 
 * trailing spaces or tabs and newlines before writing to the response stream. This will greatly
 * save the network bandwith, but this will make the source of the response more hard to read.
 * <p>
 * This filter should be configured in the web.xml as follows:
 * <pre>
 * &lt;filter&gt;
 *     &lt;description&gt;
 *         This filter class removes any whitespace from the response. It actually trims all
 *         leading and trailing spaces or tabs and newlines before writing to the response stream.
 *         This will greatly save the network bandwith, but this will make the source of the
 *         response more hard to read.
 *     &lt;/description&gt;
 *     &lt;filter-name&gt;whitespaceFilter&lt;/filter-name&gt;
 *     &lt;filter-class&gt;net.balusc.webapp.WhitespaceFilter&lt;/filter-class&gt;
 * &lt;/filter&gt;
 * &lt;filter-mapping&gt;
 *     &lt;filter-name&gt;whitespaceFilter&lt;/filter-name&gt;
 *     &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
 * &lt;/filter-mapping&gt;
 * </pre>
 *
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/12/whitespacefilter.html
 */
public class WhitespaceFilter implements Filter {

    // Actions ------------------------------------------------------------------------------------

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig config) throws ServletException {
        // Nothing to do here :(
    }

    /**
     * @see javax.servlet.Filter#doFilter(
     *      javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException
    {
        // Check type response.
        if (response instanceof HttpServletResponse) {
            // Cast back to HttpServletResponse.
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // Create a new PrintWriter for the HttpServletResponse which trims whitespace.
            PrintWriter trimWriter = createTrimWriter(httpResponse);

            // Wrap the HttpServletResponse with the new PrintWriter.
            HttpServletResponse wrappedResponse = wrapResponse(httpResponse, trimWriter);

            // Continue with filter chain.
            chain.doFilter(request, wrappedResponse);
        } else {
            // Not a HttpServletResponse.
            chain.doFilter(request, response);
        }
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
        // Oh yeah, destroy me!
    }

    // Utility (may be refactored to public utility class) ----------------------------------------

    /**
     * Create a new PrintWriter for the given HttpServletResponse which trims all whitespace.
     * @param response The involved HttpServletResponse.
     * @return A PrintWriter which trims all whitespace.
     * @throws IOException If the OutputStream of the HttpServletResponse cannot be obtained.
     */
    private static PrintWriter createTrimWriter(final HttpServletResponse response)
        throws IOException
    {
        return new PrintWriter(response.getOutputStream()) {

            // We want to override all write() methods and let them write to local StringBuilder.
            private StringBuilder builder = new StringBuilder();

            public void write(int c) {
                builder.append((char) c); // It is actually a char, not an int.
            }

            public void write(char[] chars) {
                builder.append(chars);
            }

            public void write(char[] chars, int offset, int length) {
                builder.append(chars, offset, length);
                this.flush(); // Preflush it.
            }

            public void write(String string) {
                builder.append(string);
            }

            public void write(String string, int offset, int length) {
                builder.append(string, offset, length);
                this.flush(); // Preflush it.
            }

            // Finally override the flush method so that it trims whitespace.
            public void flush() {

                // Lock the local StringBuilder.
                synchronized (builder) {

                    // Prepare.
                    BufferedReader reader = new BufferedReader(new StringReader(builder.toString()));
                    String line = null;
                    boolean preformatted = false;
                    String lineSeparator = System.getProperty("line.separator");

                    try {
                        while ((line = reader.readLine()) != null) {
                            if (preformatted) {
                                // Preformatted text, write line separator and untrimmed line.
                                out.write(lineSeparator + line);
                            } else {
                                // No preformatted text, write trimmed line.
                                out.write(line.trim());
                            }

                            if (line.contains("</pre>")) {
                                // End of preformatted text.
                                preformatted = false;
                            }

                            if (line.contains("<pre")) {
                                // Start of preformatted text.
                                preformatted = true;
                            }
                        }
                    } catch (IOException e) {
                        // We cannot throw IOException here, so wrap it with RuntimeException.
                        // This exception should never occur however.
                        throw new RuntimeException("Cannot read/write line.", e);
                    }

                    // Reset the local StringBuilder.
                    builder = new StringBuilder();

                    // And finally the real flush!
                    super.flush();
                }
            }

        };
    }

    /**
     * Wrap the given HttpServletResponse with the given PrintWriter.
     * @param response The HttpServletResponse of which the given PrintWriter have to be wrapped in.
     * @param writer The PrintWriter to be wrapped in the given HttpServletResponse.
     * @return The HttpServletResponse with the PrintWriter wrapped in.
     */
    private static HttpServletResponse wrapResponse(
        final HttpServletResponse response, final PrintWriter writer)
    {
        return new HttpServletResponseWrapper(response) {

            // Replace the existing PrintWriter by the given PrintWriter.
            public PrintWriter getWriter() throws IOException {
                return writer;
            }

        };
    }

}

WhitespaceFilter configuration in web.xml:


    <filter>
        <description>
            This filter class removes any whitespace from the response. It actually trims all
            leading and trailing spaces or tabs and newlines before writing to the response stream.
            This will greatly save the network bandwith, but this will make the source of the
            response more hard to read.
        </description>
        <filter-name>whitespaceFilter</filter-name>
        <filter-class>net.balusc.webapp.WhitespaceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>whitespaceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

That's all, folks!

Back to top

Copyright - GNU General Public License

(C) December 2007, BalusC