corner imagecorner image
FeaturesPluginsDocs & SupportCommunityPartners

Modifying Java Classes in the Generated JavaServer Faces CRUD Application

This tutorial is the second in a three-part series that demonstrates how to modify the ConsultingAgency web application that was originally created in the Generating a JavaServer Faces CRUD Application from a Database tutorial. The series is intended as a guide to show how you can use the code generated by the IDE as the foundation of your project and then modify or add code to implement specific functionality that is required by the project. The first tutorial in the series contains a description of the project's requirements and the changes that need to be made.

This series is organized so that each document focuses on modifying a specific part of the generated code.

The first tutorial in the series demonstrated how to modify some of the JSP pages to optimize the pages for consultants that are logged in. In this tutorial you will modify some of the generated Java classes to implement the methods that are invoked by the JSP pages.

In this document you will do the following:

  • Add a class that implements a phase listener
  • Add methods to the JPA controller classes and JSF controller classes
  • Add new navigation rules.

The third tutorial in the series is optional and demonstrates how to add some Ajax validation to some of the fields.

Contents

Content on this page applies to NetBeans IDE 6.5 and 6.7

To follow this tutorial, you need the following software and resources.

Software or Resource Version Required
NetBeans IDE 6.7/6.5 Java
Java Development Kit (JDK) Version 6 or version 5
GlassFish Application Server V2
MySQL database server version 5.x
Consulting Agency Database Download
ConsultingAgency Application Download
JSF Extensions (Ajax) plugin (optional) Beta Update Center

Notes:

  • This tutorial uses the source code of the ConsultingAgency application. If you have not completed the first tutorial in the series Modifying JSP Pages in the Generated JavaServer Faces CRUD Application where you start modifying the generated code, you may want to do that now. Alternatively, you can download the project that is used as the starting point for this series as a .zip archive or get the project sources from Kenai.
  • This project uses some experimental JSF libraries to add Ajax functionality described in the third tutorial in the series. The JSF libraries are bundled with NetBeans IDE 6.5. If you are using NetBeans IDE 6.7, you will need to install the JSF Extensions (Ajax) plugin from the NetBeans Beta Update Center. Alternatively, you can remove the source code that implements the Ajax functionality and skip the third document in the series. For details, see the section on Removing Ajax Functionality. For more about the functionality provided by the plugin, see the section Enabling Ajax in the Application in the Generating a JavaServer Faces CRUD Application from a Database tutorial.

Getting the Sources

This tutorial uses the source code of the ConsultingAgency application as its base. To complete the tutorials in this series you need to have the sources for the ConsultingAgency project on your local system. You can get the project sources in any of the following ways.

Notes.

  • When you open the project, you might see a warning about a missing project resource if the JSF Extensions (Ajax) plugin is not installed. You need to install the plugin to add the JSF libraries required by the project. Alternatively, you can remove the requirement for the plugin by following the steps in the section Removing Ajax Functionality. For more about the functionality provided by the plugin, see the section Enabling Ajax in the Application in the Generating a JavaServer Faces CRUD Application from a Database tutorial.
  • When you open the project, you might see a warning about a missing server. To resolve the problem, right-click the project node in the Project window, choose Resolve Missing Server Problem and then specify your local GlassFish v2.x server instance in the dialog box.
  • If you use the zip archive or get the sources from Kenai, you need to use the MySQL database server and create and populate the consult database as described in the Generating a JavaServer Faces CRUD Application from a Database tutorial.

Getting Project Sources from Kenai

If you are using NetBeans IDE 6.7, you can get the sources from the remote repository and open the project in the IDE. You can find the sources in the repository of the Consulting Agency Solution Project. The repository contains the project sources for each of the tutorials in this series.

To get the sources for this tutorial from the repository on Kenai, perform the following steps.

  1. Choose Team > Kenai > Get Sources from Kenai from the main menu.

    Alternatively, you can choose Open Kenai Project to add the project to the Kenai dashboard and then get the project sources.

  2. In the Get Sources from Kenai dialog box, locate the Kenai Repository by clicking Browse to open the Browse Kenai Projects dialog box.
  3. Search for the Consulting Agency Solution Project.
  4. Select the Consulting Agency Solution Project and click OK.
  5. Click Browse to specify the Folder to Get and select base2. Click OK.
    image of folders in project's remote repository on Kenai
  6. Click Browse to specify the Local Folder for the sources (the local folder must be empty).
  7. Click Get From Kenai.

    When you click Get From Kenai, the IDE initializes the local folder as a Subversion repository and checks out the project sources.

  8. Click Open Project in the dialog that appears when checkout is complete.

