You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by Apache Wiki <wi...@apache.org> on 2007/02/20 08:39:18 UTC

[Struts Wiki] Update of "StrutsQuickStart" by MichaelJouravlev

Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Struts Wiki" for change notification.

The following page has been changed by MichaelJouravlev:
http://wiki.apache.org/struts/StrutsQuickStart

New page:
This is a fast track Struts guide for JSP developers. It starts with a simple employee list of an imaginary company, implemented with pure JSP (Model 1), servlet + JSP (Model 2) and with Struts + JSP (which is also Model 2). After familiarity with Struts is established, this guide delves into event handling, action interaction, form submission and ulitimately finishes with complete Struts-based CRUD (Create, Retrieve, Update, Delete) application.

== Employee list with pure JSP (Model 1) ==

Before the data list is displayed, it should be retrieved from a database and saved into appropriate J2EE scope like request, session or application. Then the list is iterated and its content is shown line by line. Notice JSTL 2.0 usage.

{{{<%@ page import="java.util.ArrayList, model.EmployeeManager"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%-- Load data from the storage --%>

<%
    if (session.getAttribute("employees") == null) {
        session.setAttribute("employees", EmployeeManager.loadEmployees());
    }
%>

<%-- Display data --%>

<html>
  <body>
    <table>
      <tr>
        <th align="left">Emp #</th>
        <th align="left">Name</th>
        <th align="left">Salary</th>
      </tr>
      <c:forEach var="employee" items="${employees}">
        <tr>
          <td>${employee.id}</td>
          <td>${employee.name}</td>
          <td>${employee.salary}</td>
        </tr>
      </c:forEach>
    </table>

  </body>
</html>}}}

== Employee list with pure Servlet + JSP (Model 2) ==

One can argue that JSTL 2.0 greatly simplifies access to dynamic data from JSP, therefore pure JSP applications look less cluttered. Nevertheless, Model 2 allows additional benefits. With Model 2 approach, the data-preparation code is located in a servlet, while JSP performs purely presentation tasks. This makes JSP pages smaller, cleaner and simpler. Also, a servlet may easily replace one presentation with another by changing the name of a JSP page it forwards too. Overall design is more structured.

Here is how the servlet would look like:

{{{public class EmployeeListServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
    {
        HttpSession session = request.getSession();

        // Prepare data for output
        if (session.getAttribute("employees") == null) {
            session.setAttribute("employees", EmployeeManager.loadEmployees());
        }

        // Forward to the JSP page that renders employee list
        String view = "/jspservlet/employees.jsp";
        RequestDispatcher rd = getServletContext().getRequestDispatcher(view);
        rd.forward(request, response);
    }
}
}}}

The request is processed by the servlet, then the servlet forwards to "/jspservlet/employees.jsp" page, which displays the result:

{{{<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<html>
  <body>
    <table>
      <tr>
        <th align="left">Emp #</th>
        <th align="left">Name</th>
        <th align="left">Salary</th>
      </tr>
      <c:forEach var="employee" items="${employees}">
        <tr>
          <td>${employee.id}</td>
          <td>${employee.name}</td>
          <td>${employee.salary}</td>
        </tr>
      </c:forEach>
    </table>
  </body>
</html>}}}

== Employee list with Struts + JSP ==

In the previous Model 2 example the name of the presentation page is hardcoded in the servlet. Would not it be nice to externalize this information? Struts framework allows to do that with {struts-config.xml} file. Instead of a servlet you will be developing a custom Action class:

{{{import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.Action;
import model.EmployeeManager;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;

/**
 * Accesses the virtual imployee database.
 */
public class EmployeesListAction extends Action {

    /**
     * Handles incoming request
     */
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        // Lookup virtual database, if not found - instantiate it
        // and store in the session under "employees" name
        HttpSession session = request.getSession();
        ArrayList employees = (ArrayList) session.getAttribute("employees");
        if (employees == null) {
            employees = EmployeeManager.loadEmployees();
            session.setAttribute("employees", employees);
        }

        // Return "render" outcome to display employee list (see struts-config.xml)
        return mapping.findForward("render");
    }
}
}}}

Notice that {{{execute}}} method returns an action mapping, passing to it "render" outcome code. The correspondence of this code to a presentation page is defined in {{{struts-config.xml}}} file:

{{{<struts-config>
    <action-mappings>
      <action path  = "/employeesList" type  = "actions.EmployeesListAction">
          <forward name = "render" path = "/jspstruts1/employees.jsp"/>
      </action>
    ...
<struts-config>}}}

