Blog-Archiv

Donnerstag, 29. August 2019

JSF Lifecycle Phases

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:

  1. Restore View:
    If there is a JSF component tree for the HTTP request (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.

  2. Apply Request Values:
    Retrieve submitted values from the HTTP request, "decode" these values to the components in tree.

  3. Process Validations:
    Presentation (view) logic, call optional converters, validators, event listeners.

  4. Update Model Values:
    Write values from components to the Java backing beans as implemented in the component's value expressions.

  5. Invoke Application:
    Business logic, call actions implemented in command-buttons. Actions return page identifiers, or identifiers that serve for navigation via faces-config.xml <navigation-rule>.

  6. 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.




Montag, 26. August 2019

JSF Internationalization

Internationalization is important for applications that are used all over the world. In this Blog I'd like to present a way how this can be done with JSF.

Application Screenshots

Here are screenshots how the example application will look like. It features a language dropdown-list that translates the whole UI into the selected language when changed, without triggering a submit. Available languages are restricted to English, French and German in this example.

After choosing English:

Mind that the text in "Your Name" was not erased by the page-translation.
Mind further that the labels inside the language dropdown have been translated too.

After choosing French:

When clicking "Submit":

We see that the language setting survived the current HTTP request, because the URL changed, and this means the follower page was displayed through faces-redirect=true, so the page was not built as response to the first POST request that submitted the language.

File Structure

This is a Maven project, built in Eclipse like described in my first JSF-Blog. We don't need JPA here.

Following files can be copied unchanged into this new jsfI18n project:

  • pom.xml (just replace the Maven artifactId by jsfI18n)
  • ApplicationFacesConfig.java
  • beans.xml
  • web.xml

Sources

JSF Configuration

The JSF faces-config.xml in src/main/webapp/WEB-INF/ looks like this now:

<?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>
      <!-- filename without _language and .properties extensions -->
      <base-name>messages</base-name> 
      <!-- name of the globally available variable -->
      <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>

</faces-config>

<resource-bundle>

The <resource-bundle> element inside <application> must give the path and basename of the property-files (e.g. messages_de.properties). If the files were in a sub-folder, e.g. src/main/resources/i18n/bundles/, this would be <base-name>i18n.bundles.messages</base-name>.

The <resource-bundle> also must give the name of the global variable that should hold all properties as key/value map. In this case the variable name is "translations", we will meet this again in XHTML views where it is used as translations['key'] or translations.key.

You could also load the bundle directly in an XHTML view:

<h:head>
  <f:loadBundle basename="messages" var="translations"/>
</h:head>

This may be the better solution for a big application where loading all resources initially would require lots of time and memory. Thus holding them per XHTML view is recommended, although text resources should be shared and reused. Unfortunately traditional property files do not provide an include-mechanism.

<locale-config>

The <locale-config> element contains languages supported by the application, and the fallback language (default-locale) in case the browser doesn't send one in HTTP header. In Java you can access this by FacesContext.getCurrentInstance().getApplication().getDefaultLocale() and FacesContext.getCurrentInstance().getApplication().getSupportedLocales().

The JSF view language will be the one the browser sends. (The browser defaults to the operating-system's language if not configured explicitly.) That means, I will see an English UI in case my browser's language is English, even when the default-locale was set to "de" like here. When we want the language to be the configured default-locale, we need to tell the JSF XHTML views to set their locale from some Java bean that we must provide. But let's look at the text resource bundles before.

Translations

Here are the resource bundle files, located in the src/main/resources directory. They are "traditional" property-files, in ISO-8859-1 encoding.

The left-side key will be used by the developer in the JSF XHTML view (e.g. "Your_Name"), the user will see the translation on the right side (e.g. "Votre nom").

messages.properties (English, default)

Your_Name = Your Name
Your_Language = Your Language
Language.de = German
Language.en = English
Language.fr = French
submit = Submit

Hello = Hello
with_language = with language

messages_de.properties (German)

Your_Name = Dein Name
Your_Language = Deine Sprache
Language.de = Deutsch
Language.en = Englisch
Language.fr = Französisch
submit = Absenden

Hello = Hallo
with_language = mit Sprache

messages_fr.properties (French)

Your_Name = Votre nom
Your_Language = Votre langue
Language.de = Allemand
Language.en = Anglais
Language.fr = Français
submit = Soumettre

Hello = Bonjour
with_language = avec langue

In Java, you can load resource bundles by calling ResourceBundle.getBundle("messages").

Java

This is located in src/main/java/ in a package fri/jsf/, to be created.

We need a session-scoped Java bean that gives us access to a language setting, here in the form of a String like "en" for English.

UserSettings.java

package fri.jsf;

import java.io.Serializable;
import java.util.*;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;

@Named
@SessionScoped
public class UserSettings implements Serializable
{
    private Locale locale;
    private List<String> languages;
    
    public String getLanguage() {
        return getLocale().getLanguage();
    }
    public void setLanguage(String language) {
        locale = Locale.forLanguageTag(language);
    }
    public List<String> getPossibleLanguages() {
        if (languages == null)    {
            languages = new ArrayList<>();
            Iterator<Locale> supportedLocales = FacesContext.getCurrentInstance().getApplication().getSupportedLocales();
            while (supportedLocales.hasNext())
                languages.add(supportedLocales.next().getLanguage());
        }
        return languages;
    }

    private Locale getLocale()    {
        if (locale == null)
            locale = FacesContext.getCurrentInstance().getApplication().getDefaultLocale();
            // in real life this should come from database
        return (locale != null) ? locale : Locale.getDefault();
    }
}

The @Named annotation is new in JSF 2.3, it was @ManagedBean in older versions. Mind that this bean is session-scoped and thus needs to be serializable. Would it contain fields, also these needed to be serializable, recursively.

The initial language comes from JSF FacesContext, and also the list of possible languages, both refer to <locale-config> in faces-config.xml.

The bean exposes the language as string taken from the private property locale, and it stores any change to that very property. Thus the language setting gets lost when the session ends.

XHTML

Here are the JSF XHTML views, located in src/main/webapp/, using the UserSettings Java bean as userSettings, which is a naming convention (→ first letter lowered) when the bean has no explicit name in its @Named annotation:

i18n.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">

    <h:head>
        <title>JSF Internationalization</title>
        <f:view locale="#{userSettings.language}"/>
    </h:head>
    
    <h:body>
        <h:form>
            <h:panelGrid columns="2">
                <h:outputLabel id="langLabel" for="lang">#{translations.Your_Language}</h:outputLabel>
                <h:selectOneMenu id="lang" value="#{userSettings.language}">
                    <f:selectItems
                        value="#{userSettings.possibleLanguages}" 
                        var="thisLanguage"
                        itemValue="#{thisLanguage}"
                        itemLabel="#{translations['Language.' += thisLanguage]}"
                    />
                    <f:ajax render="langLabel lang nameLabel submitButton" />
                    <!-- perform AJAX call when this field changes,
                        send values of fields with ids listed in "execute",
                        on response update fields with ids listed in "render"  -->
                </h:selectOneMenu>
                
                <h:outputLabel id="nameLabel" for="name">#{translations['Your_Name']}</h:outputLabel>
                <h:inputText id="name" value="#{userName}" />
            </h:panelGrid>
            
            <h:commandButton id="submitButton" value="#{translations['submit']}" 
                    action="i18n-response?faces-redirect=true"/>            
        </h:form>
        
    </h:body>
</html>

Following is the response view, named in action attribute of the command-button:

i18n-response.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
      
    <h:head>
        <title>JSF Internationalization Done!</title>
        <f:view locale="#{userSettings.language}"/>
    </h:head>
    
    <h:body>
        #{translations['Hello']} 
        #{userName} 
        #{translations['with_language']} 
        #{translations['Language.'.concat(userSettings.language)]}!
    </h:body>

</html>

The answer to the question how the view knows about the language setting in UserSettings is in the <f:view/> element:

<f:view locale="#{userSettings.language}"/>

That's the way how you can explicitly set a language, directly in the view. (But you'd have to do this in any page, thus I think a JSF lifecycle listener would be a better solution.)

How to Implement Translations

JSF translations are coded in the view, and normally are quite simple. Just access the global map with a language-neutral key ('Your_Language' is in messages*.properties):

<h:outputLabel ....>#{translations['Your_Language']}</h:outputLabel>

This is the same as:

<h:outputLabel ....>#{translations.Your_Language}</h:outputLabel>

JSF expressions are normally enclosed in #{}, called deferred evaluation syntax. If you use ${}, called immediate evaluation syntax, it gets processed by the JSP engine, not the JSF lifecycle. Both forms can be XML-element content as well as XML-attribute content.

Sometimes translations get tricky and hard to understand. Look at the language-labels, and how they are built together using the JSF expression language (EL).

<f:selectItems
    value="#{userSettings.possibleLanguages}" 
    var="language"
    itemValue="#{language}"
    itemLabel="#{translations['Language.'.concat(language)]}"
/>

The <f:selectItems> element is nested inside the <h:selectOneMenu> and determines how to process the list-return of the UserSettings.getPossibleLanguages() call done in value. It defines a variable (var) named "language" which will always hold the current list item. Then it defines that the value of any item (itemValue) will be what is in the variable named "language". Finally the label (itemLabel) represents what the user will see inside the dropdown list:

The expression #{translations['Language.'.concat(language)]} first reads the variable "language", resulting e.g. in "en". Then it executes the concat() function of the String object 'Language.', passing the "language" value as parameter. Result is "Language.en", which is then used as key of the "translations" map. Look at the text resource bundle and you will find "Language.en" on the left side.

Isn't there a more readable way to solve this? I don't know any. The needed identifier is known at runtime only and must be built together by applying some rule.

Here is a variant, but even less readable:

<f:selectItems
    ....
    itemLabel="#{translations['Language.' += language]}"
/>

The += operator is in JSF EL what + is for String-context in Java, it concatenates left and right side.

How to Parameterize Text

Assumed the welcome-message is completely packed into a property like this:

userWelcome = Hello {0} with language {1}!

Then you can display it in the JSF view like this:

<h:outputFormat value="#{msgs.userWelcome}">
  <f:param value="#{userName}"/>
  <f:param value="#{translations['Language.'.concat(userSettings.language)]}"/>
</h:outputFormat>

Output would be the same as shown in the screenshot at start of this article.

How to Update the UI without Submit

Beside form submits, an HTML page can update itself using AJAX (Asynchronous JavaScript XML). The JSF <f:ajax> API allows a list of element-ids that should trigger the AJAX-call (inside "execute" attribute), and a list of element-ids that are updated on return of the call (inside "render" attribute). Both lists are space-separated.

<f:ajax render="langLabel lang nameLabel submitButton" />

I left out the execute attribute because the default trigger is the parent element, which is the targeted dropdown-menu. All elements having an id in "lang langLabel nameLabel submitButton" would be updated when the AJAX call returns.

In real world there would be much more UI-labels to be translated, so this list would be quite long. As it is not possible to use * or any wildcards in the "render" attribute, this solution is a maintenance problem!

Scoped Variables

This is not about translations any more now, it's about a variable scoping problem. Look at the result page text substitutions in i18n-response.xhtml:

    <h:body>
        #{translations['Hello']} 
        #{userName} 
        #{translations['with_language']} 
        #{translations['Language.'.concat(userSettings.language)]}!
    </h:body>

Where is the name "FritzTheCat" from the text field? It is referenced as #{userName} but the welcome says "Bonjour avec langue Français" - no "FritzTheCat"!
This name got lost because the variable userName is request-scoped. When you remove the faces-redirect=true directive in i18n.xhtml, the name would be there, because the first HTTP POST request would build the response.

How to fix this: either create a property "name" in UserSettings.java and use it as userSettings.name in XHTML, or create/use some other bean for the name that is not request-scoped.

Conclusion

I found it easy to internationalize a JSF application. Nevertheless some translation code tends to become unreadable. Immediate translation via AJAX may be a maintenance problem, but it is not really needed.




Samstag, 24. August 2019

JPA 2.1 with JSF 2.3 Example Application

This is about setting up an Eclipse project featuring JSF 2.3 and JPA 2.1. Generally it is the same procedure as in my latest Blog, but I encountered some Eclipse configuration problems when I tried to integrate JPA, so I will repeat the setup steps before introducing the example sources.

Goal is a web-page with a text-field where you can enter your favourite greetings. On submit (ENTER key or button click) your input will be saved to a database table called GREETING. As response your input will be displayed in a list below the input field. It will look like the following:

The H2 database console shows the persisted inputs:

Tools

I used the newest J2EE Eclipse 2019-06 (4.12.0) as Java IDE, and the newest Tomcat 9 as web-server.

The database is H2, in server-mode, that means I need to start it separately before I run the web-server in Eclipse. H2 has a nice startup script that opens a browser tab where you can run SQL queries and commands as user "sa" with password "".

Setup

You may encounter different Eclipse error messages during the following setup. Menu "Project" - "Clean" often helps, or project context menu "Refresh", or "Maven" - "Update Project". But you could choose to ignore all errors until you created the whole application, that's what I would recommend. Else please refer to the web by entering the error message in some search engine and following the instructions there. I spent a lot of time with that but can't document all here.

Following shows the file structure I am about to generate:

1) Create a "New Maven Project" without archetype