For more information about getting sources from Kenai, see Opening a Kenai Project.

Creating a Phase Listener Class

As part of the requirements for the application, a user must be logged in as a consultant to access any of the pages to view and modify their billable items. To accomplish this you will create a phase listener class to authenticate the user at each phase and pass the user to the correct page if the user is logged in. A user that is not authenticated triggers the "login" outcome. When you configure the JSF navigation rules you will set the "login" outcome to the login page (login.jsp).

  1. Choose File > Open Project to open the Open Project dialog box.
  2. Locate the ConsultingAgency application and click Open.
  3. Expand the Source Packages node in the Projects window.
  4. Right-click the jsf.util package and choose New > Java Class.
  5. Create a new class in the jsf.util package named AuthenticationPhaseListener that implements PhaseListener.
  6. Insert the following code into the class:
    public class AuthenticationPhaseListener implements PhaseListener {
        /**
        * <p>The outcome to trigger navigation to the login page.</p>
        */
        private static final String LOGIN_OUTCOME = "login";
    
        /**
        * <p>The outcome to trigger navigation to the welcome page.</p>
        */
        private static final String WELCOME_OUTCOME = "welcome";
    
        /**
        * <p>The session attribute key used to store an authenticated consultant.</p>
        */
        public static final String AUTHENTICATED_CONSULTANT_KEY = "jsfcrud.timecard.consultant";
    
        /**
        * <p>Invoke <code>verifyView</code> if the current phase is apply request values or render response.</p>
        */
        public void beforePhase(PhaseEvent event) {
            PhaseId phase = event.getPhaseId();
            if (PhaseId.APPLY_REQUEST_VALUES.equals(phase) || PhaseId.RENDER_RESPONSE.equals(phase)) {
                verifyView(event);
            }
        }
    
        /**
        * <p>No-op.</p>
        */
        public void afterPhase(PhaseEvent event) {
        }
    
        private void verifyView(PhaseEvent event) {
            FacesContext context = event.getFacesContext();
            String viewId = context.getViewRoot().getViewId();
            String forwardToOutcome = null;
            if (isForbiddenView(viewId)) {
                JsfUtil.addErrorMessage("You do not have permission to view the requested page.");
                //change requested view to the welcome view
                //and assume we will forward to that view
                viewId = "/welcomeJSF.jsp";
                forwardToOutcome = WELCOME_OUTCOME;
            }
            if (getLoggedInConsultant() == null) {
                if (isSecureView(viewId)) {
                    forwardToOutcome = LOGIN_OUTCOME;
                }
            }
            if (forwardToOutcome != null) {
                context.getApplication(). getNavigationHandler().handleNavigation(context, null, forwardToOutcome);
            }
        }
    
        public PhaseId getPhaseId() {
            return PhaseId.ANY_PHASE;
        }
    
        public static Consultant getLoggedInConsultant() {
            return (Consultant)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(AuthenticationPhaseListener.AUTHENTICATED_CONSULTANT_KEY);
        }
    
        private boolean isSecureView(String viewId) {
            if (viewId == null) {
                return false;
            }
            if (isUnrestrictedView(viewId)) {
                return false;
            }
            return ("/welcomeJSF.jsp".equals(viewId) || viewId.startsWith("/billable/"));
        }
    
        private boolean isForbiddenView(String viewId) {
            if (viewId == null) {
                return true;
            }
            //only unrestricted and secure views are permitted
            if (isUnrestrictedView(viewId) || isSecureView(viewId)) {
            return false;
            }
            return true;
        }
    
        private boolean isUnrestrictedView(String viewId) {
            return "/login.jsp".equals(viewId) || "/jsfcrud.js".equals(viewId) || "/jsfcrud.css".equals(viewId) || "/busy.gif".equals(viewId);
        }
    }
  7. Fix your imports (Ctrl+Shift+I) and save your changes.

