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.
HTTP Request Processing Phases
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:
- Restore View:
If there is a JSF component tree for the HTTP request (UiComponent
derivates likeHtmlInputText
), get it from cache and set the request's locale into it. If not, all phases are skipped until Render Response. - Apply Request Values:
Retrieve submitted values from the HTTP request, "decode" these values to the components in tree. - Process Validations:
Presentation (view) logic, call optional converters, validators, event listeners. - Update Model Values:
Write values from components to the Java backing beans as implemented in the component'svalue
expressions. - Invoke Application:
Business logic, call actions implemented in command-buttons. Actions return page identifiers, or identifiers that serve for navigation viafaces-config.xml
<navigation-rule>. - Render Response:
"Encode" the component tree of the current, new or delegate XHTML page into an HTML response page.
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 }; .... }
Internationalization via PhaseListener
Implementation base for this example is the "JSF Internationalization" source from my previous Blog.
Modify XHTML views
Take out the <f:view locale="#{userSettings.language}"/>
statement from both
- i18n.xhtml
- i18n-response.xhtml
<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
.
Introduce 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:
Conclusion
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.
Keine Kommentare:
Kommentar veröffentlichen