Menu

One Artifact Tutorial-04-Web

Jack Liu

Table of Contents

  1. Create a ProjectActionTest
  2. Create a ProjectAction that will fetch project
  3. Add test sample data
  4. Create projectList.jsp to show search results
  5. Create projectForm.jsp to edit a project
  6. Update Struct configuration file
  7. Add new i18n keys/values
  8. Configure Validation
  9. Add link to menu
  10. Commit all related changes

Create a ProjectActionTest

Testing is an important part of any application, and testing a Struts application is easier than most. The generic command pattern provided by XWork doesn't depend on the Servlet API at all. This makes it easy to use JUnit to test your Actions.

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

package com.ibm.ics.webapp.action;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import org.apache.struts2.ServletActionContext;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;

import com.ibm.ics.model.Project;
import com.ibm.ics.service.GenericManager;
import com.opensymphony.xwork2.ActionSupport;

public class ProjectActionTest extends BaseActionTestCase {
    private ProjectAction action;

    @Before
    public void onSetUp() {
        super.onSetUp();

        action = new ProjectAction();
        GenericManager projectManager = (GenericManager) applicationContext
                .getBean("projectManager");
        action.setProjectManager(projectManager);

        // add a test project to the database
        Project project = new Project();

        // enter all required fields

        projectManager.save(project);
    }

    @Test
    public void testGetAllProjects() throws Exception {
        assertEquals(action.list(), ActionSupport.SUCCESS);
        assertTrue(action.getProjects().size() >= 1);
    }
    @Test
    public void testEdit() throws Exception {
        log.debug("testing edit...");
        action.setId(1L);
        assertNull(action.getProject());
        assertEquals("success", action.edit());
        assertNotNull(action.getProject());
        assertFalse(action.hasActionErrors());
    }

    @Test
    public void testSave() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setId(1L);
        assertEquals("success", action.edit());
        assertNotNull(action.getProject());

        // update last name and save
        action.getProject().setName("Updated Last Name");
        assertEquals("input", action.save());
        assertEquals("Updated Last Name", action.getProject().getName());
        assertFalse(action.hasActionErrors());
        assertFalse(action.hasFieldErrors());
        assertNotNull(request.getSession().getAttribute("messages"));
    }

    @Test
    public void testRemove() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setDelete("");
        Project project = new Project();
        project.setId(2L);
        action.setProject(project);
        assertEquals("success", action.delete());
        assertNotNull(request.getSession().getAttribute("messages"));
    }
}

This class won't compile yet; you must first create the ProjectAction class.

Create a ProjectAction