Modifying the JPA Controllers

In this section you will modify the JPA controllers generated by the IDE. When you used the JSF Pages from Entity Classes in the original tutorial, the IDE generated JPA controller classes for the entity classes and some exception classes used by the JPA controllers. The logic in a JPA controller class performs the work of creating, editing, and destroying an entity and can be invoked by other classes, JSP pages and servlets.

The IDE separated the JPA controller logic from the JSF controller logic to make modifying the code easier. For example, if your database schema changes after you modified the JSF pages and JSF controller classes, you can regenerate the entity classes for the database tables and then use the JPA Controllers from Entity Classes wizard to regenerate JPA controllers for the entities. The code for the JSF pages and JSF controllers is not changed.

In this section you will add some find methods to the JPA controller classes to retrieve the entities. The find methods are invoked from the JSF controller classes.

Modifying the JPA Controller for Billable Entities

  1. Open the jpa.controllers.BillableJpaController.java class in the editor and add the following find and get methods:
    public List<Billable> findBillableEntities(int maxResults, int firstResult, String query, Map<String, Object> parameters) {
        return findBillableEntities(false, maxResults, firstResult, query, parameters);
    }
    
    private List<Billable> findBillableEntities(boolean all, int maxResults, int firstResult, String query, Map<String, Object> parameters) {
        EntityManager em = getEntityManager();
        try {
            Query q = em.createQuery(query);
            if (parameters != null) {
                for (String name : parameters.keySet()) {
                    q.setParameter(name, parameters.get(name));
                }
            }
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        } finally {
            em.close();
        }
    }
    
    public int getBillableCount(String query, Map<String, Object> parameters) {
        EntityManager em = getEntityManager();
        try {
            Query q = em.createQuery(query);
            if (parameters != null) {
                for (String name : parameters.keySet()) {
                    q.setParameter(name, parameters.get(name));
                }
            }
            return ((Long) q.getSingleResult()).intValue();
        }
        finally {
            em.close();
        }
    }
  2. Fix your imports (to import java.util.Map) and save your changes.

Modifying the JPA Controller for Consultant Entities

  1. Open the jpa.controllers.ConsultantJpaController class in the editor and add the following find methods for consultants.
    public List<Consultant> findConsultantEntities(int maxResults, int firstResult, String query, Map<String, Object> parameters) {
        return findConsultantEntities(false, maxResults, firstResult, query, parameters);
    }
    
    private List<Consultant> findConsultantEntities(boolean all, int maxResults, int firstResult, String query, Map<String, Object> parameters) {
        EntityManager em = getEntityManager();
        try {
            Query q = em.createQuery(query);
            if (parameters != null) {
                for (String name : parameters.keySet()) {
                    q.setParameter(name, parameters.get(name));
                }
            }
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        }
        finally {
            em.close();
        }
    }
       
  2. Fix your imports (to import java.util.Map) and save your changes.

Modifying the JPA Controller for Project Entities

  1. Open the jpa.controllers.ProjectJpaController class in the editor and add the following find methods for projects.
    public List<Project> findProjectEntities(String query, Map<String, Object> parameters) {
        return findProjectEntities(true, -1, -1, query, parameters);
    }
    
    private List<Project> findProjectEntities(boolean all, int maxResults, int firstResult, String query, Map<String, Object> parameters) {
        EntityManager em = getEntityManager();
        try {
            Query q = em.createQuery(query);
            if (parameters != null) {
                for (String name : parameters.keySet()) {
                    q.setParameter(name, parameters.get(name));
                }
            }
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        }
        finally {
            em.close();
        }
    }
       
  2. Fix your imports (to import java.util.Map) and save your changes.

Modifying the JSF Controller Classes

In this section you will modify the JSF controllers generated by the IDE. When you used the JSF Pages from Entity Classes in the original tutorial, the IDE generated JSF controller classes with code specifically for the generated JSF pages. After modifying the JSF pages in the previous part of the tutorial, you now need to make some corresponding changes to the JSF controllers. For some of the changes you can modify the generated code or use the generated code as an example. For new functionality such as the search function you need to add new methods.