The presentation page is unchanged from previous Servlet/JSP example. 

== Event handling in Struts: delete an employee ==

Now after you got the idea how Struts fits the servlet/JSP environment, let us improve our application. How about being able to delete an employee from the list? To do this we will change the JSP page, adding an "Operation" column and creating a "Delete" link for each table row. The link would contain employee ID. When clicked, the link will be processed by the same Action that displays the list. To distinguish the "Delete" command from the request to merely display the list, we will add a special parameter to the link. We will define this parameter as an event for this action in the {{{struts-config.xml}}} file, and we will redesign the Action class to perform employee removal code when this parameter is received.

Here is modified EmployeeListAction class, notice that it extends EventDispatchAction class:
{{{public class EmployeesListActionDelete extends EventDispatchAction {

    /**
     * Handles "deleteEvent" event (see struts-config.xml file). Deletes an employee
     * from the list of employees.
    public ActionForward delete (ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {

        // Reads empId parameter from the request. This parameter is part of the "Delete" link
        // in the employee table. If deletion failed, an error message is generated and is
        // queued into request scope, where it is pulled from by a JSP page.
        String empId = request.getParameter("empId");
        if (empId != null) {
            boolean deleted = EmployeeManager.deleteEmployee(empId);
            if (!deleted) {
                ActionErrors errors = new ActionErrors();
                errors.add("ERROR", new ActionMessage("crud.itemnotfound", empId));
                saveErrors(request.getSession(), errors);
            }
        }
        // Processing of the event has finished, do whatever is appropriate next,
        // which is to redisplay the updated employee list.
        return mapping.findForward("finished");
    }

    /**
     * Handles cases when no event is found in the request. In this case simply
     * displays the list of employees. Compare with EmployeeListAction.execute method.
    public ActionForward unspecified(ActionMapping mapping,
                                     ActionForm form,
                                     HttpServletRequest request,
                                     HttpServletResponse response) throws Exception {
        // Lookup virtual database, if not found - instantiate it
        // and store in the session under "employees" name
        HttpSession session = request.getSession();
        ArrayList employees = (ArrayList) session.getAttribute("employees");
        if (employees == null) {
            employees = EmployeeManager.loadEmployees();
            session.setAttribute("employees", employees);
        }

        // Return "render" outcome to display employee list (see struts-config.xml)
        return mapping.findForward("render");
    }
}
}}}

Now the definition of "Delete" event in the action mapping in {{{struts-config.xml}}} file:
{{{<action path  = "/employeesListDelete"
        type  = "actions.EmployeesListActionDelete"
        parameter = "deleteEvent=delete">
    <forward name = "render" path = "/jspstruts2/employees.jsp"/>
    <forward name = "finished" path = "/employeesListDelete.do" redirect = "true"/>
</action>}}}

The "parameter" attribute states that if "deleteEvent" request parameter is received, then "delete" method of EmployeesListActionDelete should be called. Also notice two mappings, "render" and "finished". First is used to display employee list, second is used to finish processing of "deleteEvent" event and to redisplay the item list by redirecting to it.

Finally, the JSP. Notice the <html:link> tag to create a link, JSTL 2.0 is used for convenience:

{{{<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:html>
  <body>
    <table>
      <tr>
        <th align="left">Emp #</th>
        <th align="left">Name</th>
        <th align="left">Salary</th>
        <th align="left">Operation</th>
      </tr>
      <c:forEach var="employee" items="${employees}">
        <tr>
          <td>${employee.id}</td>
          <td>${employee.name}</td>
          <td>${employee.salary}</td>
          <td><html:link action="employeesListDelete.do?deleteEvent&empId=${employee.id}">Delete</html:link></td>
        </tr>
      </c:forEach>
    </table>
  </body>
</html:html>}}}

== Action interaction in Struts and form submission: view an employee ==

Let us change the application to view an employee information in a separate form. To view an employee we will create a separate Action class, EmployeeActionView, which will handle "view" event:

{{{/**
 * Action that displays an employee in a separate HTML form.
 * Uses EventActionDispatcher to handle events like "view" and "close".
 */
public class EmployeeActionView extends Action {

    /**************************************************************************
     * Set up event dispatching
     *************************************************************************/

    /**
     * Instantiate event dispatcher. EventActionDispatcher class is shipped
     * in struts-extras.jar.
     */
    protected ActionDispatcher dispatcher = new EventActionDispatcher(this);

    /**
     * Dispatch an event to an appropriate event handler. By using a dispatcher
     * an action class does not need to extend a DispatchAction or other
     * dispatching class flavor.
     */
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        return dispatcher.execute(mapping, form, request, response);
    }

    /**************************************************************************
     * Event handlers
     *************************************************************************/

    /**
     * Handles "view" event (see struts-config.xml file). Loads an existing Employee
     * from the list for preview.
     */
    public ActionForward view (ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response) throws Exception {
        EmployeeForm empForm = (EmployeeForm) form;

        // Load existing item; ID of item to load is already in the form bean;
        // Struts has populated the form bean with request values.
        ActionMessages errors = loadEmployee(empForm);

        // Employee has been found and loaded successfully ==> view employee
        if (errors == null || errors.isEmpty()) {
            empForm.setHeader("View employee");
            return mapping.findForward("render");

        // Could not load employee ==> queue errors to the session object and redirect
        // to the employee list page.
        } else {
            request.getSession().setAttribute(Globals.ERROR_KEY, errors);
            return mapping.findForward("finished");
        }
    }

    /**
     * Handles "close" event (see struts-config.xml file).
     */
    public ActionForward close (ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response) throws Exception {
        // Redirect to the employee list page
        return mapping.findForward("finished");
    }

    /**************************************************************************
     * Helpers
     **************************************************************************/

    /**
     * Loads item from storage (in this example from the session object)
     * @return non-empty ActionMessages object if could not load item.
     */
    private ActionMessages loadEmployee(EmployeeForm empForm) {
        ActionErrors errors = new ActionErrors();
        try {
            String empId = empForm.getEmpId();
            Employee employee = EmployeeManager.loadEmployee(empId);
            if (employee != null) {
                empForm.setName(employee.getName());
                empForm.setSalary(String.valueOf(employee.getSalary()));
                return null;
            } else {
                errors.add("ERROR", new ActionMessage("crud.itemnotfound", empId));
                return errors;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            errors.add("ERROR", new ActionMessage("crud.internalstorageerror", e.getMessage()));
            return errors;
        }
    }

}
}}}

We will change the employee list page so that {{{EmployeeActionView}}} is called when a user clicks on employee ID:

{{{<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:html>
  <body>
    <table>
      <tr>
        <th align="left">Emp #</th>
        <th align="left">Name</th>
        <th align="left">Salary</th>
      </tr>
      <c:forEach var="employee" items="${employees}">
        <tr>
          <td><html:link action="employeeActionView.do?view&empId=${employee.id}">${employee.id}</html:link></td>
          <td>${employee.name}</td>
          <td>${employee.salary}</td>
        </tr>
      </c:forEach>
    </table>
  </body>
</html:html>}}}

The employee list is displayed with the simple Action class as in the first Struts sample (see above). The configuration file reflects the relationship of two actions:

{{{<form-beans>
  <form-bean name = "crudform" type="actions.EmployeeForm"/>
</form-beans>

<action path  = "/employeesListView"
        type  = "actions.EmployeesListAction">
  <forward name = "render" path = "/jspstruts3/employees.jsp"/>
</action>

<action path  = "/employeeActionView"
        type  = "actions.EmployeeActionView"
        name  = "crudform"
        scope = "request"
        parameter = "view,close">
  <forward name = "render" path = "/jspstruts3/employee-view.jsp"/>
  <forward name = "finished" path = "/employeesListView.do" redirect = "true"/>
</action>}}}

This example uses EmployeeForm to temporarily store input/output data.

== Data editing and full-blown CRUD application ==

Now when we know how to view and delete an employee, let us add the missing parts: creation, update and saving of modified employee data. The EmployeeAction class handles "create", "view", "edit", "duplicate", "save", "delete" and "close" events, this is how it is configured:

{{{<action path  = "/employeesListCRUD"
        type  = "actions.EmployeesListAction">
  <forward name = "render" path = "/jspstruts4/employees.jsp"/>
</action>

<action path  = "/employeeActionCRUD"
        type  = "actions.EmployeeAction"
        name  = "crudform"
        scope = "request"
        validate  = "false"
        parameter = "create,view,edit,duplicate,save=validateAndSave,delete,close">
  <forward name = "renderedit" path = "/jspstruts4/employee-edit.jsp"/>
  <forward name = "renderpreview" path = "/jspstruts4/employee-view.jsp"/>
  <forward name = "finished" path = "/employeesListCRUD.do" redirect = "true"/>
</action>}}}

The {{{EmployeeAction}}} class is not very complicated, but you better dowload the sample WAR file with the source code, and see it yourself.