Simple Application using MVC

This post will show my previous application using Struts Framework for MVC, and Hibernate ORM to connect to MySQL Database. By using Struts Framework, we are forced to particularly structured way. Hibernate offers amazing portability accross the relational databases, and increases developers’ productivity.

The application consists of user login and CRUD of Product database.

Login feature was created from scratch, while the CRUD was created using tools called strutstool which use the concept of Scaffolding. Scaffolding is a quick way to generate some of the major pieces of an application. If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job. I learned this concept when I was studying Ruby on Rails.

It was a great tool for developer.

First thing to do is, create project, then script this in command line:

java -jar strutstool scaffold Product id:integer name:string description:string price:integer stock integer

It will automatically generate several files.

Files created from struttools command
Files created from struttools command

This is the Entity of the Product which is generated earlier. In MVC Concept, this is related to Model.

package com.ay.product.model.entity;

import com.framework.util.validator.constraints.NotNull;
import com.framework.util.validator.constraints.XSSFilter;
import java.io.Serializable;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
// generator:imports

@Indexed
public class Product implements Serializable {
    @DocumentId
    private int id;

    @Field(index=Index.TOKENIZED,store=Store.YES)
    private String name;

    @Field(index=Index.TOKENIZED,store=Store.YES)
    private String description;

    @Field(index=Index.TOKENIZED,store=Store.YES)
    private int price;

    @Field(index=Index.TOKENIZED,store=Store.YES)
    private int stock;

    // generator:attributes
    public int getId() {
        return id;
    }

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

    @NotNull
    @XSSFilter
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @NotNull
    @XSSFilter
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @NotNull
    @XSSFilter
    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @NotNull
    @XSSFilter
    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }

    // generator:methods
}

The mapping to hibernate, was also created

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<!-- generator:mapping -->
    <class name="com.ay.product.model.entity.Product" table="product">
        <id name="id" column="id">
            <generator class="increment"/>
        </id>
        <property name="name" column="name" not-null="true" type="string" length="255" />
        <property name="description" column="description" not-null="true" type="string" length="255" />
        <property name="price" column="price" not-null="true" type="integer" />
        <property name="stock" column="stock" not-null="true" type="integer" />
    </class>

</hibernate-mapping>

Java files for product controller. In MVC Concept, this is the Controller, which control and handle the request.

package com.ay.product.controller;

// generator:imports

import com.ay.product.model.entity.Product;
import com.ay.product.model.search.ProductSearchMap;
import com.ay.product.model.service.ProductService;
import com.ay.product.model.service.ProductServiceImpl;
import com.framework.util.pagination.manager.PagingLookupManagerException;
import com.framework.util.search.EntitySearchMap;
import com.framework.util.struts.StrutsController;
import com.framework.util.repository.RepositoryException;
import com.framework.util.search.SearchAware;
import com.framework.util.validator.ValidatorException;
import com.opensymphony.xwork2.ModelDriven;