Note. You do not need to make any changes to the utility classes (JsfUtil and PagingInfo) or the ELResolver (JsfCrudELResolver) that are used by the JSF controller classes.

Editing the JSF Controller Class for Billable

In this exercise you will modify some of the existing methods in the jsf.BillableController.java class to retrieve billable items associated with the consultant. You will also add some methods to implement the search function for billable items, check if the user is allowed to perform the operation and add a validator for endDate.

  1. Open the jsf.BillableController.java class in the editor.
  2. Add the following member fields:
    private Map<String, Object> searchParameters = null;
    private static final String[] properties = {"billableId", "startDate", "endDate", "hours", "hourlyRate", "description", "project", "consultantId"};
       
  3. Add the following getSearchHours() and setSearchHours(Short) methods that are called from Search.jsp.
    public Short getSearchHours() {
        if (billable == null) {
            return null;
        }
        short billableHours = billable.getHours();
        if (billableHours == -1) {
            return null;
        }
        return new Short(billableHours);
    }
    
    public void setSearchHours(Short hours) {
        if (billable == null) {
            return;
        }
        short hoursAsPrimitive = hours == null ? -1 : hours.shortValue();
        billable.setHours(hoursAsPrimitive);
    }
  4. Make the following changes (in bold) to the getPagingInfo() method.
    public PagingInfo getPagingInfo() {
        if (pagingInfo.getItemCount() == -1) {
            pagingInfo.setItemCount(getItemCount());
        }
        return pagingInfo;
    }
  5. Make the following changes (in bold) to the getBillable() method to call getNewBillable().
    public Billable getBillable() {
        if (billable == null) {
            billable = (Billable) JsfUtil.getObjectFromRequestParameter("jsfcrud.currentBillable", converter, null);
        }
        if (billable == null) {
            billable = getNewBillable();
        }
        return billable;
    }
  6. Make the following changes (in bold) to the createSetup() method.
    public String createSetup() {
        reset(false);
        billable = getNewBillable();
        return "billable_create";
    }
  7. Make the following changes (in bold) to the validateCreate(FacesContext,UIComponent,Object) method.
    public void validateCreate(FacesContext facesContext, UIComponent component, Object value) {
        Billable newBillable = getNewBillable();
        String newBillableString = converter.getAsString(FacesContext.getCurrentInstance(), null, newBillable);
        String billableString = converter.getAsString(FacesContext.getCurrentInstance(), null, billable);
        if (!newBillableString.equals(billableString)) {
            createSetup();
        }
    }
  8. Add the getNewBillable() method to the class to set values for fields the consultant cannot modify. Notice that the method calls the getLoggedInConsultant() method in the AuthenticationPhaseListener class.
    private Billable getNewBillable() {
        Billable result = new Billable();
        Consultant consultant = AuthenticationPhaseListener.getLoggedInConsultant();
        result.setConsultantId(consultant);
        result.setHourlyRate(consultant.getHourlyRate());
        result.setBillableHourlyRate(consultant.getBillableHourlyRate());
        return result;
    }
  9. Now you will add code to some of the methods to call an isOperationForbidden method to check if the user can perform the operation.

    Add the following code (in bold) to the create() method before the try block.

    public String create() {
        if (isOperationForbidden()) {
            return null;
        }
        try {
            jpaController.create(billable);
            ...
    }
  10. Add the following code (in bold) to the scalarSetup(String destination) method, before the return statement.
    private String scalarSetup(String destination) {
            ...
            return relatedOrListOutcome();
        }
        if (isOperationForbidden()) {
            return null;
        }
        return destination;
    }
  11. Add the following code (in bold) to the edit() method before the try block.
    public String edit() {
        ...
                JsfUtil.addErrorMessage("Could not edit billable. Try again.");
            }
            return outcome;
        }
        if (isOperationForbidden()) {
            return null;
        }
        try {
            jpaController.edit(billable);
        ...
    }
  12. Add the following code (in bold) to the destroy() method, before String idAsString = JsfUtil.getRequestParameter("jsfcrud.currentBillable");
    public String destroy() {
        if (isOperationForbidden()) {
            return null;
        }
        String idAsString = JsfUtil.getRequestParameter("jsfcrud.currentBillable");
        Long id = new Long(idAsString);
        try {
        ...
    }
  13. Add the isOperationForbidden() method to the class to check if the logged in consultant can modify the data.
    private boolean isOperationForbidden() {
        boolean forbidden = false;
        Consultant loggedInConsultant = AuthenticationPhaseListener.getLoggedInConsultant();
        if (loggedInConsultant == null) {
            forbidden = true;
        } else if (billable == null) {
            //destroy operation
            String consultantOfCurrentBillableAsString = JsfUtil.getRequestParameter("jsfcrud.timecard.consultantOfCurrentBillable");
            if (consultantOfCurrentBillableAsString != null) {
                Integer consultantOfCurrentBillableAsInteger = new Integer(consultantOfCurrentBillableAsString);
                if (!consultantOfCurrentBillableAsInteger.equals(loggedInConsultant.getConsultantId())) {
                    forbidden = true;
                }
            }
        } else if (!loggedInConsultant.equals(billable.getConsultantId())) {
            //detail or edit operation
            forbidden = true;
        }
        if (forbidden) {
            JsfUtil.addErrorMessage("You do not have permission to perform the requested operation.");
        }
        return forbidden;
    }
  14. Modify the getBillableItems() method to add the following code (in bold) to add search parameters and set a query when getting the billable items.
    public List<Billable> getBillableItems() {
        if (billableItems == null) {
            getPagingInfo();
            tweakSearchParameters();
            String query = getQueryForParameters("select object(o) from Billable as o");
            billableItems = jpaController.findBillableEntities(pagingInfo.getBatchSize(), pagingInfo.getFirstItem(), query, searchParameters);
        }
        return billableItems;
    }
  15. Add the following methods to the class to implement the search function, including the tweakSearchParameters and getQueryForParameters methods.
    public String searchSetup() {
        reset(false);
        billable = new Billable();
        billable.setHours((short)-1);
        return "billable_search";
    }
    
    public String search() {
        searchParameters = new HashMap<String, Object>();
        Object[] values = {billable.getBillableId(), billable.getStartDate(), billable.getEndDate(), new Short(billable.getHours()), billable.getHourlyRate(), billable.getDescription(), billable.getProject(), null};
        for (int i = 0; i < properties.length; i++) {
            if (values[i] != null) {
                if("hours".equals(properties[i])) {
                    if (((Short)values[i]).shortValue() == -1) {
                        continue;
                    }
                } else if (values[i] instanceof String) {
                    String trimmedValue = ((String)values[i]).trim();
                    if (trimmedValue.length() == 0) {
                        continue;
                    }
                    trimmedValue = trimmedValue.replaceAll("~", "~~");
                    trimmedValue = trimmedValue.replaceAll("%", "~%");
                    values[i] = "%" + trimmedValue + "%";
                }
                searchParameters.put(properties[i], values[i]);
            }
        }
        reset(true, false);
        return "billable_list";
    }
    
    public int getItemCount() {
        tweakSearchParameters();
        String query = getQueryForParameters("select count(o) from Billable as o");
        return jpaController.getBillableCount(query, searchParameters);
    }
    
    private String getQueryForParameters(String baseQuery) {
        if (searchParameters == null) {
            return baseQuery;
        }
        StringBuffer where = new StringBuffer();
        for (int i = 0; i < properties.length; i++) {
            if (searchParameters.containsKey(properties[i])) {
                if (where.length() > 0) {
                    where.append(" and ");
                }
                if ("startDate".equals(properties[i])) {
                    where.append("(o.startDate is null or o.startDate >= :startDate) and (o.endDate is null or o.endDate >= :startDate)");
                }
                else if ("endDate".equals(properties[i])) {
                    where.append("(o.startDate is null or o.startDate <= :endDate) and (o.endDate is null or o.endDate <= :endDate)");
                }
                else if (searchParameters.get(properties[i]) instanceof String) {
                    where.append("o." + properties[i] + " like :" + properties[i] + " escape '~'");
                }
                else {
                    where.append("o." + properties[i] + " = :" + properties[i]);
                }
            }
        }
        if (where.length() > 0) {
            baseQuery += " where " + where;
        }
        return baseQuery;
    }
    
    private void tweakSearchParameters() {
        if (searchParameters == null) {
            searchParameters = new HashMap<String,Object>();
        }
        if (searchParameters.get(properties[properties.length - 1]) == null) {
            Consultant loggedInConsultant = AuthenticationPhaseListener.getLoggedInConsultant();
            searchParameters.put(properties[properties.length - 1], loggedInConsultant);
        }
    }

    Note. Notice that the searchSetup method returns "billable_search". You will later configure the JSF navigation rules so that Search.jsp is loaded when the outcome is "billable_search".

  16. Modify the next() and prev() methods to change reset(false) to reset(false, false) so that searchParameters are also reset.
    public String next() {
        reset(false, false);
        getPagingInfo().nextPage();
        return "billable_list";
    }
    
    public String prev() {
        reset(false, false);
        getPagingInfo().previousPage();
        return "billable_list";
    }
  17. Modify the reset(boolean) method to also reset searchParameters and add a new reset(boolean) method.
    private void reset(boolean resetFirstItem, boolean resetSearchParameters) {
        billable = null;
        billableItems = null;
        pagingInfo.setItemCount(-1);
        if (resetFirstItem) {
            pagingInfo.setFirstItem(0);
        }
        if (resetSearchParameters) {
            searchParameters = null;
        }
    }
    
    private void reset(boolean resetFirstItem) {
        reset(resetFirstItem, true);
    }
  18. Add the following validator method for endDate used in Search.jsp, New.jsp and Edit.jsp.
    public void validateEndDate(FacesContext facesContext, UIComponent component, Object value) {
        ValueHolder startDateInputField = (ValueHolder)facesContext.getViewRoot().findComponent("form1:startDate");
        Date startDate = (Date)startDateInputField.getLocalValue();
        if (startDate != null) {
            Date endDate = (Date)value;
            if (startDate.after(endDate)) {
                throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "The end date cannot precede the start date.", null));
            }
        }
    }
  19. Fix your imports (make sure you import java.util.Date and javax.faces.validator.ValidatorException) and save your changes.

