In my previous Blog I mentioned that setting a server-side chosen language should be done through a life-cycle listener (phase listener), better than using a <f:view locale="#{userSettings.language}"/>
statement in each view. So let me prove that this actually works.
Talking about JSF lifecycle phases means talking about how the server processes one HTTP request. There are several articles on the Internet that describe this more or less understandable. The best I found is the one of BalusC, a prominent JSF persona supporting OmniFaces and Stackoverflow. You should really read his article, it makes things technically very clear. Also the technique how you can make a JSF component accessible in a Java bean via the binding="#{myBean.myTextField}"
attribute is interesting.
Here are the the phases with my comments:
UiComponent
derivates like HtmlInputText
), get it from cache and set the request's locale into it. If not, all phases are skipped until Render Response. value
expressions. faces-config.xml
<navigation-rule>. Any of these phases except Restore View can branch off into Render Response, when errors occur, or FacesContext.renderResponse()
gets called. Furhter the immediate
attribute on fields and commands can move things into Apply Request Values phase, and change how conversion and validation is done.
You can find Java sources in com.sun.faces.lifecycle.LifecycleImpl
, you can try to understand their implementation, or debug it.
public class LifecycleImpl extends Lifecycle {
....
private Phase response = new RenderResponsePhase();
private Phase[] phases = {
null, // ANY_PHASE placeholder, not a real Phase
new RestoreViewPhase(),
new ApplyRequestValuesPhase(),
new ProcessValidationsPhase(),
new UpdateModelValuesPhase(),
new InvokeApplicationPhase(),
response
};
....
}
Implementation base for this example is the "JSF Internationalization" source from my previous Blog.
Take out the <f:view locale="#{userSettings.language}"/>
statement from both
<h:head>
<title>JSF Internationalization</title>
<!-- f:view locale="#{userSettings.language}"/-->
</h:head>
Now switching the language should not work any more. Try it out. We will fix this by adding a Java PhaseListener
.
The class UserSettings
from the previous example stays unchanged. We add a new session-scoped class, implementing the JSF interface PhaseListener
, in same package, where we refer to the userSettings
bean for getting the current language (locale). (I could not make dependency injection work with JSF 2.3, so excuse the ugly CDI.current()
workaround!)
package fri.jsf;
import java.io.Serializable;
import java.util.*;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.spi.CDI;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.inject.Inject;
@SessionScoped
public class I18nPhaseListener implements Serializable, PhaseListener
{
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
@Override
public void beforePhase(PhaseEvent event) {
if (event.getFacesContext().getViewRoot() != null &&
event.getPhaseId() == PhaseId.RENDER_RESPONSE) // initial call doesn't have RESTORE_VIEW phase
event.getFacesContext().getViewRoot().setLocale(getLocale());
}
@Override
public void afterPhase(PhaseEvent event) {
if (event.getFacesContext().getViewRoot() != null &&
(event.getPhaseId() == PhaseId.RESTORE_VIEW || // prepare life-cycle with recent language
event.getPhaseId() == PhaseId.UPDATE_MODEL_VALUES)) // establish any new language
event.getFacesContext().getViewRoot().setLocale(getLocale());
}
private Locale getLocale() {
return getUserSettings().getLocale();
}
// @Inject
// private UserSettings userSettings;
private UserSettings getUserSettings() {
//return userSettings;
return CDI.current().select(UserSettings.class).get();
}
}
To make this work we must register the class as phase listener in faces-config.xml
:
<?xml version="1.0"?>
<faces-config
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
version="2.3">
<application>
<resource-bundle>
<base-name>messages</base-name>
<var>translations</var>
</resource-bundle>
<locale-config>
<default-locale>de</default-locale>
<supported-locale>de</supported-locale>
<supported-locale>fr</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
</application>
<lifecycle>
<!-- Register i18n phase listener: -->
<phase-listener>fri.jsf.I18nPhaseListener</phase-listener>
</lifecycle>
</faces-config>
Now launch the application and check that it works:
The question rises whether we need to know about JSF life cycle phases. Generally I think no, but global special cases may need it. Read about the POST-Redirect-GET pattern implemented generically by BalusC.
ɔ⃝ Fritz Ritzberger, 2019-08-29