About this Tutorial

This tutorial creates master/detail screens with Java Server Faces (JSF). The JSF implementation used in AppFuse is Mojarra, but you should be able to use any 2.0+ JSF implementation. The list (master) screen will have the ability to sort columns, as well as page 25 records at a time. 

We highly recommend using IntelliJ IDEA when developing web applications in Java. Not only is its Java and JUnit support fantastic, but it has excellent CSS and JavaScript support. Using JRebel with IDEA is likely to provide you with the most pleasant Java development experiences you've ever had.

This tutorial assumes you've created a project with the appfuse-basic-jsf archetype and have already completed the Persistence and Services tutorials. If you're using the appfuse-modular-jsf archetype, please morph your mind into using the web module as the root directory. If you created your project with a different web framework than JSF, you're likely to be confused and nothing will work in this tutorial. (wink)

Table of Contents

  1. Introduction to JSF
  2. Create a PersonListTest
  3. Create a PersonList class that will fetch people
  4. Create persons.xhtml to show search results
  5. Create a PersonFormTest and PersonForm for edit(), save() and delete() methods
  6. Create personForm.xhtml to edit a person
  7. Configure Validation
  8. Create a Canoo WebTest to test browser-like actions
  9. Add link to menu

The code for this tutorial is located in the "tutorial-jsf" module of the appfuse-demos project on GitHub. Use the following command to check it out from Subversion:

svn co https://github.com/appfuse/appfuse-demos/trunk/tutorial-jsf

Introduction to JSF 

JavaServer Faces (JSF) is a component-based, event-driven web framework. The figure below shows how JSF fits into a web application's architecture.

One of JSF's prominent features is the ability to wire client-generated events to server-side event handlers. For example, when a user clicks a link or a button, methods in a class can be called. These methods can be listeners or actions. Listeners typically alter the state of a page-backing Java class or managed bean. They can alter the JSF life cycle, but they do not typically control navigation. Actions are no-argument methods that return a string that signifies where to go next. Returning null from an action means, "Stay on the same page."

If you want a more in-depth learning experience, I suggest you read David Geary's Core JSF. I had it close by my side and used it frequently while integrating JSF into AppFuse. Thanks for the help David and Cay (co-author)!

Create a PersonListTest

Testing is an important part of any application, and testing a JSF application isn't too difficult. In most web frameworks, an "Action" of some sort contains the controller logic. However, with JSF, they're commonly referred to as "Managed Beans". The methods within these beans are called actions. This tutorial is not going to teach you a whole lot about how JSF works, but it will get you up and running quickly with it.

Managed Beans in JSF follow a command pattern similar to the one used by Struts 2 Actions. Since these classes are simple POJOs and don't depend on the Servlet API at all, they're very easy to test. AppFuse does most of the hard work for you, meaning it initializes the FacesContext and allows you to grab pre-configured managed beans. Shale has a testing framework that AppFuse uses to simplify testing JSF beans even more.

Create a PersonListTest.java class in src/test/java/**/webapp/action:

package org.appfuse.tutorial.webapp.action;

import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import static org.junit.Assert.*;

public class PersonListTest extends BasePageTestCase {
    private PersonList bean;
    @Autowired @Qualifier("personManager")
    private GenericManager<Person, Long> personManager;

    @Override
    @Before
    @SuppressWarnings("unchecked")
    public void onSetUp() {
        super.onSetUp();
        bean = new PersonList();
        bean.setPersonManager(personManager);

        // add a test person to the database
        Person person = new Person();
        person.setFirstName("Abbie Loo");
        person.setLastName("Raible");
        personManager.save(person);
    }

    @Override
    @After
    public void onTearDown() {
        super.onTearDown();
        bean = null;
    }

    @Test
    public void testSearch() throws Exception {
        assertTrue(bean.getPersons().size() >= 1);
        assertFalse(bean.hasErrors());
    }
}

This class will not compile until you create the PersonList class.