public class ProductController extends StrutsController 
        implements ModelDriven<Product>, SearchAware {

    // generator:attributes

    private ProductService productService;
    private Product product = new Product();
    private Integer productId;

    // Actions =================================================================
    // generator:actions

    public String index() {
        try {
            paginatedList = paginateListFactory.getPaginatedListFromRequest(request);
            paginatedList.setPageSize(Integer.parseInt(getText("table.pagesize")));

            if (isSearch()) {
                paginatedList = pagingManager.getSearchRecordsPage(searchParams, paginatedList);
            } else {
                paginatedList = pagingManager.getAllRecordsPage(paginatedList);
            }
        } catch (PagingLookupManagerException ex) {
            errorHandler(ex);
        }

        statusHandler();
        return SUCCESS;
    }

    public String edit() {
        try {
        	// generator:loaders

            if (isSave()) {
                getProductService().save(product);
                return SUCCESS_SAVE;
            } else {
                product = getProductService().findById(productId);
                if (product == null) {
                    return NOT_FOUND;
                }
            }
        } catch (RepositoryException ex) {
            errorHandler(ex);
        } catch (ValidatorException ex) {
            errorHandler(ex);
        }

        return SUCCESS;
    }

    public String add() {
        try {
        	// generator:loaders

            if (isSave()) {
                getProductService().save(product);
                return SUCCESS_SAVE;
            }
        } catch (RepositoryException ex) {
            errorHandler(ex);
        } catch (ValidatorException ex) {
            errorHandler(ex);
        }

        return SUCCESS;
    }

    public void delete() {
        try {
            getProductService().delete(productId);
        } catch (RepositoryException ex) {
            errorHandler(ex);
        }
    }

    // Implemented interface methods ===========================================

    public Product getModel() {
        return product;
    }

    public EntitySearchMap getEntitySearchMap() {
        return new ProductSearchMap();
    }

    // Getters and Setters =====================================================
    // generator:accessors

    public ProductService getProductService() {
        return productService;
    }

    public void setProductService(ProductService productService) {
        this.productService = productService;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public Integer getProductId() {
        return productId;
    }

    public void setProductId(Integer productId) {
        this.productId = productId;
    }
}

Struts settings for product controller

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "/WEB-INF/classes/struts-2.1.7.dtd">

<struts>
    <package name="product" namespace="/product" extends="default">
    	<!-- Index -->
        <action name="index" method="index" class="productController">
            <result name="success" type="tiles">productIndex</result>
            <result name="error" type="tiles">productIndex</result>
            <result name="input" type="tiles">productIndex</result>
        </action>

        <!-- Edit -->
        <action name="edit" method="edit" class="productController">
            <result name="success" type="tiles">productEdit</result>
            <result name="successSave" type="redirectAction">
                <param name="actionName">index</param>
                <param name="namespace">/product</param>
                <param name="status">success</param>
            </result>
            <result name="notFound" type="redirectAction">
                <param name="actionName">index</param>
                <param name="namespace">/product</param>
                <param name="status">notFound</param>
            </result>
            <result name="error" type="tiles">productEdit</result>
            <result name="input" type="tiles">productEdit</result>
        </action>

        <!-- Add -->
        <action name="add" method="add" class="productController">
            <result name="success" type="tiles">productAdd</result>
            <result name="successSave" type="redirectAction">
                <param name="actionName">index</param>
                <param name="namespace">/product</param>
                <param name="status">success</param>
            </result>
            <result name="error" type="tiles">productAdd</result>
            <result name="input" type="tiles">productAdd</result>
        </action>

        <!-- Delete -->
        <action name="delete" method="delete" class="productController">
        </action>

        <!-- generator:actions -->
    </package>
</struts>

For the View part of MVC, strutstool created baselayout.jsp, add.jsp, edit.jsp, form.jsp, and index.jsp.

baselayout.jsp

<%@page trimDirectiveWhitespaces="true" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<%@taglib uri="/struts-jquery-tags" prefix="sj" %>
<%@taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@taglib uri="http://htmlcompressor.googlecode.com/taglib/compressor" prefix="compress" %>

<compress:html enabled="true"
               compressJavaScript="true"
               jsCompressor="closure"
               removeComments="true"
               compressCss="true"
               removeIntertagSpaces="true"
               >

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title><tiles:insertAttribute name="mainTitle" ignore="true" /> - <tiles:insertAttribute name="title" ignore="true" /></title>

        <!-- jQuery headers -->
        <sj:head jqueryui="true" jquerytheme="ui-darkness" />

        <!-- Display Tag Style -->
        <link rel="stylesheet" type="text/css" href="<s:url value="/resources/displaytag.css"/>" />

        <style type="text/css">
            .ui-widget { font-size: 10pt; }
            #content {
                margin: 0 auto;
                width: 80%;
            }
            label {clear:both;display:block;}
            #messages {margin: 0 0 10px 0;}

        </style>
    </head>
    <body>
        <div id="wrapper">
            <div id="content">
                <tiles:insertAttribute name="body" />
            </div>
        </div>
    </body>
</html>
</compress:html>

add.jsp

<%@taglib uri="/struts-tags" prefix="s"%>

<h1>Add Product</h1>

<div>
    <s:form action="add" namespace="/product">
        <s:include value="form.jsp" />
    </s:form>
</div>

edit.jsp

<%@taglib uri="/struts-tags" prefix="s"%>

<h1>Edit Product ${productId}</h1>

<div>
	<s:if test="product != null">
		<s:form action="edit" namespace="/product">
		    <s:include value="form.jsp" />
		</s:form>
	</s:if>
	<s:else>
		<s:text name="error.notFound" />
	</s:else>
</div>

form.jsp

<%@taglib uri="/struts-tags" prefix="s"%>
<%@taglib uri="/struts-jquery-tags" prefix="sj" %>