Eclipse menu "File" - "New" - "Project", choose "Maven" - "Maven Project", click "Next".
Activate "Create a simple project" checkbox, click "Next".
Enter your Maven-coordinates, I used "fri.jsf" as group-id and "jsfJpa" as artifact-id, mind that these will be in the generated pom.xml file that will be edited in next step.
Choose "war" for packaging.
Click "Finish".

2) Edit Maven pom.xml

Put following into the generated pom.xml file in project root directory. Adapt groupId and artifactId to the ones you entered in previous step when generating the project.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  
  <groupId>fri.jsf</groupId>
  <artifactId>jsfJpa</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <packaging>war</packaging>
  
  <description>JPA binding for JSF</description>

  <dependencies>
    <!-- START JSF deps -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.faces</artifactId>
      <version>2.3.9</version>
    </dependency>
    
    <dependency>
      <groupId>org.jboss.weld.servlet</groupId>
      <artifactId>weld-servlet-shaded</artifactId>
      <version>3.1.2.Final</version>
    </dependency>
    <!-- END JSF deps -->

    <!-- START JPA deps -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>5.4.4.Final</version>
    </dependency>

    <!-- JDBC driver for some database like H2 -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.4.199</version>
      <scope>runtime</scope>
    </dependency>
    <!-- END JPA deps -->
    
    <!-- Test scope -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13-beta-3</version>
      <scope>test</scope>
    </dependency>

  </dependencies>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      
    </plugins>

  </build>