Create a PersonList class that will fetch people 

Create a PersonList.java file in src/main/java/**/webapp/action:

package org.appfuse.tutorial.webapp.action;

import org.appfuse.dao.SearchException;
import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.List;

public class PersonList extends BasePage implements Serializable {
    private GenericManager<Person, Long> personManager;

    @Autowired
    public void setPersonManager(@Qualifier("personManager") GenericManager<Person, Long> personManager) {
        this.personManager = personManager;
    }

    public PersonList() {
        setSortColumn("id"); // sets the default sort column
    }

    public List<Person> getPersons() {
        return sort(personManager.getAll());
    }
}

The sort() method (called in the getPersons() method) is in the BasePage class. This method makes it possible for you to sort columns in the UI.

Now you need to register this as a managed-bean with JSF. Spring's SpringBeanFacesELResolver (defined in faces-config.xml) makes this easy to do. Simple add the following annotations at the class-level:

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("request")
@Component("personList")
public class PersonList extends BasePage implements Serializable {

If you run mvn test -Dtest=PersonListTest, your test should pass.

Nice!

{span:style=color:green}BUILD SUCCESS\\
Total time:  7.414s{span}

Create persons.xhtml to show search results 

Create a src/main/webapp/persons.xhtml page to display the list of people:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui" xmlns:ui="http://java.sun.com/jsf/facelets">

<ui:composition template="/layouts/default.xhtml">
    <ui:define name="title">#{text['personList.title']}</ui:define>
    <ui:param name="menu" value="PersonMenu"/>
    <ui:define name="bodyId">personList</ui:define>
    <ui:define name="body">

        <div class="col-sm-10">
            <h2>#{text['personList.heading']}</h2>

            <p>#{text['personList.message']}</p>

            <h:form id="editPerson">
            <div id="actions" class="btn-group">
                <h:commandButton value="#{text['button.add']}" action="add" id="add" immediate="true" styleClass="btn btn-primary"/>
                <h:commandButton value="#{text['button.done']}" action="home" id="cancel" immediate="true" styleClass="btn btn-default"/>
            </div>

            <p:dataTable id="persons" var="person" value="#{personList.persons}"
                         sortBy="#{personList.sortColumn}" paginator="true" rows="25"
                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
                         rowsPerPageTemplate="5,10,15">
                <p:column>
                    <f:facet name="header">
                        <p:column sortBy="id">
                            <h:outputText value="#{text['person.id']}" />
                        </p:column>
                    </f:facet>
                    <h:commandLink action="#{personForm.edit}" value="#{person.id}">
                        <f:param name="id" value="#{person.id}"/>
                        <f:param name="from" value="list"/>
                    </h:commandLink>
                </p:column>
                <p:column>
                    <f:facet name="header">
                        <p:column sortBy="firstName">
                            <h:outputText value="#{text['person.firstName']}" />
                        </p:column>
                    </f:facet>
                    <h:outputText value="#{person.firstName}" escape="true"/>
                </p:column>
                <p:column>
                    <f:facet name="header">
                        <p:column sortBy="lastName">
                            <h:outputText value="#{text['person.lastName']}" />
                        </p:column>
                    </f:facet>
                    <h:outputText value="#{person.lastName}" escape="true"/>
                </p:column>
            </p:dataTable>
            </h:form>
        </div>
    </ui:define>
</ui:composition>
</html> 

Add a navigation-rule in src/main/webapp/WEB-INF/faces-config.xml that routes clicking on the Add button to the personForm.xhtml you'll create in step 5.

<navigation-rule>
    <from-view-id>/persons.xhtml</from-view-id>
    <navigation-case>
        <from-outcome>add</from-outcome>
        <to-view-id>/personForm.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>

Open src/main/resources/ApplicationResources.properties and add i18n keys/values for the various "person" properties:

# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information

Run mvn jetty:run and open http://localhost:8080/persons in your browser. Login with admin/admin and you should see a screen similar to the figure below.

Security settings for AppFuse specify that most url-patterns should be protected (except for /signup and /passwordHint). This guarantees that clients must go through JSF's FacesServlet to get to view pages.

If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:

body#pageName element.class { background-color: blue } 

Create a PersonFormTest and PersonForm for edit(), save() and delete() methods 

To start creating the detail screen, create a PersonFormTest.java class in src/test/java/**/webapp/action:

package org.appfuse.tutorial.webapp.action;

import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import static org.junit.Assert.*;

public class PersonFormTest extends BasePageTestCase {
    private PersonForm bean;
    @Autowired @Qualifier("personManager")
    private GenericManager<Person, Long> personManager;