Editing the JSF Controller Class for Consultant

In this exercise you add the login and logout methods. The login method is invoked from login.jsp and the logout method is invoked from welcomeJSF.jsp.

  1. Open the jsf.ConsultantController.java class in the editor.
  2. Add the following login() and logout() methods.
    public String login() {
        if (consultant == null) {
            JsfUtil.addErrorMessage("An error occurred attempting to log in.");
            return null;
        }
        String query = "select object(o) from Consultant as o where o.email = :email and o.password = :password";
        Map<String, Object> parameters = new HashMap<String, Object>();
        String email = consultant.getEmail();
        parameters.put("email", email);
        parameters.put("password", consultant.getPassword());
        List<Consultant> consultants = null;
        try {
            consultants = jpaController.findConsultantEntities(1, 0, query, parameters);
        } catch (Exception e) {
            JsfUtil.ensureAddErrorMessage(e, "A persistence error occurred.");
        }
        if (consultants != null && consultants.size() > 0) {
            Consultant c = consultants.get(0);
            FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(AuthenticationPhaseListener.AUTHENTICATED_CONSULTANT_KEY, c);
            JsfUtil.addSuccessMessage("Welcome, " + email + ".");
            return "welcome";
        } else {
            JsfUtil.addErrorMessage("Invalid login.");
            return null;
        }
    }
    
    public String logout() {
        HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
        if (session != null) {
            session.invalidate();
        }
        JsfUtil.addSuccessMessage("You are now logged out.");
        return "login";
    }
  3. Fix your imports (make sure you import java.util.Map) and save your changes.