<div id="messages">
<s:actionerror />
</div>

<s:push value="product">
	<s:hidden name="id" />

    <p>
        <s:label key="label.name" />
        <s:textfield name="name" />
        <s:fielderror fieldName="name" />
    </p>
    <p>
        <s:label key="label.description" />
        <s:textfield name="description" />
        <s:fielderror fieldName="description" />
    </p>
    <p>
        <s:label key="label.price" />
        <s:textfield name="price" />
        <s:fielderror fieldName="price" />
    </p>
    <p>
        <s:label key="label.stock" />
        <s:textfield name="stock" />
        <s:fielderror fieldName="stock" />
    </p>

	<p>
	    <s:hidden name="save" value="true" />
	    <s:submit key="label.save" name="" />
	    <s:submit key="form.cancel" onclick="window.location.href = './index.action';return false;" />
	</p>
</s:push>

index.jsp

<%@taglib uri="http://displaytag.sf.net" prefix="display" %> 
<%@taglib uri="/struts-tags" prefix="s" %>
<%@taglib uri="/struts-jquery-tags" prefix="sj" %>

<!-- set the namespace of the view -->
<s:set name="namespace">/product</s:set>

<h1>Index of Product</h1>

<div id="messages">
    <s:actionerror theme="jquery" />
    <s:actionmessage theme="jquery" />
</div>

<div id="tableOptions">
    <s:url id="addURL" action="add" namespace="%{#namespace}" />
    <s:submit value="Add" onclick="window.location.href = '%{addURL}';" />

    <!-- Search bar -->
    <s:include value="../helpers/searchBar.jsp" />
</div>

<!-- Get the options column name -->
<s:set name="options"><s:text name="table.header.options" /></s:set>
<s:set name="pagesize"><s:text name="table.pagesize" /></s:set>

<!-- Start: Display Tag Table -->
<display:table id="product" name="paginatedList" defaultsort="1" pagesize="${pagesize}" export="false" requestURI="/product/index.action">
    <!-- Set the current user id -->
    <s:set name="id">${product.id}</s:set>

    <!-- Create the edit url -->
    <s:url id="editURL" action="edit" namespace="%{#namespace}">
        <s:param name="productId" value="#id"></s:param>
    </s:url>

    <!-- Create the delete url -->
    <s:url id="deleteURL" action="delete" namespace="%{#namespace}">
        <s:param name="productId" value="#id"></s:param>
    </s:url>

    <!-- Start: COLUMNS -->
    <display:column titleKey="" >
        <input type="checkbox" name="select" value="${product.id}"/>
    </display:column>

    <display:column property="id" value="label.id" href="${editURL}" sortable="true" sortName="id" />

    <display:column property="name" value="label.name" sortable="true" />
    <display:column property="description" value="label.description" sortable="true" />
    <display:column property="price" value="label.price" sortable="true" />
    <display:column property="stock" value="label.stock" sortable="true" />

    <display:column title="${options}">

        <!-- Edit button -->
        <s:submit value="Edit" onclick="window.location.href = '%{editURL}';" />

        <!-- Start: Remove dialog -->
        <sj:dialog
            id="remove%{#id}"
            buttons="{
                    'Yes':function() {
                        $.post('%{deleteURL}', function(data) {
                            $('#remove%{#id}').dialog('close');
                            $('#successRemove').dialog('open');
                        });
                     },
                    'No':function() { $(this).dialog('close'); }
                    }"
            autoOpen="false"
            modal="true"
            title="Remove Product"
        >
        Do you want to remove product <s:property value="#id"/>?
        </sj:dialog>
        <!-- End: Remove dialog -->

        <!-- Button to open remove dialog -->
        <sj:submit
            openDialog="remove%{#id}"
            value="Remove"
            button="false"
        />

    </display:column>
    <!-- End: COLUMNS -->
    <display:setProperty name="paging.banner.placement" value="bottom" />
</display:table>
<!-- End: Display Tag Table -->

<sj:dialog
    id="successRemove"
    buttons="{
            'OK':function() { $(this).dialog('close'); }
            }"
    autoOpen="false"
    modal="true"
    title="Product removed!"
>
Product successful removed!
</sj:dialog>