    @Override
    @Before
    public void onSetUp() {
        super.onSetUp();
        bean = new PersonForm();
        bean.setPersonManager(personManager);
    }

    @Override
    @After
    public void onTearDown() {
        super.onTearDown();
        bean = null;
    }

    @Test
    public void testAdd() throws Exception {
        Person person = new Person();
        // set required fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        bean.setPerson(person);

        assertEquals("list", bean.save());
        assertFalse(bean.hasErrors());
    }

    @Test
    public void testEdit() throws Exception {
        log.debug("testing edit...");
        bean.setId(1L);

        assertEquals("edit", bean.edit());
        assertNotNull(bean.getPerson());
        assertFalse(bean.hasErrors());
    }

    @Test
    public void testSave() {
        bean.setId(1L);

        assertEquals("edit", bean.edit());
        assertNotNull(bean.getPerson());
        Person person = bean.getPerson();

        // update fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        bean.setPerson(person);

        assertEquals("edit", bean.save());
        assertFalse(bean.hasErrors());
    }

    @Test
    public void testRemove() throws Exception {
        Person person = new Person();
        person.setId(2L);
        bean.setPerson(person);

        assertEquals("list", bean.delete());
        assertFalse(bean.hasErrors());
    }
}

Nothing will compile at this point; you need to create the PersonForm that you're referring to in this test.

In src/main/java/**/webapp/action, create a PersonForm.java class that extends AppFuse's BasePage. Populate it with the following code:

package org.appfuse.tutorial.webapp.action;

import java.io.Serializable;
import org.appfuse.tutorial.model.Person;
import org.appfuse.service.GenericManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("request")
@Component("personForm")
public class PersonForm extends BasePage implements Serializable {
    private GenericManager<Person, Long> personManager;
    private Person person = new Person();
    private Long id;

