5. The View and Controller Layers

The user interface (UI) consists of a start page for navigating to the data management UI pages and one UI page for each object type and data management use case. All these UI pages are defined in the form of JSF view files in subfolders of the WebContent/views/ folder. We create the Main Menu page index.xhtml in the subfolder WebContent/views/app with the following content:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="..." xmlns:h="..." xmlns:f="...">
  <ui:composition template="/WEB-INF/templates/page.xhtml">
    <ui:define name="content">
      <h2>Public Library</h2>
      <h:button value="Manage publishers" 
          outcome="/views/publishers/index" />
      <h:button value="Manage books" outcome="/views/books/index" />
      <h:form>
        <h:commandButton value="Clear database" 
            action="#{appCtrl.clearData()}" />
        <h:commandButton value="Create test data" 
            action="#{appCtrl.createTestData()}" />
      </h:form>
    </ui:define>
  </ui:composition>
</html>

It creates the menu buttons which provide the redirects to corresponding views for each of the management pages. Additionally, we need to create a corresponding AppController class which is responsible for the creation and deletion of the test data. The controller is used to combine code of the Publisher.createTestData and Publisher.clearData methods with Book.createTestData and Book.clearData methods as follows:

@ManagedBean( name="appCtrl")
@SessionScoped
public class AppController {
  @PersistenceContext( unitName="UnidirAssocApp")
  private EntityManager em;
  @Resource()
  UserTransaction ut;

  public String clearData() {
    try {
      Book.clearData( em, ut);
      Publisher.clearData( em, ut);
    } catch ( Exception e) {
      e.printStackTrace();
    }
    return "index";
  }
  public String createTestData() {
    try {
      Publisher.createTestData( em, ut);
      Book.createTestData( em, ut);
    } catch ( Exception e) {
      e.printStackTrace();
    }
    return "index";
  }
}

The deletion of Book and Publisher data must be done in a particular order for avoiding referential integrity violations (books have to be deleted first, before their publishers are deleted).

5.1. Initialize the app

Since the code runs in a Tomcat container, the initialization is made internally by the container.

5.2. Show information about associated objects in the List Objects use case

In our example we have only one reference property, Book::publisher, which is functional. For showing information about the optional publisher of a book in the list books use case, the corresponding cell in the HTML table is filled with the name of the publisher, if there is any:

<ui:composition template="/WEB-INF/templates/page.xhtml">
  <ui:define name="content">
    <h:dataTable value="#{bookCtrl.books}" var="b">
      ...
      <h:column>
        <f:facet name="header">Publisher</f:facet>
        #{b.publisher.name}
      </h:column>
    </h:dataTable>
    <h:button value="Main menu" outcome="index" />
  </ui:define>
</ui:composition>

Notice the cascade call used in the #{b.publisher.name} JSF expression: accessing the property name of the publisher which is a property of the book instance b.

5.3. Allow selecting associated objects in the create and update use cases

In this section, we discuss how to create and update an object which has reference properties, such as a Book object with the reference property publisher.

5.3.1. Create the Object-to-String converter

HTML require string or number values to populate forms. In our case, the property publisher of the Book class is an object reference. JSF allows the mapping from object and string and back by using face converters, which are classes annotated with @FacesConverter and implementing the javax.faces.convert.Converter interface:

@FacesConverter( value="pl.model.converter.PublisherConverter")
public class PublisherConverter implements Converter {
  @Override
  public Object getAsObject( FacesContext context, 
      UIComponent component, String value) {
    PublisherController ac = FacesContext
        .getCurrentInstance()
        .getApplication()
        .evaluateExpressionGet( context, "#{publisherCtrl}",
            PublisherController.class);
    EntityManager em = ac.getEntityManager();
    if (value == null) return null;
    else return em.find( Publisher.class, value);
  }
  @Override
  public String getAsString( FacesContext context, 
      UIComponent component, Object value) {
    if (value == null) return null;
    else if (value instanceof Publisher) {
      return ((Publisher) value).getName();
    }
    else return null;
  }
}

It defines two methods, getAsObject and getAsString, responsible for the two mappings. It allows to define custom representations which can be used not only in connection with HTML forms but also allows serialization which can be used with display views. For being able to extract an object from database we need an EntityManager which can be obtained from our controller class instances (e.g., the managed bean publisherCtrl). The instance of the controller can be obtained by using the FacesContext singletone as shown above:

PublisherController ac = FacesContext.getCurrentInstance()
     .getApplication()
     .evaluateExpressionGet( context, "#{publisherCtrl}", 
          PublisherController.class);

In addition, we need to add a getEntityManager method in the controller class as follows:

