Wednesday, September 27, 2006

Debug JSF lifecycle

Listen and debug JSF lifecycle phases

The JSF lifecycle will be explained and debugged here. We'll also check what happens if you add immediate="true" to the UIInput and UICommand and what happens when a ConverterException and ValidatorException will be thrown.

The JSF lifecycle contains 6 phases:

  1. Restore view
  2. Apply request values
  3. Process validations
  4. Update model values
  5. Invoke application
  6. Render response

You can use a PhaseListener to trace the phases of the JSF lifecycle and execute some processes where required. But you also can use a PhaseListener to debug the phases to see what is happening in which phase. Here is a basic example of such a LifeCycleListener:

package mypackage;

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

public class LifeCycleListener implements PhaseListener {

    public void beforePhase(PhaseEvent event) {
        System.out.println("BeforePhase: " + event.getPhaseId());
    }

    public void afterPhase(PhaseEvent event) {
        System.out.println("AfterPhase: " + event.getPhaseId());
    }

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }

}

Add the following lines to the faces-config.xml to activate the LifeCycleListener.

<lifecycle>
    <phase-listener>mypackage.LifeCycleListener</phase-listener>
</lifecycle>

This produces like the following in the system output:

BeforePhase: RESTORE_VIEW 1
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: PROCESS_VALIDATIONS 3
AfterPhase: PROCESS_VALIDATIONS 3
BeforePhase: UPDATE_MODEL_VALUES 4
AfterPhase: UPDATE_MODEL_VALUES 4
BeforePhase: INVOKE_APPLICATION 5
AfterPhase: INVOKE_APPLICATION 5
BeforePhase: RENDER_RESPONSE 6
AfterPhase: RENDER_RESPONSE 6

Back to top

Basic debug example

To trace all phases of the JSF lifecycle, here is some sample code which represents simple JSF form with a fake converter and validator and the appropriate backing bean. The code sample can be used to give us more insights into the phases of the JSF lifecycle, to understand it and to learn about it.

The test JSF file: test.jsp

<h:form>
    <h:inputText
        binding="#{myBean.inputBinding}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
    >
        <f:converter converterId="myConverter" />
        <f:validator validatorId="myValidator" />
    </h:inputText>
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
    />
    <h:outputText
        binding="#{myBean.outputBinding}"
        value="#{myBean.outputValue}"
    />
    <h:messages />
</h:form>

The fake converter: MyConverter.java

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class MyConverter implements Converter {

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println("MyConverter getAsObject: " + value);
        return value;
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        System.out.println("MyConverter getAsString: " + value);
        return (String) value;
    }

}

The fake validator: MyValidator.java

package mypackage;

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

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
    }

}

The backing bean: MyBean.java

package mypackage;

import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.event.ValueChangeEvent;

public class MyBean {

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

    private HtmlInputText inputBinding;
    private String inputValue;
    private HtmlOutputText outputBinding;
    private String outputValue;

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

    public void action() {
        outputValue = inputValue;
        log("succes");
    }

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

    public HtmlInputText getInputBinding() {
        log(inputBinding);
        return inputBinding;
    }

    public String getInputValue() {
        log(inputValue);
        return inputValue;
    }

    public HtmlOutputText getOutputBinding() {
        log(outputBinding);
        return outputBinding;
    }

    public String getOutputValue() {
        log(outputValue);
        return outputValue;
    }

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

    public void setInputBinding(HtmlInputText inputBinding) {
        log(inputBinding);
        this.inputBinding = inputBinding;
    }

    public void setInputValue(String inputValue) {
        log(inputValue);
        this.inputValue = inputValue;
    }

    public void setOutputBinding(HtmlOutputText outputBinding) {
        log(outputBinding);
        this.outputBinding = outputBinding;
    }

    // Listeners ----------------------------------------------------------------------------------

    public void inputChanged(ValueChangeEvent event) {
        log(event.getOldValue() + " to " + event.getNewValue());
    }

    // Helpers ------------------------------------------------------------------------------------

    private void log(Object object) {
        String methodName = new Exception().getStackTrace()[1].getMethodName();
        System.out.println("MyBean " + methodName + ": " + object);
    }
}