    @Autowired
    public void setPersonManager(@Qualifier("personManager") GenericManager<Person, Long> manager) {
        this.personManager = manager;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String delete() {
        personManager.remove(person.getId());
        addMessage("person.deleted");

        return "list";
    }

    public String edit() {
        if (id == null) {
            id = new Long(getParameter("id"));
        }
        person = personManager.get(id);

        return "edit";
    }

    public String save() {
        boolean isNew = (person.getId() == null || person.getId() == 0);
        personManager.save(person);

        String key = (isNew) ? "person.added" : "person.updated";
        addMessage(key);

        if (isNew) {
            return "list";
        } else {
            return "edit";
        }
    }
}

You might notice a number of keys in this file - "person.deleted", "person.added" and "person.updated". These are all keys that need to be in your i18n bundle (ApplicationResources.properties). You should've added these at the beginning of this tutorial. If you want to customize these messages, to add a person's name or something, simply add a {0} placeholder in the key's message and then use the addMessage(key, stringtoreplace) method.

Near the top of faces-config.xml, add navigation-rules that controls page flow after actions are executed:

<navigation-rule>
    <from-view-id>/personForm.xhtml</from-view-id>
    <navigation-case>
        <from-outcome>cancel</from-outcome>
        <to-view-id>/persons.xhtml</to-view-id>
        <redirect/>
    </navigation-case>
    <navigation-case>
        <from-outcome>edit</from-outcome>
        <to-view-id>/personForm.xhtml</to-view-id>
    </navigation-case>
    <navigation-case>
        <from-outcome>list</from-outcome>
        <to-view-id>/persons.xhtml</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>

You'll also need to modify the navigation-rule you added for persons.xhtml so an "edit" result routes to the personForm.xhtml as well. The first part of the following XML should already exist in faces-config.xml.

<navigation-rule>
    <from-view-id>/persons.xhtml</from-view-id>
    <navigation-case>
        <from-outcome>add</from-outcome>
        <to-view-id>/personForm.xhtml</to-view-id>
    </navigation-case>
    <navigation-case>
        <from-outcome>edit</from-outcome>
        <to-view-id>/personForm.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>

If you look at your PersonFormTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (src/test/resources/sample-data.xml). Adding it at the bottom should work - order is not important since it (currently) does not relate to any other tables. If you already have this table, make sure the 2nd record exists so testRemove() doesn't fail.

<table name='person'>
  <column>id</column>
  <column>first_name</column>
  <column>last_name</column>
  <row>
    <value>1</value>
    <value>Matt</value>
    <value>Raible</value>
  </row>
  <row>
    <value>2</value>
    <value>Bob</value>
    <value>Johnson</value>
  </row>
</table>

DbUnit loads this file before you run any tests, so these records will be available to your PersonFormTest class. Save all your files and run the tests in PersonFormTest using the command mvn test -Dtest=PersonFormTest.

{span:style=color:green}BUILD SUCCESS\\
Total time: 7.772s{span}

Create personForm.xhtml to edit a person 

Create a src/main/webapp/personForm.xhtml page to display the form:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui" xmlns:ui="http://java.sun.com/jsf/facelets">

<ui:composition template="/layouts/default.xhtml">
    <ui:define name="title">#{text['personDetail.title']}</ui:define>
    <ui:param name="menu" value="PersonMenu"/>

    <ui:define name="body">
        <script type="text/javascript">
            var msgDelConfirm = "<h:outputFormat value="#{text['delete.confirm']}"><f:param value="Person"/></h:outputFormat>";
        </script>

        <div class="col-sm-3">
            <h2>#{text['personDetail.heading']}</h2>
        </div>

        <div class="col-sm-7">
            <h:form id="personForm" styleClass="well">
                <h:inputHidden value="#{personForm.person.id}" id="id"/>
                <div class="form-group">
                    <h:outputLabel styleClass="control-label" for="firstName" value="#{text['person.firstName']}"/>
                    <h:inputText id="firstName" value="#{personForm.person.firstName}" styleClass="form-control"
                                 maxlength="50"/>
                </div>
                <div class="form-group">
                    <h:outputLabel styleClass="control-label" for="lastName" value="#{text['person.lastName']}"/>
                    <h:inputText id="lastName" value="#{personForm.person.lastName}" styleClass="form-control"
                                 maxlength="50"/>
                </div>
                <div class="form-group">
                    <h:commandButton value="#{text['button.save']}" action="#{personForm.save}"
                                     id="save" styleClass="btn btn-primary"/>
                    <h:commandButton value="#{text['button.delete']}" action="#{personForm.delete}"
                                     id="delete" styleClass="btn btn-default"
                                     onclick="return confirmMessage(msgDelConfirm)"/>
                    <h:commandButton value="#{text['button.cancel']}" action="cancel" immediate="true"
                                     id="cancel" styleClass="btn btn-default"/>
                </div>
            </h:form>
        </div>
    </ui:define>
</ui:composition>
</html>

Run mvn jetty:run, open your browser to http://localhost:8080/persons, and click on the Add button.

Fill in the first name and last name fields and click the Save button. This should route you to the list screen, where a success message flashes and the new person displays in the list.

The src/main/webapp/common/messages.jsp file in AppFuse renders the success message in this screen. This file is included in layouts/default.xhtml. It also handles displaying validation errors:

<%@ include file="/common/taglibs.jsp" %>
<c:if test="${not empty errors}">
    <div class="alert alert-danger alert-dismissable">
        <a href="#" data-dismiss="alert" class="close">&times;</a>
        <c:forEach var="error" items="${errors}">
            <c:out value="${error}"/><br />
        </c:forEach>
    </div>
    <c:remove var="errors"/>
</c:if>

<c:if test="${not empty messages}">
    <div class="alert alert-success alert-dismissable">
        <a href="#" data-dismiss="alert" class="close">&times;</a>
        <c:forEach var="msg" items="${messages}">
            <c:out value="${msg}"/><br />
        </c:forEach>
    </div>
    <c:remove var="messages" scope="session"/>
</c:if>

Configure Validation

Implementing validation with JSF is quite simple. For server-side validation, all you need to do is add required="true" to your <h:inputText> tags. Other validations (besides required) can be specified by nested validation tags in your inputText tags.

In the personForm.xhtml you created, there is no validation-related information. Therefore, perform the following steps to make firstName and lastName required fields.