</project>

This uses Hibernate as JPA provider ("hibernate-entitymanager"). You could choose any other by simply replacing the dependency. Further you should replace the H2 database driver dependency with the one that fits to your local database.

Do not yet run a Maven build!

3) In "Project Properties", click on "Project Facets"

Activate

  • "Dynamic Web Module" with version 4.0,
  • "Java" with 1.8,
  • "JavaServer Faces" with 2.3
  • "JPA" with 2.1

Click "Apply and Close". It may happen that you have to first activate "Dynamic Web Module" 4.0, then "Apply and Close", then open the dialog again and activate JSF 2.3.

In the end you need the state as shown in this screenshot:

If something doesn't work out here, Eclipse writes to .settings/org.eclipse.wst.common.project.facet.core.xml, you can try to edit that file and "Refresh" the project after.

4) Run Maven Build

In Project Explorer, select the project and right-mouse click context menu "Maven" - "Update Project". This will build the project and refresh Eclipse after.

5) Configure Eclipse Project

All of the following happens in project "Properties" dialog that you can launch with context menu on the project in Project Explorer.

Configure JPA to "Discover annotated classes automatically", and "Disable Library Configuration":

Also "Disable Library Configuration" for "Java Server Faces" under "Project Facets":

Configure Maven to NOT "Resolve dependencies from Workspace":

Make sure "Maven Dependencies" are in JavaBuild Path:


6) Configuration Files for JSF

Expand the path src/main/webapp/WEB-INF/ and add following files there, overwriting existing ones:

web.xml

<?xml version="1.0"?>
<web-app 
        xmlns="http://java.sun.com/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-app_4_0.xsd" 
        version="4.0">
    
    <servlet>
    <servlet-name>JSF JPA Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    </servlet>
  
    <servlet-mapping>
    <servlet-name>JSF JPA Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
  
</web-app>

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">

</faces-config>

beans.xml

<?xml version="1.0"?>
<beans
       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/beans_2_0.xsd"
       version="2.0"
       bean-discovery-mode="all">

</beans>

In src/main/java/ create a package fri/jsf/ (or any package you like) and add following Java source (the @FacesConfig annotation is needed by JSF 2.3):

ApplicationFacesConfig.java

package fri.jsf;

import javax.enterprise.context.ApplicationScoped;
import javax.faces.annotation.FacesConfig;

@FacesConfig
@ApplicationScoped
public class ApplicationFacesConfig
{
}

7) Configuration File for JPA

I need to tell the JDBC driver how to connect to my database. In src/main/resources/META-INF/ add following file, again overwriting the existing one (it could happen that an already existing one is in src/main/java/META-INF/, then overwrite this one):