The minimal faces configuration: faces-config.xml

<converter>
    <converter-id>myConverter</converter-id>
    <converter-class>mypackage.MyConverter</converter-class>
</converter>
<validator>
    <validator-id>myValidator</validator-id>
    <validator-class>mypackage.MyValidator</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>
Back to top

The first call

The first call should output at least:

BeforePhase: RESTORE_VIEW 1
AfterPhase: RESTORE_VIEW 1
BeforePhase: RENDER_RESPONSE 6
MyBean getInputBinding: null
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean getInputValue: null
MyBean getOutputBinding: null
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
As the UIViewRoot is empty, there is nothing to do in here.

2. Apply request values.
This phase is skipped because there is no form submit.

3. Process validations.
This phase is skipped because there is no form submit.

4. Update model values.
This phase is skipped because there is no form submit.

5. Invoke application.
This phase is skipped because there is no form submit.

6. Render response.
The components are created for the first time and stored in the UIViewRoot and set in the eventual component bindings. If the component binding getters outputs predefinied components and not null, then those will be used. The values to be shown are retrieved from the value binding getters in the backing bean. If the values aren't set yet, they defaults to null.

Back to top

The refresh

After doing a refresh in the browser in the same user session, the output should look like at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: RENDER_RESPONSE 6
MyBean getInputValue: null
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
This phase is skipped because there is no form submit.

3. Process validations.
This phase is skipped because there is no form submit.

4. Update model values.
This phase is skipped because there is no form submit.

5. Invoke application.
This phase is skipped because there is no form submit.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If the values aren't set yet, they defaults to null.

Back to top

The form submit

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
AfterPhase: PROCESS_VALIDATIONS 3
BeforePhase: UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
AfterPhase: UPDATE_MODEL_VALUES 4
BeforePhase: INVOKE_APPLICATION 5
MyBean action: succes
AfterPhase: INVOKE_APPLICATION 5
BeforePhase: RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
Nothing to see here. In real the values are retrieved from the form and set in the relevant components in the UIViewRoot, for example inputBinding.setValue("test").

3. Process validations.
The values are retrieved as objects from the components, passed through the converter getAsObject() method and validated by the validator. Finally the valueChangeListener is invoked.

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean.

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Back to top

Add immediate="true" to UIInput only

Extend the h:inputText in the test.jsp with immediate="true":

    ...
    <h:inputText
        binding="#{myBean.inputBinding}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
        immediate="true"
    >
    ...

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: PROCESS_VALIDATIONS 3
AfterPhase: PROCESS_VALIDATIONS 3
BeforePhase: UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
AfterPhase: UPDATE_MODEL_VALUES 4
BeforePhase: INVOKE_APPLICATION 5
MyBean action: succes
AfterPhase: INVOKE_APPLICATION 5
BeforePhase: RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
The values are retrieved from the form, passed through the converter getAsObject() method, validated by the validator and the valueChangeListener is invoked. Finally the converted and validated values will be set in the relevant components in the UIViewRoot, for example inputBinding.setValue("test"). This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText.

3. Process validations.
Nothing to see here. The conversion and validation is already processed in the Apply request values phase, before the values being put in the components. This is due to the immediate="true" in the h:inputText.

4. Update model values.
The values set in the components in the UIViewRoot will now be set in the value binding setters of the backing bean.

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Back to top

Add immediate="true" to UICommand only