@ManagedBean( name="publisherCtrl")
@SessionScoped
public class PublisherController {
  @PersistenceContext( unitName="UnidirAssocApp")
  private EntityManager em;
  @Resource()
  UserTransaction ut;

  public EntityManager getEntityManager() {
    return this.em;
  }
  ...
}

JSF needs to compare two publisher instances, when the publisher list has to auto-select the current publisher, in the update book use case. For this reason, the Publisher model class needs to implement the equals method. In our case, two publishers are "one and the same", if the values of their name property are equal:

@Override
public boolean equals( Object obj) {
  if (obj instanceof Publisher) {
    Publisher publisher = (Publisher) obj;
    return ( this.name.equals( publisher.name));
  } else return false;
}

5.3.2. Write the view code

To allow selection of objects which have to be associated with the currently edited object from a list in the create and update use cases, an HTML selection list (a select element) is populated with the instances of the associated object type with the help of JSF h:selectOneMenu element. The WebContent/views/books/create.xhtml file is updated as follows:

<ui:composition template="/WEB-INF/templates/page.xhtml">
 <ui:define name="content">
  <h:form id="createBookForm" styleClass="pure-form pure-form-aligned">
   <h:panelGrid columns="3">
    ...
    <h:outputLabel for="publisher" value="Publisher:" />
    <h:selectOneMenu id="publisher" value="#{book.publisher}">
     <f:selectItem itemValue="" itemLabel="---" />
     <f:selectItems value="#{publisherCtrl.publishers}" var="p" 
                    itemLabel="#{p.name}" itemValue="#{p}" />
     <f:converter converterId="pl.model.converter.PublisherConverter" />
    </h:selectOneMenu>
    <h:message for="publisher" errorClass="error" />
    </h:panelGrid>
     <h:commandButton value="Create" 
         action="#{bookCtrl.add( book.isbn, book.title, 
         book.year, book.publisher.name)}" />
  </h:form>
 </ui:define>
</ui:composition>

The object-to-string and back converter is specified by using the f:converter JSF element and its associated @converterId attribute. The value of this attribute must be the same with the one specified by the value property of the annotation @FacesConverter used when the converter class is defined, e.g., @FacesConverter( value="pl.model.converter.PublisherConverter"). In general, the converter simple class name should be used (e.g., PublisherConverter), or the complete fully qualified name, if there is a risk of naming conflicts (e.g., pl.model.converter.PublisherConverter). We recommend to use the fully qualified name in all cases.

The #{publisherCtrl.publishers} JSF expression results in calling the getPublishers method of the PublisherController class, which is responsible to return the list of available publishers:

public List<Publisher> getPublishers() {
  return Publisher.getAllObjectsAll( em);
}

The creation of the book is obtained by using the h:commandButton JSF element. It results in the invocation of the add method of the BookController class:

public String add( String isbn, String title, Integer year, 
    Publisher publisher) {
  try {
    Book.add( em, ut, isbn, title, year, publisher);
    // Enforce clearing the form after creating the Book row.
    // Without this, the form will show the latest completed data
    FacesContext facesContext = FacesContext.getCurrentInstance();
    facesContext.getExternalContext().getRequestMap().remove( "book");
  } catch ( EntityExistsException e) {
    try {
      ut.rollback();
    } catch ( Exception e1) {
      e1.printStackTrace();
    }
    e.printStackTrace();
  } catch ( Exception e) {
    e.printStackTrace();
  }
  return "create";
}

It simply calls the Book.add method, responsible with the creation of a new row in the books table.

The code for the update book use case is very similar, the only important modification being the addition of the select element (i.e., using the h:selectOneMenu JSF element), which allows to select which book to update:

<ui:composition template="/WEB-INF/templates/page.xhtml">
 <ui:define name="content">
  <h:form id="createBookForm" styleClass="pure-form pure-form-aligned">
   <h:panelGrid columns="3">
    ...
    <h:outputLabel for="selectBook" value="Select book: " />
    <h:selectOneMenu id="selectBook" value="#{book.isbn}">
     <f:selectItem itemValue="" itemLabel="---" /> 
     <f:selectItems value="#{bookCtrl.books}" var="b" 
         itemValue="#{b.isbn}" itemLabel="#{b.title}" /> 
     <f:ajax listener="#{bookCtrl.refreshObject( book)}" 
         render="isbn title year publisher"/>
    </h:selectOneMenu>
    <h:message for="selectedBook"/>
     ...
   </h:panelGrid>
   <h:commandButton value="Create" action="#{bookCtrl.add( 
       book.isbn, book.title, book.year, book.publisher.name)}" />
  </h:form>
 </ui:define>
</ui:composition>