  1. Add required="true" to the inputText fields in personForm.xhtml. This makes these fields required on the server-side.
  2. Add a <p:message/> tag after the input field with a "for" attribute that matches the input field's id. 

Below is the revised contents of the <h:form> tag with these changes. Replace the <h:form> in your personForm.xhtml with these changes.

<h:form id="personForm" styleClass="well">
    <h:inputHidden value="#{personForm.person.id}" id="id"/>
    <div class="form-group">
        <h:outputLabel styleClass="control-label" for="firstName" value="#{text['person.firstName']}"/>
        <h:inputText id="firstName" value="#{personForm.person.firstName}" styleClass="form-control"
                     required="true" maxlength="50"/>
        <p:message for="firstName"/>
    </div>
    <div class="form-group">
        <h:outputLabel styleClass="control-label" for="lastName" value="#{text['person.lastName']}"/>
        <h:inputText id="lastName" value="#{personForm.person.lastName}" styleClass="form-control"
                     required="true" maxlength="50"/>
        <p:message for="lastName"/>
    </div>
    <div class="form-group form-actions">
        <h:commandButton value="#{text['button.save']}" action="#{personForm.save}"
                         id="save" styleClass="btn btn-primary"/>
        <h:commandButton value="#{text['button.delete']}" action="#{personForm.delete}"
                         id="delete" styleClass="btn btn-default"
                         onclick="return confirmMessage(msgDelConfirm)"/>
        <h:commandButton value="#{text['button.cancel']}" action="cancel" immediate="true"
                         id="cancel" styleClass="btn btn-default"/>
    </div>
</h:form>

After saving all your files and running mvn jetty:run, validation should kick in when you try to save this form. To test, go to http://localhost:8080/personForm and try to add a new user with no first or last name. You should see the following:

Create a Canoo WebTest to see browser-like actions

The next (optional) step in this tutorial is to create a Canoo WebTest to test your UI. This step is optional, because you can run the same tests manually through your browser. Regardless, it's a good idea to automate as much of your testing as possible.

You can use the following URLs to test the different actions for adding, editing and saving a user.

There is a WebTest Recorder Firefox plugin that allows you to record your tests, rather than manually writing them.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open src/test/resources/web-tests.xml and add the following XML. You'll notice that this fragment has a target named ''PersonTests'' that runs all the related tests.

<target name="PersonTests"
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
    <echo>Successfully ran all Person UI tests!</echo>
</target>

<!-- Verify the people list screen displays without errors -->
<target name="SearchPeople" description="Tests search for and displaying all people">
    <webtest name="searchPeople">
        &config;
        <steps>
            &login;
            <invoke description="View Persons List" url="/persons"/>
            <verifytitle description="we should see the personList title"
                         text=".*${personList.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson" description="Tests editing an existing Person's information">
    <webtest name="editPerson">
        &config;
        <steps>
            &login;
            <invoke description="View Persons List" url="/persons"/>
            <clicklink label="1"/>
            <verifytitle description="we should see the personDetail title"
                         text=".*${personDetail.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Edit a person and then save -->
<target name="SavePerson" description="Tests editing and saving a person">
    <webtest name="savePerson">
        &config;
        <steps>
            &login;
            <invoke description="View Person List" url="/persons"/>
            <clicklink label="1"/>
            <verifytitle description="we should see the personDetail title"
                         text=".*${personDetail.title}.*" regex="true"/>
            <!-- update some of the required fields -->

