Lux Log

December 17, 2007

Part 2 of 3 Basic web app extended to full CRUD

Filed under: Java, NetBeans — Luk @ 8:49 pm

In this second part we’re extending the application we’ve set up in part one to support updates and delete actions for existing records. Still, the result will be far from production ready but the page views I got since yesterday encouraged me to continue writing.Before embarking upon this part it is imperative that you finished part one. We will be extending existing code with new parts and modifying other parts. Next to that, in order to keep my rants as compact as possible, I won’t be repeating how to open a project, how to build the application etc. If however some thing are not clear, please post me a comment. Having said that we are still using PostgreSQL, GlassfishV2 and NetBeans.

Upfront requirements:

  • Have the project from part one up and running

Table of contents

The example will be extended with functionalities to update and delete existing records.

2010.png

2020.png

Step 1: Extend the controller class

Let’s start by adding two action methods to the controller class, one for updating a User object, and one for deleting a User object:

  • deleteUser()
  • editUser()

We also add two helper methods. These are called from deleteUser() to give feedback on whether delete succeeded or failed.

  • addErrorMessage(String msg)
  • addSuccessMessage(String msg)

Finally we have extended the saveUser() method with a condition that checks whether a userId is passed in. If so, we update an existing user. If not, a new user is added.

Our controller class (JumpStartEjbJsf-war > Source Packages > lux.controllers > UserController.java) now looks like this (the added methods are indicated with a comment starting with // #2):

package lux.controllers;

import java.util.Map;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import lux.domain.User;
import lux.facade.UserDAO;

public class UserController {

    @EJB
    UserDAO userDao;
    private User user;
    private DataModel model;

    public String createUser() {
        this.user = new User();
        return "create_new_user";
    }

    // #2: extended with condition to check whether it is create or update
    public String saveUser() {
        String r = "success";

        try {
            if (user.getUserId() == null) {
                userDao.createUser(user);
            } else {
                userDao.updateUser(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
            r = "failed";
        }

        return r;
    }

    public DataModel getUsers() {
        model = new ListDataModel(userDao.getAllUsers());
        return model;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    // #2: delete action backing bean method
    public String deleteUser() {
        String r = "user_deleted";
        FacesContext context = FacesContext.getCurrentInstance();
        Map requestParameterMap = (Map) context.getExternalContext().getRequestParameterMap();

        try {
            Integer userId = Integer.parseInt(requestParameterMap.get("userId").toString());
            User u = userDao.getUser(userId);
            String userName = u.getUsername();
            userDao.deleteUser(u);
            addSuccessMessage("User " + userName + " successfully deleted.");
        } catch (NumberFormatException ne) {
            addErrorMessage(ne.getLocalizedMessage());
            r = "failed";
        } catch (Exception e) {
            addErrorMessage(e.getLocalizedMessage());
            r = "failed";
        }

        return r;
    }

    // #2: edit action backing bean method
    public String editUser() {
        FacesContext context = FacesContext.getCurrentInstance();
        Map requestParameterMap = (Map) context.getExternalContext().getRequestParameterMap();
        Integer userId = Integer.parseInt(requestParameterMap.get("userId").toString());
        this.user = userDao.getUser(userId);
        return "edit_user";
    }

    // #2: helper method for printing error message in jsf page
    private void addErrorMessage(String msg) {
        FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg);
        FacesContext fc = FacesContext.getCurrentInstance();
        fc.addMessage(null, facesMsg);
    }

    // #2: helper method for printing feedback in jsf page
    private void addSuccessMessage(String msg) {
        FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg);
        FacesContext fc = FacesContext.getCurrentInstance();
        fc.addMessage("successInfo", facesMsg);
    }
}

Either replace the contents of your UserController.java file with the contents above, or modify and add the fragments prefaced with “// #2”.

Step 2: Extend the JSF dataTable with commandLinks for delete and edit

The above methods editUser() and deleteUser() are action methods. These methods can be called from a JSF form to perform an action. Let’s extend our dataTable with commandLinks to both methods. Also we are adding a <h:messages/> element to give feedback after the user requested a delete action.

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>User Listing</title>
    </head>
    <body>
        <f:view>
            <h:form>
                <!-- #2 added messages element for delete feedback -->
                <h:messages></h:messages>
<h1><h:outputText value="User Listing"/></h1>

<h:commandLink action="#{user.createUser}" value="Create a user"/>
                <h:dataTable value="#{user.users}"
                             var="dataTableItem" border="1" cellpadding="2" cellspacing="2">
                    <h:column>
                        <f:facet name="header">
                            <h:outputText  value="Username"/>
                        </f:facet>
                        <h:outputText value="#{dataTableItem.username}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText  value="First name"/>
                        </f:facet>
                        <h:outputText value="#{dataTableItem.firstName}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText  value="Last name"/>
                        </f:facet>
                        <h:outputText value="#{dataTableItem.lastName}" />
                    </h:column>
                    <!-- #2 added column with edit and delete commandLink -->
                    <h:column>
                        <f:facet name="header">
                            <h:outputText  value="Edit"/>
                        </f:facet>
                        <h:commandLink action="#{user.editUser}">
                            <h:outputText value="Edit"/>
                            <f:param name="userId" value="#{dataTableItem.userId}" />
                        </h:commandLink>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText  value="Delete"/>
                        </f:facet>
                        <h:commandLink action="#{user.deleteUser}">
                            <h:outputText value="Delete"/>
                            <f:param name="userId" value="#{dataTableItem.userId}" />
                        </h:commandLink>
                    </h:column>
                </h:dataTable>
            </h:form>
        </f:view>
    </body>
</html>

Either replace the contents of your index.jsp file with the contents above, or modify file your file by adding/modifying the fragments above prefaced with <!– #2.

Step 3: Add edit JSF page

There are better, more manageable ways to do this but for now to add an “edit” page just:

  • make a copy of adduser.jsp,
  • remove the password fieldset from your copy,
  • add a title <h1>Edit user <h:outputText value="#{user.user.username}" /></h1>

1. Create a new JSP file edituser.jsp
2. Overwrite the contents of your newly created file with the code below
3. Save your file

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Edit user</title>
    </head>
    <body>
        <f:view>
<h1>Edit user <h:outputText value="#{user.user.username}" /></h1>
<h:form>
                <h:messages/>
                <h:panelGrid columns="2">
                    <h:outputText value="Username"/>
                    <h:inputText
                        label="Username"
                        value="#{user.user.username}"
                        required="true"/>
                    <h:outputText value="First name"/>
                    <h:inputText
                        label="First name"
                        value="#{user.user.firstName}" />
                    <h:outputText value="Last name"/>
                    <h:inputText
                        label="Last name"
                        value="#{user.user.lastName}" />
                    <h:panelGroup/>
                    <h:commandButton
                        action="#{user.saveUser}"
                        value="Save"/>
                </h:panelGrid>
            </h:form>
        </f:view>
    </body>
</html>

Step 4: Modify the page flow in faces-config.xml

For the delete action the user stays on index.jsp. For the edit action however the same page flow as for adduser.jsp is implemented. This makes a slightly more complex-looking diagram for faces-config than we had before in step 1. You can imagine that with a lot of pages and lots of navigation this can get messy.

2100.png

After modifying the diagram the xml (JumpStartEjbJsf-war > Configuration Files > faces-config.xml) should look like this:

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
    <managed-bean>
        <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>lux.controllers.UserController</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
    <navigation-rule>
        <from-view-id>/index.jsp</from-view-id>
        <navigation-case>
            <from-outcome>create_new_user</from-outcome>
            <to-view-id>/adduser.jsp</to-view-id>
        </navigation-case>
        <!-- #2 added for edit action -->
        <navigation-case>
            <from-outcome>edit_user</from-outcome>
            <to-view-id>/edituser.jsp</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-outcome>failed</from-outcome>
            <to-view-id>/failed.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <from-view-id>/adduser.jsp</from-view-id>
        <navigation-case>
            <from-outcome>failed</from-outcome>
            <to-view-id>/failed.jsp</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/index.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <!-- #2 added for edit action -->
    <navigation-rule>
        <from-view-id>/edituser.jsp</from-view-id>
        <navigation-case>
            <from-outcome>failed</from-outcome>
            <to-view-id>/failed.jsp</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/index.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
</faces-config>

Step 5: Modify existing methods in UserDAOBean

Final steps are to modify the methods updateUser(User u) and deleteUser(User u). For updateUser we added:

String pw = u.getPassword();
u.setPassword(pw);

We did this so that the password could be left out of the edit page. For deleteUser we had to add a line because, well er… it didn’t work.

User mgdUser = em.merge(u);

It actually turned out to be spec defined behavior that before an entity bean can be removed it must be read in the current persistence context. More info at https://glassfish.dev.java.net/javaee5/persistence/persistence-example.html#merge_and_remove.
The above changes result in following code (overwrite the existing UserDAOBean.java with it, or modify the parts prefaced with // #2)

package lux.facade;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import lux.domain.User;

@Stateless
public class UserDAOBean implements UserDAO {

@PersistenceContext
private EntityManager em;

public User getUser(int UserId) {
User u = new User();
u = em.find(User.class, UserId);
return u;
}

public List getAllUsers() {
Query q = em.createQuery(“SELECT u FROM User u”);
List users = q.getResultList();
return users;
}

public void createUser(User u) {
String hashedPw = hashPassword(u.getPassword());
u.setPassword(hashedPw);
em.persist(u);
}

// #2 changed: created hash on hash
public void updateUser(User u) {
String pw = u.getPassword();
u.setPassword(pw);
em.merge(u);
}

// #2: modified, didn’t work
public void deleteUser(User u) {
User mgdUser = em.merge(u);
em.remove(mgdUser);
}

private String hashPassword(String password) {
StringBuilder sb = new StringBuilder();

try {
MessageDigest messageDigest = MessageDigest.getInstance(“SHA”);
byte[] bs;
bs = messageDigest.digest(password.getBytes());

for (int i = 0; i < bs.length; i++) { String hexVal = Integer.toHexString(0xFF & bs[i]); if (hexVal.length() == 1) { sb.append("0"); } sb.append(hexVal); } } catch (NoSuchAlgorithmException ex) { Logger.getLogger(UserDAOBean.class.getName()).log(Level.SEVERE, null, ex); } return sb.toString(); } }[/sourcecode]

Step 6: Deploy and run your full CRUD web application

Ok, that’s it. Press F6 to run your application. If you run into any problems, you can debug by pressing Shift-F5.
Good luck!

Everything running fine? Then move on to the final and last part 3.

cc -Some rights

3 Comments »

  1. Hello, your tutorial is useful for every one interesting on EJB3 + Netbeans 6. There was functionality that I didn´t knew I was there all those times. This second step was as most important as the first one and I anxious for the third. Best regards and It´s a perfect job, contribuding so much for Java community.
    Alisson
    SCEA(I), SCBCD, SCWCD

    Comment by Alisson Patrick — February 6, 2008 @ 7:54 pm

  2. Hello

    I’ve been following this tutorial since this week and it’s a very useful example.

    After doing the changes mentioned in chapter 2 got this error after “undeploy – deploy” the web module of my project

    Deploying application in domain failed; Error loading deployment descriptors for module [EJB3_JSF-war] — Cannot resolve reference Unresolved Ejb-Ref co.net.aplicacion.controles.UserController/userDAO@jndi: @null@co.net.aplicacion.facade.UserDAOLocal@Session@null
    Deployment error:
    The module has not been deployed.
    See the server log for details.
    at org.netbeans.modules.j2ee.deployment.devmodules.api.Deployment.deploy(Deployment.java:166)
    at org.netbeans.modules.j2ee.ant.Deploy.execute(Deploy.java:104)
    at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:288)
    at sun.reflect.GeneratedMethodAccessor122.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:105)
    at org.apache.tools.ant.Task.perform(Task.java:348)
    at org.apache.tools.ant.Target.execute(Target.java:357)
    at org.apache.tools.ant.Target.performTasks(Target.java:385)
    at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1329)
    at org.apache.tools.ant.Project.executeTarget(Project.java:1298)
    at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
    at org.apache.tools.ant.Project.executeTargets(Project.java:1181)
    at org.apache.tools.ant.module.bridge.impl.BridgeImpl.run(BridgeImpl.java:277)
    at org.apache.tools.ant.module.run.TargetExecutor.run(TargetExecutor.java:460)
    at org.netbeans.core.execution.RunClassThread.run(RunClassThread.java:151)
    Caused by: The module has not been deployed.
    at org.netbeans.modules.j2ee.deployment.devmodules.api.Deployment.deploy(Deployment.java:160)
    … 16 more
    BUILD FAILED (total time: 0 seconds)

    What can I do now?

    Thanks in advance

    Comment by Julian Osorio Amaya — September 26, 2008 @ 6:02 am

  3. think you for this very helpful tutorial. it’s a good way for who want to learn JSF+EJB3+JPA application and who didn’t know who to do.

    Comment by carino — November 18, 2008 @ 9:59 am


RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.