The CRUD for product was magically done 🙂 Before we continue, make sure Hibernate is already configured. This is my configuration:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/prk</property>
    <property name="hibernate.connection.username">prk</property>
    <property name="hibernate.connection.password">prk</property>
    <property name="hibernate.show_sql">true</property>
    <!-- Hibernate Search Configuration -->
    <property name="hibernate.hbm2ddl.auto">update</property>
    <property name="hibernate.search.default.directory_provider">filesystem</property>
    <property name="hibernate.search.default.indexBase">/home/maycon/lucene/indexes</property>
    <!-- Here you include the hibernate mappings -->
    <!-- PLEASE DO NOT REMOVE THE COMMENT BELOW -->
    <!-- generator:mappings -->
    <mapping resource="com/ay/product/model/mapping/Product.hbm.xml"/>
    <mapping resource="com/ay/product/model/mapping/User.hbm.xml"/>
  </session-factory>
</hibernate-configuration>

Next, I implement the login feature

Model

User.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ay.product.model.entity;

/**
 *
 * @author ay12-0
 */
public class User {
    String username, password;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User() {
    }

}

User.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.ay.product.model.entity.User" table="user">
        <id name="username" column="username" type="string" length="255"/>
        <property name="password" column="password" not-null="true" type="string" length="255" />
    </class>
</hibernate-mapping>

View

index.jsp

<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
    <head>
        <title>Login</title>
        <link href="css/login.css" rel="stylesheet"/>
    </head>
    <body>
        <div class="container">
            <div class="login">
                <h1>Login</h1>

                <s:form method="post" action="login.action">
                    <p><input type="text" name="username" value="" placeholder="Username or Email"></p>
                    <p><input type="password" name="password" value="" placeholder="Password"></p>
                    <p class="submit"><s:actionerror/><input type="submit" name="commit" value="Login"></p>
                </s:form>
            </div>
        </div>
    </body>
</html>

Controller

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ay.login;

import com.framework.util.hibernate.HibernateUtil;
import com.opensymphony.xwork2.ActionSupport;
import org.hibernate.Query;
import org.hibernate.criterion.Restrictions;
import com.ay.product.model.entity.User;
import java.util.List;

public class LoginAction extends ActionSupport {

    private String username;
    private String password;

    public String authenticate() {
        List<User> users = HibernateUtil.getSession().
                createCriteria(User.class).add(Restrictions.eq("username", username)).add(Restrictions.eq("password", password)).list();
        if ("".equals(username) || "".equals(password)) {
            addActionError("Please fill all fields");
            return "error";
        } else if (users.size()==0){
            addActionError("Invalid username or password");
            return "error";
        }else{
            return "success";
        }
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

struts setting for whole project

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "/WEB-INF/classes/struts-2.1.7.dtd">

<struts>
    <!-- Struts theme -->
    <constant name="struts.ui.theme" value="simple" />
    <!-- Development mode -->
    <constant name="struts.devMode" value="true" />

    <!-- Enable global file 'global.properties' -->
    <constant name="struts.custom.i18n.resources" value="global" />

    <!-- Standard package configuration -->
    <package name="default" extends="struts-default">
        <!-- Tiles -->
        <result-types>
            <result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult" />
        </result-types>

        <interceptors>
            <!-- Declare your interceptors here -->

            <!-- // -->

            <!-- The application stack, place your interceptors here -->
            <interceptor-stack name="applicationStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>

                <interceptor-ref name="params">
                    <param name="excludeParams">dojo\..*,^struts\..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>

                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse,index</param>
                </interceptor-ref>

                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
            </interceptor-stack>
        </interceptors>

        <!-- Set the above interceptor stack as default -->
        <default-interceptor-ref name="applicationStack" />
        <!-- Here you can place global results for all actions -->
        <!--
        <global-results>

        </global-results>
        -->
        <action name="login" method="authenticate"
                class="com.ay.login.LoginAction">
            <result name="success" type="redirect">/product/index.action</result>
            <result name="error">index.jsp</result>
        </action>
    </package>

    <!-- Here you include other struts configuration files -->
    <!-- PLEASE DO NOT REMOVE THE COMMENT BELOW -->
    <!-- generator:includes -->
    <include file="com/ay/product/controller/config/ProductController.xml" />

</struts>

Web result

Login
Login

add
Add

index
Index

edit
Edit

That’s it. Thank you 🙂

About Aditya Ardiya

Aditya Ardiya 1501153210 Computer Science Student Bina Nusantara University http://www.binus.ac.id
This entry was posted in Advanced Topic in Software Engineering, Assignment. Bookmark the permalink.

Comments are closed.