persistence.xml

<?xml version="1.0"?>
<persistence
        xmlns="http://xmlns.jcp.org/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
        version="2.1">

    <persistence-unit name="JsfJpaPU">
    
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        
        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test" />
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="javax.persistence.jdbc.user" value="jsfjpa" />
            <property name="javax.persistence.jdbc.password" value="jsfjpa" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            
            <!-- Scan for classes annotated by @Entity on startup, instead of hardcoding all classes here -->
            <property name="hibernate.archive.autodetection" value="class" />
            
            <!-- Auto-drop and -recreate the database tables on startup (not the database schema!) -->
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
            <!-- Never do this in production mode! -->
            
            <!-- Display all database statements on console log -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            
        </properties>
        
    </persistence-unit>

</persistence>

I use JPA property names as much as possible here, but some have to be Hibernate because JPA does not cover their functionality. You would have to change vendor-specific property names when using another JPA-provider.

Application Sources

Now I create the example application. Following files should go to src/main/java/. In package fri/jsf I create a sub-package jpa and put following sources into it:

Greeting.java (the entity-bean, mapped to a database table)

package fri.jsf.jpa;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(schema = "jsfjpa")
public class Greeting implements Serializable
{
  @Id
  @GeneratedValue
  private long id;
    
  private String text;
    
  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }

  public String getText() {
    return text;
  }
  public void setText(String text) {
    this.text = text;
  }
}

Mind that no public setter or getter in an entity must be final (see JPA specification).

Mind further that the entity will be saved into an explicitly given database schema "jsfjpa", defined by the @Table annotation. Declaring this here may not be a good idea (because I would have to duplicate the schema name on every entity), but I want to try out if the table actually goes to that schema.

GreetingModel.java (the DAO)

package fri.jsf.jpa;

import java.util.Collection;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class GreetingModel
{
  public static final String JPA_UNIT = "JsfJpaPU"; // from META-INF/persistence.xml
 
  private static final EntityManager em = 
      Persistence.createEntityManagerFactory(JPA_UNIT).createEntityManager();

  public Collection<Greeting> readGreetings() {
    final EntityTransaction tx = em.getTransaction();
    tx.begin();
    Collection<Greeting> greetings = em.createQuery("select g from "+Greeting.class.getName()+" g", Greeting.class).getResultList();
    tx.commit();
    return greetings;
  }
 
  public void saveGreeting(Greeting greeting) {
    final EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.persist(greeting);
    tx.commit();
  }
}

The model encapsulates persistence access. Mind that JPQL (JPA query language) requires the "select" keyword (unlike Hibernate-QL), and it requires an alias name for each entity-type occurring in the query.

The following belongs to JSF, not JPA, so put it into package src/main/java/fri/jsf:

GreetingController.java ("backing bean")

package fri.jsf;

import java.util.Collection;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import fri.jsf.jpa.Greeting;
import fri.jsf.jpa.GreetingModel;

@Named
@RequestScoped
public class GreetingController
{
  private Greeting greeting = new Greeting();
 
  public Greeting getGreeting() {
    return greeting;
  }

  public Collection<Greeting> getGreetings() {
    return getModel().readGreetings();
  }

  public String addGreeting() {
    String followerPage = "";
    if (greeting.getText().trim().length() > 0) {
      followerPage = saveGreeting(greeting);
      greeting = new Greeting();
    }
    return followerPage;
  }
 
  private String saveGreeting(Greeting greeting) {
    getModel().saveGreeting(greeting);
    return "hello-world?faces-redirect=true";
    // POST-redirect-GET to avoid duplicates on browser-refresh by user
  }
 
  private GreetingModel getModel() {
    return new GreetingModel();
  }
}

The controller connects model and view.

I have a model with an entity, I have a controller, now I will create a JSF-view in src/main/webapp:

hello-world.xml

!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>

  <h:head>
      <title>Hello JSF + JPA</title>
  </h:head>
 
  <h:body>
  
    <h:form>
      <h:outputLabel for="greeting">Your Greeting:</h:outputLabel>
      <h:inputText id="greeting" value="#{greetingController.greeting.text}"/>
   
      <h:commandButton value="Submit" action="#{greetingController.addGreeting()}"/>
      <!--  This will submit every field inside the h:form -->
    </h:form>
  
    <ui:repeat value="#{greetingController.greetings}" var="greeting">
      <li>#{greeting.text}</li>
    </ui:repeat>
  
     
  </h:body>

</html>

This view has a form with one labeled input field for the greeting and a submit-button (h:commandButton). On bottom it has an output "repeater" that renders the list of greetings done so far.

The "value" attribute of the h:inputText field defines the content of the text field (like it is with HTML INPUT). It is bound to the expression value="#{greetingController.greeting.text}" (JSF expression language). This refers to a bean of class "GreetingController" (defaults to "greetingController" with first letter upper-case, or name of the Java-bean). JSF instantiates it (HTTP-request scoped!) and accesses its property "greeting", and from here the property "text", which is represented by the Greeting.getText() Java method. Because the Greeting.text property has no default value, the text field is empty.

On submit of the h:form, the text field's current content is written to GreetingController.greeting.setText(} method through a HTTP-POST. The action="#{greetingController.addGreeting()}" of the submit-button then calls the GreetingController.addGreeting(} method that saves the Greeting built in GreetingController to the according database table.

The response is the return of addGreeting(}, which is the same page but with a new Greeting instance, so the text field appears empty again. Mind that the follower-page is determined by a method call to a Java class, nevertheless it also could be hardcoded in the XHTML view.

The output-repeater on bottom renders all greetings through the binding value="#{greetingController.greetings}". The "var" attribute names a variable that is used to render a Greeting inside the loop body: #{greeting.text} - "greeting" is a variable name in this case.

The POST-Redirect-GET pattern (see GreetingController.saveGreeting) is to avoid duplicate greetings caused by the user clicking browser-"Refresh" after submitting. In JSF you can implement that by appending ?faces-redirect=true to the action response, easy to do as long as the response page needs no parameters. Try to reproduce the duplication by removing the faces-redirect=true, then submitting a greeting, then doing a browser "Refresh". Although the text field is empty, the previous input will be added again. It is in the recent POST used by the browser to redisplay the page. Mind that this "double submit" problem is not solved when the second POST arrives before the browser received the response of the first POST.

Build Application

Finally you must build the project with Maven. Select the project in Project Explorer, right click context menu "Maven" - "Update Project". I hope all Eclipse errors are gone now!

Run Application

First you need to run your H2 database server by executing its bin/h2.sh (UNIX) or bin/h2.bat (WINDOWS) script. This will open a browser tab, use this database view to launch following statements:

CREATE USER jsfjpa PASSWORD 'jsfjpa'

In persistence.xml I configured a database user "jsfjpa" with password "jsfjpa". Thus I must actually create it now. Mind that this is a H2-specific statement!

CREATE SCHEMA jsfjpa AUTHORIZATION jsfjpa

JPA won't create the schema given in Greeting.java (see @Table annotation) automatically. But it is configured to drop and create all tables on startup (property javax.persistence.schema-generation.database.action in persistence.xml), so it will fail and throw an exception when I don't provide the schema now.

Now go to Eclipse and select hello-world.xml in Project Explorer and right click context menu "Run As" - "Run on Server". You probably will have to select Tomcat 9 from a list if you have installed several servers. Finally you should see Eclipse open a browser page with the JSF/JPA application like shown in the screenshot at start of this article.

You can add greetings now, and check that they actually get stored to the database.

Conclusion

JSF is a big and complex thing, so this article got too long. I find the JSF ways quite elegant, but I have a bad feeling when I see that navigation (determining a follower page) can be done in all of Java-controller, XHTML-view, and faces-config.xml.