Extend the h:commandButton in the test.jsp with immediate="true" (don't forget to remove from h:inputText):

    ...
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
        immediate="true"
    />
    ...

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
MyBean action: succes
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: RENDER_RESPONSE 6
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
The real processing of the form submission happens here. This happens in this phase instead of the Invoke application phase due to the immediate="true" in the h:commandButton. The UIInput components which don't have immediate="true" set will be ignored completely and therefore their values will not be converted and validated.

3. Process validations.
This phase is skipped due to the immediate="true" in the h:commandButton.

4. Update model values.
This phase is skipped due to the immediate="true" in the h:commandButton.

5. Invoke application.
This phase is skipped due to the immediate="true" in the h:commandButton.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean.
Take care: as the Update model values phase is skipped, the value bindings aren't been set and the value binding getters will return null. The UIInput components which don't have immediate="true" set will be ignored completely and therefore you cannot retrieve the values from the component or value bindings anyway. They will all remain the previous values.

Back to top

Add immediate="true" to UIInput and UICommand

Extend the h:inputText as well as the h:commandButton in the test.jsp with immediate="true":

    ...
    <h:inputText
        binding="#{myBean.inputBinding}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
        immediate="true"
    >
    ...
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
        immediate="true"
    />
    ...

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
MyBean action: succes
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: RENDER_RESPONSE 6
MyConverter getAsString: test
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
The values are retrieved from the form, passed through the converter getAsObject() method, validated by the validator, the valueChangeListener is invoked and the converted and validated values will be set in the relevant components in the UIViewRoot, for example inputBinding.setValue("test"). This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText. Finally the real processing of the form submission happens here. This happens in this phase instead of the Invoke application phase due to the immediate="true" in the h:commandButton.

3. Process validations.
This phase is skipped due to the immediate="true" in the h:commandButton.

4. Update model values.
This phase is skipped due to the immediate="true" in the h:commandButton.

5. Invoke application.
This phase is skipped due to the immediate="true" in the h:commandButton.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.
Take care: as the Update model values phase is skipped, the value bindings aren't been set and the value binding getters will return null. But the values are still available by the component bindings which are been set in phase Apply request values. In this case you can retrieve the input value from inputBinding.getValue() in the action() method. The new input value is also available by the ValueChangeEvent in the inputChanged() method.

Back to top

Conversion error

Let's see what happens if a conversion error will occur. Change the getAsObject() method of MyConverter.java as follows (and remove the immediate="true" from the test.jsp file):

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class MyConverter implements Converter {

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println("MyConverter getAsObject: " + value);
        throw new ConverterException("Conversion failed.");
    }

    ...
}

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
AfterPhase: PROCESS_VALIDATIONS 3
BeforePhase: RENDER_RESPONSE 6
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
Nothing to see here. In real the values are retrieved from the form and set in the relevant components in the UIViewRoot, for example inputBinding.setValue("test").

3. Process validations.
The values are retrieved as objects from the components and passed through the converter getAsObject() method, where a ConverterException is thrown. The validator and the valueChangeListener are bypassed. The lifecycle will proceed to the Render response phase immediately.

4. Update model values.
This phase is skipped due to the ConverterException.

5. Invoke application.
This phase is skipped due to the ConverterException.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expecting the values for which a ConverterException has occurred.

Back to top

Validation error

Let's see what happens if a validation error will occur. Change the validate() method of MyValidator.java as follows (and remove the immediate="true" from the test.jsp file and revert MyConverter.java back to original):

package mypackage;

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

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
        throw new ValidatorException(new FacesMessage("Validation failed."));
    }

}

The form submit with the value "test" entered should output at least:

BeforePhase: RESTORE_VIEW 1
MyBean setInputBinding: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputBinding: javax.faces.component.html.HtmlOutputText@13bbca56
AfterPhase: RESTORE_VIEW 1
BeforePhase: APPLY_REQUEST_VALUES 2
AfterPhase: APPLY_REQUEST_VALUES 2
BeforePhase: PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
MyValidator validate: test
AfterPhase: PROCESS_VALIDATIONS 3
BeforePhase: RENDER_RESPONSE 6
MyBean getOutputValue: null
AfterPhase: RENDER_RESPONSE 6

1. Restore view.
The components are restored in the UIViewRoot and set in the eventual component bindings.

2. Apply request values.
Nothing to see here. In real the values are retrieved from the form and set in the relevant components in the UIViewRoot, for example inputBinding.setValue("test").

3. Process validations.
The values are retrieved as objects from the components, passed through the converter getAsObject() method and validated by the validator, where a ValidatorException is thrown. The valueChangeListener is bypassed. The lifecycle will proceed to the Render response phase immediately.

4. Update model values.
This phase is skipped due to the ValidatorException.

5. Invoke application.
This phase is skipped due to the ValidatorException.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expecting the values for which a ValidatorException has occurred.

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) September 2006, BalusC