Editing the JSF Controller Class for Project

In this exercise you add methods that determine the projects that are displayed in the project drop-down list when a consultant creates, updates and searches billable items. The drop down list only displays projects associated with the logged in consultant. AssignedProjectsForLoggedInConsultantSelectOne is invoked from the New.jsp and Edit.jsp pages. BilledProjectsForLoggedInConsultantSelectOne is invoked from the Search.jsp page.

  1. Open the jsf.ProjectController class in the editor.
  2. Add the following methods:
    public SelectItem[] getBilledProjectsForLoggedInConsultantSelectOne() {
        String query = "select distinct b.project from Billable as b where b.consultantId = :consultant";
        Map<String, Object> parameters = new HashMap<String, Object>();
        Consultant loggedInConsultant = AuthenticationPhaseListener.getLoggedInConsultant();
        parameters.put("consultant", loggedInConsultant);
        return JsfUtil.getSelectItems(jpaController.findProjectEntities(query, parameters), true);
    }
    
    public SelectItem[] getAssignedProjectsForLoggedInConsultantSelectOne() {
        Consultant loggedInConsultant = AuthenticationPhaseListener.getLoggedInConsultant();
        Collection<Project> assignedProjectsCollection = loggedInConsultant.getProjectCollection();
        List assignedProjectsList = new ArrayList(assignedProjectsCollection);
        return JsfUtil.getSelectItems(assignedProjectsList, true);
    }
  3. Fix your imports (to import java.util.Collection and java.util.Map) and save your changes.