Create a ProjectAction.java class (that extends AppFuse's BaseAction) in src/main/java/**/webapp/action:

package com.ibm.ics.webapp.action;

import java.util.List;

import com.ibm.ics.model.Project;
import com.ibm.ics.service.GenericManager;

public class ProjectAction extends BaseAction {

    private GenericManager<Project, Long> projectManager;
    private List<Project> projects;

    public void setProjectManager(GenericManager<Project, Long> projectManager) {
        this.projectManager = projectManager;
    }

    public List<Project> getProjects() {
        return projects;
    }

    public String list() {
        projects = projectManager.getAll();
        return SUCCESS;
    }

    private Project project;
    private Long id;

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

    public Project getProject() {
        return project;
    }

    public void setProject(Project project) {
        this.project = project;
    }

    public String delete() {
        projectManager.remove(project.getId());
        saveMessage(getText("project.deleted"));

        return SUCCESS;
    }

    public String edit() {
        if (id != null) {
            project = projectManager.get(id);
        } else {
            project = new Project();
        }

        return SUCCESS;
    }

    public String save() throws Exception {
        if (cancel != null) {
            return CANCEL;
        }

        if (delete != null) {
            return delete();
        }

        boolean isNew = (project.getId() == null);

        project = projectManager.save(project);

        String key = (isNew) ? "project.added" : "project.updated";
        saveMessage(getText(key));

        if (!isNew) {
            return INPUT;
        } else {
            return SUCCESS;
        }
    }

}

Add test sample data

If you look at your ProjectActionTest, 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='project'>
        <column>id</column>
        <column>name</column>
        <column>description</column>
        <row>
            <value>1</value>
            <value>connections</value>
            <value>this is connections project description.</value>
        </row>
        <row>
            <value>2</value>
            <value>sametime</value>
            <value>this is sametime project description.</value>
        </row>
    </table>

Run the ProjectActionTest using your IDE or mvn test -Dtest=ProjectActionTest.

Create projectList.jsp to show search results

Create a src/main/webapp/WEB-INF/pages/projectList.jsp page to display the list of project:

    <%@ include file="/common/taglibs.jsp"%>

    <head>
        <title><fmt:message key="projectList.title"/></title>
        <meta name="heading" content="<fmt:message key='projectList.heading'/>"/>
        <meta name="menu" content="ProjectMenu"/>
    </head>

    <input type="button" style="margin-right: 5px" class="button" onclick="location.href='<c:url value="/editProject"/>'" value="<fmt:message key="button.add"/>"/>
    <input type="button" class="button" onclick="location.href='<c:url value="/mainMenu"/>'" value="<fmt:message key="button.done"/>"/>

    <display:table name="projects" class="table" requestURI="" id="projectList" export="true" pagesize="25">
        <display:column property="id" sortable="true" href="editProject" media="html"
            paramId="id" paramProperty="id" titleKey="project.id"/>
        <display:column property="id" media="csv excel xml pdf" titleKey="project.id"/>
        <display:column property="name" sortable="true" titleKey="project.name"/>
        <display:column property="description" sortable="true" titleKey="project.description"/>

        <display:setProperty name="paging.banner.item_name"><fmt:message key="projectList.project"/></display:setProperty>
        <display:setProperty name="paging.banner.items_name"><fmt:message key="projectList.projects"/></display:setProperty>

        <display:setProperty name="export.excel.filename"><fmt:message key="projectList.title"/>.xls</display:setProperty>
        <display:setProperty name="export.csv.filename"><fmt:message key="projectList.title"/>.csv</display:setProperty>
        <display:setProperty name="export.pdf.filename"><fmt:message key="projectList.title"/>.pdf</display:setProperty>
    </display:table>

    <input type="button" style="margin-right: 5px" class="button" onclick="location.href='<c:url value="/editProject"/>'" value="<fmt:message key="button.add"/>"/>
    <input type="button" class="button" onclick="location.href='<c:url value="/mainMenu"/>'" value="<fmt:message key="button.done"/>"/>

    <script type="text/javascript">
        highlightTableRows("projectList");
    </script>

Create projectForm.jsp to edit a project's information

Create a src/main/webapp/WEB-INF/pages/projectForm.jsp page to display the form:

    <%@ include file="/common/taglibs.jsp"%>

    <head>
        <title><fmt:message key="projectDetail.title"/></title>
        <meta name="heading" content="<fmt:message key='projectDetail.heading'/>"/>
    </head>

    <s:form id="projectForm" action="saveProject" method="post" validate="true">
        <li style="display: none">
            <s:hidden key="project.id"/>
        </li>
        <s:textfield key="project.name" required="false" cssClass="text medium"/>
        <s:textfield key="project.description" required="false" cssClass="text medium"/>

        <li class="buttonBar bottom">
            <s:submit cssClass="button" method="save" key="button.save" theme="simple"/>
            <c:if test="${not empty project.id}">
                <s:submit cssClass="button" method="delete" key="button.delete"
                    onclick="return confirmDelete('Project')" theme="simple"/>
            </c:if>
            <s:submit cssClass="button" method="cancel" key="button.cancel" theme="simple"/>
        </li>
    </s:form>

    <script type="text/javascript">
        Form.focusFirstElement($("projectForm"));
    </script>

Update Struct configuration file

Next, update the src/main/resources/struts.xml file to include the "projects", "editProject" and "saveProject" actions.

    <action name="projects" class="com.ibm.ics.webapp.action.ProjectAction"
        method="list">
        <result>/WEB-INF/pages/projectList.jsp</result>
    </action>

    <action name="editProject" class="com.ibm.ics.webapp.action.ProjectAction"
        method="edit">
        <result>/WEB-INF/pages/projectForm.jsp</result>
        <result name="error">/WEB-INF/pages/projectList.jsp</result>
    </action>

    <action name="saveProject" class="com.ibm.ics.webapp.action.ProjectAction"
        method="save">
        <result name="input">/WEB-INF/pages/projectForm.jsp</result>
        <result name="cancel" type="redirectAction">projects</result>
        <result name="delete" type="redirectAction">projects</result>
        <result name="success" type="redirectAction">projects</result>
    </action>

Add new i18n keys/values

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

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

project.added=Project has been added successfully.
project.updated=Project has been updated successfully.
project.deleted=Project has been deleted successfully.

# -- project list page --
projectList.title=Project List
projectList.heading=Projects
projectList.project=Project
projectList.projects=Projects

# -- project detail page --
projectDetail.title=Project Detail
projectDetail.heading=Project Information

Configure Validation

Struts 2 allows two types of validation: per-action and model-based. Since you likely want the same rules applied for the project object across different actions, this tutorial will use model-based.

Create a new file named Project-validation.xml in the src/main/resources/**/model directory (you'll need to create this directory). It should contain the following XML:

    <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
    <validators>
        <field name="project.name">
            <field-validator type="requiredstring">
                <message key="errors.required" />
            </field-validator>
        </field>
    </validators>

Now you need to configure ProjectAction to know about visitor validation. To do this, create a ProjectAction-validation.xml file in src/main/resources/**/webapp/action (you'll need to create this directory). Fill it with the following XML:

    <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
    <validators>
        <field name="project">
            <field-validator type="visitor">
                <param name="appendPrefix">false</param>
                <message/>
            </field-validator>
        </field>
    </validators>

Add link to menu

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

menu.viewProject=View Project

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="ProjectMenu" title="menu.viewProject" page="/projects"/>

Make sure the above XML is inside the <menus> tag, but not within another </menus>

. Then open src/main/webapp/common/menu.jsp and add the following code to it:

    <%@ include file="/common/taglibs.jsp"%>

    <menu:useMenuDisplayer name="Velocity" config="cssHorizontalMenu.vm" permissions="rolesAdapter">
    <ul id="primary-nav" class="menuList">
        <li class="pad">&nbsp;</li>
        <c:if test="${empty pageContext.request.remoteUser}"><li><a href="<c:url value="/login"/>" class="current"><fmt:message key="login.title"/></a></li></c:if>
        <menu:displayMenu name="MainMenu"/>
        <menu:displayMenu name="ProjectMenu"/>
        <menu:displayMenu name="UserMenu"/>
        <menu:displayMenu name="AdminMenu"/>
        <menu:displayMenu name="Logout"/>
    </ul>
    </menu:useMenuDisplayer>

Run mvn jetty:run, open your browser to http://localhost:8080/projects

Commit all related changes

you can now commit all files below as one git commit.
src/main/resources/ApplicationResources.properties
src/main/resources/com/ibm/ics/model/Project-validation.xml
src/main/resources/com/ibm/ics/webapp/action/ProjectAction-validation.xml
src/main/resources/struts.xml
src/main/webapp/WEB-INF/menu-config.xml
src/main/webapp/common/menu.jsp
src/test/resources/sample-data.xml
src/main/java/com/ibm/ics/webapp/action/ProjectAction.java
src/main/webapp/WEB-INF/pages/projectForm.jsp
src/main/webapp/WEB-INF/pages/projectList.jsp
src/test/java/com/ibm/ics/webapp/action/ProjectActionTest.java