            <clickbutton label="${button.save}" description="Click Save"/>
            <verifytitle description="Page re-appears if save successful"
                         text=".*${personDetail.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.updated}"/>
        </steps>
    </webtest>
</target>

<!-- Add a new Person -->
<target name="AddPerson" description="Adds a new Person">
    <webtest name="addPerson">
        &config;
        <steps>
            &login;
            <invoke description="View Person List" url="/persons"/>
            <clickbutton description="Click the 'Add' button" label="${button.add}"/>
            <verifytitle description="we should see the personDetail title"
                         text=".*${personDetail.title}.*" regex="true"/>

            <!-- enter required fields -->
            <setinputfield description="set firstName" name="personForm:firstName" value="Jack"/>
            <setinputfield description="set lastName" name="personForm:lastName" value="Raible"/>
            <clickbutton label="${button.save}" description="Click button 'Save'"/>
            <verifytitle description="Person List appears if save successful"
                         text=".*${personList.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.added}"/>
        </steps>
    </webtest>
</target>

<!-- Delete existing person -->
<target name="DeletePerson" description="Deletes existing Person">
    <webtest name="deletePerson">
        &config;
        <steps>
            &login;
            <invoke description="View Person List" url="/persons"/>
            <clicklink label="2"/>
            <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifyNoDialogResponses/>
            <verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target in src/test/resources/web-test.xml.

<target name="run-all-tests" 
    depends="Login,Logout,PasswordHint,Signup,UserTests,StaticPages,WebServices,DWR,FileUpload,PersonTests"
    description="Call and executes all test cases (targets)"/>

After adding this, you should be able to run mvn verify -Pitest and have these tests execute. If this command results in "BUILD SUCCESSFUL" - nice work!

Add link to menu

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in src/main/webapp/home.xhtml:

<li>
    <a href="persons">#{text['menu.viewPeople']}</a>
</li>

Where menu.viewPeople is an entry in src/main/resources/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to src/main/webapp/WEB-INF/menu-config.xml:

<Menu name="PersonMenu" title="menu.viewPeople" page="/persons"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add the PersonMenu just before the AdminMenu in src/main/webapp/common/menu.jsp.

<%@ include file="/common/taglibs.jsp"%>
<menu:useMenuDisplayer name="Velocity" config="navbarMenu.vm" permissions="rolesAdapter">
<div class="collapse navbar-collapse" id="navbar">
<ul class="nav navbar-nav">
    <c:if test="${empty pageContext.request.remoteUser}">
        <li class="active">
            <a href="<c:url value="/login"/>"><fmt:message key="login.title"/></a>
        </li>
    </c:if>
    <menu:displayMenu name="Home"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PersonMenu"/>
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="Logout"/>
</ul>
</div>
</menu:useMenuDisplayer>

Now if you run mvn jetty:run and go to http://localhost:8080/home, you should see something like the screenshot below.

Notice that there is a new link in your main screen (from home.xhtml) and on the top in your menu bar (from menu.jsp).

That's it!
You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and JSF - Congratulations!

Because it's so much fun to watch tests fly by and success happen, run all your tests again using mvn install -Pitest.

Happy Day!

{span:style=color:green} BUILD SUCCESS\\
Total time: 52.444s{span}