Editing the Navigation Rules

Now that you are finished modifying and adding the required methods, you need to update the navigation rules for the JSF pages by editing the faces-config.xml file. You need to specify the phase listener class as part of the JSF lifecycle and create new navigation rules for the two pages that you created in the first part of the tutorial (login.jsp and Search.jsp).

  1. Expand the Configuration Files node in the Projects window.
  2. Double-click faces-config.xml to open the file in the editor and click the XML tab to edit the raw XML.
  3. Add the following <lifecycle> elements (in bold) before the <application> element.
    <lifecycle>
        <phase-listener>jsf.util.AuthenticationPhaseListener</phase-listener>
    </lifecycle>
    <application>
        <el-resolver>jsf.util.JsfCrudELResolver</el-resolver>
    </application>
  4. Add the following login navigation rule (in bold) after the welcome navigation rule.
    <navigation-rule>
        <navigation-case>
            <from-outcome>welcome</from-outcome>
            <to-view-id>/welcomeJSF.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <navigation-case>
            <from-outcome>login</from-outcome>
            <to-view-id>/login.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
  5. Add the following billable_search navigation rule (in bold) after the billable_detail navigation rule.
    <navigation-rule>
        <navigation-case>
            <from-outcome>billable_detail</from-outcome>
            <to-view-id>/billable/Detail.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <navigation-case>
            <from-outcome>billable_search</from-outcome>
            <to-view-id>/billable/Search.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

Running The Application

Now that you have made all the necessary changes to the JSP files and the Java classes, you can run the application to see if everything is working correctly. After logging in, search for billable items and create a new billable item.

  1. Check that your database is running.
  2. Right-click the ConsultingAgency node in the Projects window and choose Run.

    When you choose Run, the IDE deploys the application to the server and opens the browser to the login page (http://localhost:8080/ConsultingAgency/).

  3. Enter the Email and Password. Click Login.

    Note. The login screen requires a valid email and password.

    • If you entered the sample data in the Adding Data to the Database section in the initial tutorial where you created the ConsultingAgency application (Generating a JavaServer Faces CRUD Application from a Database), you can now type for the email address and janet.smart for the password.
    • If you entered different data, you can find the email and password for a consultant by right-clicking the consultant table in the Services window of the IDE and choosing View Data. The IDE will display the table data in the lower window of the SQL editor.
    • If your database is not populated, you need to follow the steps in the Adding Data to the Database section of the initial tutorial to run the SQL script to populate the database.

    After you log in you see the welcome screen for the consultant containing links to the screens that the consultant can access. A logged in consultant can search for their billable items or create a new billable item.

    screenshot of browser window after logging in
  4. Click Search Billable Items and search for billable items of Secret Project.
  5. Create a new billable item.

Troubleshooting

If you are attempting to run the project for the first time and you encounter problems displayed in the Output window, see the Troubleshooting section of the first tutorial in the series.

Now that the application is running correctly, you can go to the third part of the tutorial to Add Ajax Validation to the Generated JavaServer Faces CRUD Application.


See Also

For more information about using NetBeans IDE to develop web applications using Java Persistence and JSF, see the following resources: