Chapter 11

Application Design

There are two models used in Java web application design, conveniently called Model 1 and Model 2. Model 1 is page-centric and suitable for very small applications only. Model 2 is the recommended architecture for all but the simplest Java web applications.

This chapter discusses Model 2 in minute detail and provides three Model 2 sample applications. The first example features a basic Model 2 application with a servlet as the controller. The second example is also a simple Model 2 application, however it uses a filter as the controller. The third example introduces a validator component for validating user input.

Note

At the time of writing, an effort is being made to standardize MVC web frameworks through JSR 371.

Model 1 Overview

When you first learn JSP, your sample applications would normally enable navigation from one page to another by providing a clickable link to the latter. While this navigation method is straightforward, in medium-sized or large applications with significant numbers of pages this approach can cause a maintenance headache. Changing the name of a JSP page, for instance, could force you to rename the links to the page in many other pages. As such, Model 1 is not recommended unless your application will only have two or three pages.

Model 2 Overview

Model 2 is based on the Model-View-Controller (MVC) design pattern, the central concept behind the Smalltalk-80 user interface. As the term “design pattern” had not been coined at that time, it was called the MVC paradigm.

An application implementing the MVC pattern consists of three modules: model, view and controller. The view takes care of the display of the application. The model encapsulates the application data and business logic. The controller receives user input and commands the model and/or the view to change accordingly.

In Model 2, you have a servlet or a filter acting as the controller. All modern web frameworks are Model 2 implementations. Frameworks such as Struts 1, JavaServer Faces and Spring MVC employ a servlet controller in their MVC architectures, whereas Struts 2, another popular framework, uses a filter. Generally JSP pages are employed as the views of the application, even though other view technologies are supported. As the models, you use POJOs (POJO is an acronym for Plain Old Java Object). POJOs are ordinary objects, as opposed to Enterprise JavaBeans (EJB) or other special objects. Many people choose to use JavaBeans (plain JavaBeans, not EJBs) to store the states of model objects and move business logic to action classes.

Figure 11.1 shows the architecture of a Model 2 application.

Figure 11.1: Model 2 architecture

In a Model 2 application, every HTTP request must be directed to the controller. The request’s Uniform Request Identifier (URI) tells the controller what action to invoke. The term “action” refers to an operation that the application is able to perform. The Java object associated with an action is called an action object. A single action class may be used to serve different actions (as in Struts 2 and Spring MVC) or a single action (as in Struts 1).

A seemingly trivial operation may take more than one action. For instance, adding a product to a database would require two actions:

1. Display an “Add Product” form for the user to enter product information.

2. Save the product information in the database.

As mentioned above, you use the URI to tell the controller which action to invoke. For instance, to get the application to send the “Add Product” form, you would use a URI like this:

http://domain/appName/input-product

To get the application to save a product, the URI would be:

http://domain/appName/save-product

The controller examines the URI to decide what action to invoke. It also stores the model object in a place that can be accessed from the view, so that server-side values can be displayed on the browser. Finally, the controller uses a RequestDispatcher or HttpServletResponse.sendRedirect() to forward/redirect to a view (a JSP page or another resource). In the JSP page, you use the Expression Language expressions and custom tags to display values.

Note that calling RequestDispatcher.forward() or HttpServletResponse.sendRedirect() does not prevent the code below it from being executed. Therefore, unless the call is the last line in a method, you need to return explicitly.

if (action.equals(...)) {
    RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
    rd.forward(request, response);
    return;//explicitly return. Or else, the code below will be executed
}
// do something else

Most of the time, you would use a RequestDispatcher to forward to a view because it is faster than sendRedirect. This is due to the fact that a redirect causes the server to send the HTTP response status code 302 with a Location header containing a new URL to the browser. Upon receiving the status code 302, the browser makes a new HTTP request to the URL found in the Location header. In other words, a redirect requires a round-trip that makes it slower than a forward.

What is the advantage of using a redirect over a forward? With a redirect, you can direct the browser to a different application. You cannot do this with a forward. If a redirect is used to hit a different resource in the same application, it is because it yields a different URL than the original request URL. As such, if the user accidentally presses the browser Reload/Refresh button after a response is rendered, the code associated with the original request URL will not be executed again. For instance, you would not want the same code that makes a credit card payment re-executed just because the user accidentally pressed the Reload or Refresh button of her browser.

The last example in this chapter, the appdesign4 application, shows an example of a redirect and demonstrates how it can be done.

Model 2 with A Servlet Controller

This section presents a simple Model 2 application to give you a general idea of what a Model 2 application looks like. In real life, Model 2 applications are far more complex than this.

The application can be used to enter product information and is named appdesign1. The user fills in a form like the one in Figure 11.2 and submits it. The application then sends a confirmation page to the user and display the details of the saved product. (See Figure 11.3)

Figure 11.2: The Product form

Figure 11.3: The product details page

The application is capable of performing these two actions:

1. Display the “Add Product” form. This action sends the entry form in Figure 11.2 to the browser. The URI to invoke this action must contain the string input-product.

2. Save the product and returns the confirmation page in Figure 11.3. The URI to invoke this action must contain the string save-product.

The application consists of the following components:

1. A Product class that is the template for the model objects. An instance of this class contains product information.

2. A ProductForm class, which encapsulates the fields of the HTML form for inputting a product. The properties of a ProductForm are used to populate a Product.

3. A ControllerServlet class, which is the controller of this Model 2 application.

4. An action class named SaveProductAction.

5. Two JSP pages (ProductForm.jsp and ProductDetails.jsp) as the views.

6. A CSS file that defines the styles of the views. This is a static resource.

The directory structure of this application is shown in Figure 11.4.

Figure 11.4: appdesign1 directory structure

Let’s take a closer look at each component in appdesign1.

The Product Class

A Product instance is a JavaBean that encapsulates product information. The Product class (shown in Listing 11.1) has three properties: productName, description, and price.

Listing 11.1: The Product class

package appdesign1.model;
import java.io.Serializable;
import java.math.BigDecimal;

public class Product implements Serializable {
    private static final long serialVersionUID = 748392348L;
    private String name;
    private String description;
    private BigDecimal price;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

The Product class implements java.io.Serializable so that its instances can be stored safely in HttpSession objects. As an implementation of Serializable, Product should have a serialVersionUID field.

The ProductForm Class

A form class is mapped to an HTML form. It is the representation of the HTML form on the server. The ProductForm class, given in Listing 11.2, contains the String values of a product. At a glance the ProductForm class is similar to the Product class and you might question why ProductForm needs to exist at all. A form object, as you can see in the section “Validators” later in this chapter, saves passing the ServletRequest to other components, such as validators. ServletRequest is a servlet-specific type and should not be exposed to other layers of the applications.

The second purpose of a form object is to preserve and redisplay user input in its original form if input validation fails. You will learn how to do this in the section “Validators” later in this chapter.

Note that most of the time a form class does not have to implement Serializable as form objects are rarely stored in an HttpSession.

Listing 11.2: The ProductForm class

package appdesign1.form;
public class ProductForm {
    private String name;
    private String description;
    private String price;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
}

The ControllerServlet Class

The ControllerServlet class (presented in Listing 11.3) extends the javax.servlet.http.HttpServlet class. Both its doGet and doPost methods call the process method, which is the brain of the servlet controller.

I am probably raising a few eyebrows here by naming the servlet controller ControllerServlet, but I’m following the convention that says all servlet classes should be suffixed with Servlet.

Listing 11.3: The ControllerServlet Class

package appdesign1.controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import appdesign1.action.SaveProductAction;
import appdesign1.form.ProductForm;
import appdesign1.model.Product;
import java.math.BigDecimal;

@WebServlet(name = "ControllerServlet", urlPatterns = {
        "/input-product", "/save-product" })
public class ControllerServlet extends HttpServlet {

    private static final long serialVersionUID = 1579L;

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {

        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName,
         * for example: /appdesign1/input-product.
         * However, in the event of a default context, the
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /input-product
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        // execute an action
        String dispatchUrl = null;
        if ("input-product".equals(action)) {
            // no action class, just forward
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // create form
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            // create model
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
            	   product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method
            SaveProductAction saveProductAction =
            		new SaveProductAction();
            saveProductAction.save(product);

            // store model in a scope variable for the view
            request.setAttribute("product", product);
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }

        if (dispatchUrl != null) {
            RequestDispatcher rd =
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}

The process method in the ControllerServlet class processes all incoming requests. It starts by obtaining the request URI and the action name.

String uri = request.getRequestURI();
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1); 

The value of action in this application can be either input-product or save-product.

The process method then continues by performing these steps:

1. Instantiate a relevant action class, if any.

2. If an action object exists, create and populate a form object with request parameters. There are three properties in the save-product action: name, description, and price. Next, create a model object and populate its properties from the form object.

3. If an action object exists, call the action method.

4. Forward the request to a view (JSP page).

The part of the process method that determines what action to perform is in the following if block:

        // execute an action
        if ("input-product".equals(action)) {
            // no action class, just forward
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // instantiate action class
            ...
        }

There is no action class to instantiate for action input-product. For save-product, the process method creates a ProductForm and a Product and copies values from the former to the latter. At this stage, there’s no guarantee all non-string properties, such as price, can be copied successfully, but we’ll deal with this later in the section “Validators.” The process method then instantiates the SaveProductAction class and calls its save method.

            // create form
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            // create model
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
            	product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method
            SaveProductAction saveProductAction =
            		new SaveProductAction();
            saveProductAction.save(product);

The Product is then stored in the HttpServletRequest so that the view can access it.

            // store action in a scope variable for the view
            request.setAttribute("product", product);

The process method concludes by forwarding to a view. If action equals input-product, control is forwarded to the ProductForm.jsp page. If action is save-product, control is forwarded to the ProductDetails.jsp page.

        // forward to a view
        if (dispatchUrl != null) {
            RequestDispatcher rd = 
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }

The Action Class

There is only one action class in the application, which is responsible for saving a product to some storage, such as a database. The action class is named SaveProductAction and is given in Listing 11.4.

Listing 11.4: The SaveProductAction class

package appdesign1.action;

public class SaveProductAction {
    public void save(Product product) {
        // insert Product to the database
    }
}

In this example, the SaveProductAction class does not have implementation for its save method. You will provide an implementation in the next examples in this chapter.

The Views

The application utilizes two JSP pages for the views of the application. The first page, ProductForm.jsp, is displayed if the action is input-product. The second page, ProductDetails.jsp, is shown for save-product. ProductForm.jsp is given in Listing 11.5 and ProductDetails.jsp in Listing 11.6.

Listing 11.5: The ProductForm.jsp page

<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="save-product">
    <h1>Add Product 
        <span>Please use this form to enter product details</span>
    </h1>
    <label>
        <span>Product Name :</span>
        <input id="name" type="text" name="name" 
            placeholder="The complete product name"/>
    </label>
    <label>
        <span>Description :</span>
        <input id="description" type="text" name="description" 
            placeholder="Product description"/>
    </label>
    <label>
        <span>Price :</span>
        <input id="price" name="price" type="number" step="any"
            placeholder="Product price in #.## format"/>
    </label> 
    <label>
        <span>&nbsp;</span> 
        <input type="submit"/> 
    </label> 
</form>
</body>
</html>

Note

Do not use an HTML table to format a form. Instead, use CSS.

Note

The step attribute in the price input field forces the browser to allow for a decimal number.

Listing 11.6: The ProductDetails.jsp page

<!DOCTYPE html>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    </p>
</div>
</body>
</html>

The ProductForm.jsp page contains an HTML form for entering product details. The ProductDetails.jsp page uses the Expression Language (EL) to access the product scoped-object in the HttpServletRequest.

In this application, as is the case for most Model 2 applications, you need to prevent the JSP pages from being accessed directly from the browser. There are a number of ways to achieve this, including:

  • Putting the JSP pages under WEB-INF. Anything under WEB-INF or a subdirectory under WEB-INF is protected. If you put your JSP pages under WEB-INF you cannot access them directly from the browser, but the controller can still dispatch requests to those pages. However, this is not a recommended approach since not all containers implement this feature.
  • Using a servlet filter and filter out requests for JSP pages.
  • Using security restriction in the deployment descriptor. This is easier than using a filter since you do not have to write a filter class. This method is chosen for this application.

Testing the Application

Assuming you are running the application on your local machine on port 8080, you can invoke the application using the following URL:

http://localhost:8080/appdesign1/input-product

You will see something similar to Figure 11.2 in your browser.

When you submit the form, the following URL will be sent to the server:

http://localhost:8080/appdesign1/save-product

Using a servlet controller allows you to use the servlet as a welcome page. This is an important feature since you can then configure your application so that the servlet controller will be invoked simply by typing your domain name (such as http://example.com) in the browser’s address box. You can’t do this with a filter.

Model 2 with A Filter Dispatcher

While a servlet is the most common controller in a Model 2 application, a filter can act as a controller too. Note, however, that a filter does not have the privilege to act as a welcome page. Simply typing the domain name won’t invoke a filter dispatcher. Struts 2 uses a filter as a controller because the filter is used to serve static contents too.

The following example (appdesign2) is a Model 2 application that uses a filter dispatcher. The directory structure of appdesign2 is shown in Figure 11.5.

Figure 11.5: appdesign2 directory structure

The JSP pages and the Product class are the same as the ones in appdesign1. However, instead of a servlet as controller, you have a filter called FilterDispatcher (given in Listing 11.7).

Listing 11.7: The DispatcherFilter class

package appdesign2.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import appdesign2.action.SaveProductAction;
import appdesign2.form.ProductForm;
import appdesign2.model.Product;
import java.math.BigDecimal;

@WebFilter(filterName = "DispatcherFilter",
        urlPatterns = { "/*" })
public class DispatcherFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig)
            throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request,
            ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String uri = req.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName, for
         * example /appdesign2/input-product. However, in the
         * case of a default context, the context name is empty,
         * and uri has this form /resourceName, e.g.:
         * /input-product
         */
        // action processing
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        String dispatchUrl = null;
        if ("input-product".equals(action)) {
            // do nothing
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if (action.equals(action)) {
            // create form
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            
            // create model
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(product.getDescription());
            try {
                product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method
            SaveProductAction saveProductAction = 
                    new SaveProductAction();
            saveProductAction.save(product);
            
            // store model in a scope variable for the view
            request.setAttribute("product", product);
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }

        // forward to a view
        if (dispatchUrl != null) {
            RequestDispatcher rd = request
                    .getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        } else {
            // let static contents pass
            filterChain.doFilter(request, response);
        }
    }
}

The doFilter method performs what the process method in appdesign1 did.

Since the filter targets all URLs including static contents, you need to call filterChain.doFilter() if no action is invoked.

        } else {
            // let static contents pass
            filterChain.doFilter(request, response);
        }

To test the application, direct your browser to this URL:

http://localhost:8080/appdesign2/input-product

Validators

Input validation is an important step in performing an action. Validation ranges from simple tasks like checking if an input field has a value to more complex ones like verifying a credit card number. In fact, validation play such an important role that the Java community has published JSR 303, “Bean Validation” to standardize input validation in Java. Modern MVC frameworks often offer both programmatic and declarative validation methods. In programmatic validation, you write code to validate user input. In declarative validation, you provide validation rules in XML documents or properties files.

Note

Even though you can perform client-side input validation with HTML5 or JavaScript, don’t rely on it because the savvy user can bypass it easily. Always perform server-side input validation!

The following example features a new application (appdesign3) that extends the servlet controller-based Model 2 application in appdesign1. The new application incorporates a product validator whose class is given in Listing 11.8.

Listing 11.8: The ProductValidator class

package appdesign3.validator;
import java.util.ArrayList;
import java.util.List;
import appdesign3.form.ProductForm;

public class ProductValidator {
    public List<String> validate(ProductForm productForm) {
        List<String> errors = new ArrayList<>();
        String name = productForm.getName();
        if (name == null || name.trim().isEmpty()) {
            errors.add("Product must have a name");
        }
        String price = productForm.getPrice();
        if (price == null || price.trim().isEmpty()) {
            errors.add("Product must have a price");
        } else {
            try {
                Float.parseFloat(price);
            } catch (NumberFormatException e) {
                errors.add("Invalid price value");
            }
        }
        return errors;
    }
}

The ProductValidator class in Listing 11.8 offers a validate method that works on a ProductForm. The validator makes sure that a product has a non-empty name and its price is a valid number. The validate method returns a List of Strings containing validation error messages. An empty List means successful validation.

Now that you have a validator, you need to tell the controller to use it. Listing 11.9 presents the revised version of ControllerServlet. Pay special attention to the lines in bold.

Listing 11.9: The ControllerServlet class in appdesign3

package appdesign3.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import appdesign3.action.SaveProductAction;
import appdesign3.form.ProductForm;
import appdesign3.model.Product;
import appdesign3.validator.ProductValidator;
import java.math.BigDecimal;

@WebServlet(name = "ControllerServlet", urlPatterns = { 
        "/input-product", "/save-product" })
public class ControllerServlet extends HttpServlet {
    
    private static final long serialVersionUID = 98279L;

    @Override
    public void doGet(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request,
            HttpServletResponse response) 
            throws IOException, ServletException {

        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName, 
         * for example: /appdesign1/input-product. 
         * However, in the case of a default context, the 
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /input-product
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1); 
        String dispatchUrl = null;

        if ("input-product".equals(action)) {
            // no action class, there is nothing to be done
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // instantiate action class
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(
                    request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            
            // validate ProductForm
            ProductValidator productValidator = new
                    ProductValidator();
            List<String> errors = 
                    productValidator.validate(productForm);
            if (errors.isEmpty()) {
                // create Product from ProductForm
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(
                        productForm.getDescription());
                product.setPrice(new BigDecimal(productForm.getPrice()));
                
                // no validation error, execute action method
                SaveProductAction saveProductAction = new 
                        SaveProductAction();
                saveProductAction.save(product);
                
                // store action in a scope variable for the view
                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {
                request.setAttribute("errors", errors);
                request.setAttribute("form", productForm);
                dispatchUrl = "/jsp/ProductForm.jsp";
            }
        }

        // forward to a view
        if (dispatchUrl != null) {
            RequestDispatcher rd = 
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}

The new ControllerServlet class in Listing 11.9 inserts code that instantiates the ProductValidator class and calls its validate method on save-product.

            // validate ProductForm
            ProductValidator productValidator = new
                    ProductValidator();
            List<String> errors = 
                    productValidator.validate(productForm);

The validate method takes a ProductForm, which encapsulates product information entered to the HTML form. Without a ProductForm you would have to pass the ServletRequest to the validator.

The validate method returns an empty List if validation was successful, in which case a Product will be created and passed to a SaveProductAction. Upon successful validation, the controller stores the Product in the ServletContext and forwards to the ProductDetails.jsp page, which then displays the product’s details. If validation failed, the controller stores the errors List and the ProductForm in the ServletContext and forwards back to ProductForm.jsp.

         if (errors.isEmpty()) {
                // create Product from ProductForm
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(
                        productForm.getDescription());
                product.setPrice(new BigDecimal(productForm.getPrice()));
                
                // no validation error, execute action method
                SaveProductAction saveProductAction = new 
                        SaveProductAction();
                saveProductAction.save(product);
                
                // store action in a scope variable for the view
                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {
                request.setAttribute("errors", errors);
                request.setAttribute("form", productForm);
                dispatchUrl = "/jsp/ProductForm.jsp";
            }

The ProductForm.jsp page in appdesign3 has been modified to give it the capability of showing error messages and redisplaying invalid values. Listing 11.10 shows the ProductForm.jsp in appdesign3.

Listing 11.10: The ProductForm.jsp page in appdesign3

<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="save-product">
    <h1>Add Product 
        <span>Please use this form to enter product details</span>
    </h1>
    ${empty requestScope.errors? "" : "<p style='color:red'>" 
      += "Error(s)!"
      += "<ul>"}
    <!--${requestScope.errors.stream().map(
          x -> "--><li>"+=x+="</li><!--").toList()}-->
    ${empty requestScope.errors? "" : "</ul></p>"}
    <label>
        <span>Product Name :</span>
        <input id="name" type="text" name="name" 
            placeholder="The complete product name"
            value="${form.name}"/>
    </label>
    <label>
        <span>Description :</span>
        <input id="description" type="text" name="description" 
            placeholder="Product description"
            value="${form.description}"/>
    </label>
    <label>
        <span>Price :</span>
        <input id="price" name="price" type="number" step="any"
            placeholder="Product price in #.## format"
            value="${form.price}"/>
    </label> 
    <label>
        <span>&nbsp;</span> 
        <input type="submit"/> 
    </label> 
</form>
</body>
</html>

You can test appdesign3 by invoking the input-product action:

http://localhost:8080/appdesign3/input-product

Unlike the previous examples, if the Product form contains an invalid value when you submit it, an error message will be displayed along with the incorrect value. Figure 11.6 shows two validation error messages.

Figure 11.6: The ProductForm with error messages

Dependency Injection

People have been using dependency injection in the past decade as a solution to, among others, code testability. In fact, dependency injection is behind great frameworks such as Spring MVC and Struts 2. So, what is dependency injection?

Martin Fowler wrote an excellent article on this subject:

http://martinfowler.com/articles/injection.html

Before Fowler coined the term “dependency injection,” the phrase “inversion of control” was often used to mean the same thing. As Fowler notes in his article, the two are not exactly the same.

If you have two components, A and B, and A depends on B, you can say A is dependent on B or B is a dependency of A. Suppose A has a method, importantMethod, that uses B as defined in the following code fragment:

public class A {
    public void importantMethod() {
        B b = ... // get an instance of B
        b.usefulMethod();
        ...
    }
    ...
}

A must obtain an instance of B before it can use B. While it is as straightforward as using the new keyword if B is a Java concrete class, it can be problematic if B is not and there are various implementations of B. You will have to choose an implementation of B and by doing so you reduce the reusability of A because you cannot use A with implementations of B that you did not choose.

As an example, consider the appdesign4 application that is used to generate PDFs. This small application has two actions, form and pdf. The first does not have an action class and simply forwards to a form that can be used to enter some text. The second generates a PDF file and uses a PDFAction class. The action class itself relies on a service class that generates the PDF.

The PDFAction and PDFService classes are given in Listing 11.11 and Listing 11.12, respectively.

Listing 11.11: The PDFAction class

package action;
import service.PDFService;

public class PDFAction {
    private PDFService pdfService;

    public void setPDFService(PDFService pdfService) {
        this.pdfService = pdfService;
    }

    public void createPDF(String path, String input) {
        pdfService.createPDF(path, input);
    }
}

Listing 11.12: The PDFService class

package service;
import util.PDFUtil;

public class PDFService {
    public void createPDF(String path, String input) {
        PDFUtil.createDocument(path, input);
    }
}

PDFService uses a PDFUtil class, which in turn employs the Apache PDFBox library to create PDF documents. Feel free to look up the PDFUtil class in appdesign4 if you’re interested in working with PDF.

What’s important is this. As shown in Listing 11.11, PDFAction requires a PDFService to do its job. In other words, PDFAction is dependent on PDFService. Without dependency injection, you would have to instantiate the PDFService class inside the PDFAction class and this would make PDFAction less testable. On top of that, you would have to recompile PDFAction if you need to change the implementation of PDFService.

With dependency injection every component has its dependencies injected to it and this makes testing each component easier. Far easier. For a class to be used in a dependency injection environment, you have to make it inject-ready. One way to do it is to create a set method for each dependency. For example, the PDFAction class has a setPDFService method that can be called to pass a PDFService. Injection can also occur through a constructor or a class field.

Once all your classes are inject-ready, you can choose a dependency injection framework and import it to your project. Spring Framework, Google Guice, Weld, and PicoContainer are some good ones.

Note

Dependency injection in Java is specified in JSR 330 and JSR 299.

The appdesign4 application uses the DependencyInjector class in 11.13 in lieu of a dependency injection framework. (In a real-world application, of course you would use a proper framework.) This class has been designed to work with appdesign4 alone and can be instantiated easily, with or without a container. Once instantiated, its start method must be called to perform initialization. After use, its shutdown method should be called to release resources. In this example, both start and shutdown are empty.

Listing 11.13: The DependencyInjector class

package util;
import action.PDFAction;
import service.PDFService;

public class DependencyInjector {
 
    public void start() {
        // initialization code
    }
 
    public void shutDown() {
        // clean-up code
    }
 
    /*
     * Returns an instance of type. type is of type Class
     * and not String because it's easy to misspell a class name
     */
    public Object getObject(Class type) {
        if (type == PDFService.class) {
            return new PDFService();
        } else if (type == PDFAction.class) {
            PDFService pdfService = (PDFService) 
                    getObject(PDFService.class);
            PDFAction action = new PDFAction();
            action.setPDFService(pdfService);
            return action;
        }
        return null;
    }
}

To obtain an object from a DependencyInjector, call its getObject method, passing the Class of the expected object. DependencyInjector supports two types, PDFAction and PDFService. For example, to get an instance of PDFAction, you would call getObject by passing PDFAction.class:

PDFAction pdfAction = (PDFAction) 
        dependencyInjector.getObject(PDFAction.class);

The beauty of DependencyInjector (and all dependency injection frameworks) is that the object it returns comes injected with dependencies. If a dependency has dependencies, the dependency is also injected with its own dependencies. For instance, a PDFAction you obtain from a DependencyInjector already contains a PDFService. There is no need to create a PDFService yourself in the PDFAction class.

The servlet controller in appdesign4 is given in Listing 11.14. Note that it instantiates the DependencyInjector in its init method and calls the DependencyInjector’s shutdown method in its destroy method. The servlet no longer creates a dependency on its own. Instead, it obtains it from the DependencyInjector.

Listing 11.14: The ControllerServlet class in appdesign4

package servlet;
import action.PDFAction;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import util.DependencyInjector;

@WebServlet(name = "ControllerServlet", urlPatterns = {
    "/form", "/pdf"})
public class ControllerServlet extends HttpServlet {
private static final long serialVersionUID = 6679L;
    private DependencyInjector dependencyInjector;
 
    @Override
    public void init() {
        dependencyInjector = new DependencyInjector();
        dependencyInjector.start();
    }
 
    @Override
    public void destroy() {
        dependencyInjector.shutDown();
    }
    protected void process(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        ReadListener r = null;
        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName,
         * for example: /app10a/product_input.
         * However, in the case of a default context, the
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /pdf
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        if ("form".equals(action)) {
            String dispatchUrl = "/jsp/Form.jsp";
            RequestDispatcher rd = 
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        } else if ("pdf".equals(action)) {
            HttpSession session = request.getSession(true);
            String sessionId = session.getId();
            PDFAction pdfAction = (PDFAction) dependencyInjector
                    .getObject(PDFAction.class);
            String text = request.getParameter("text");
            String path = request.getServletContext()
                    .getRealPath("/result") + sessionId + ".pdf";
            pdfAction.createPDF(path, text);
            
            // redirect to the new pdf
            StringBuilder redirect = new 
                    StringBuilder();
            redirect.append(request.getScheme() + "://");
            redirect.append(request.getLocalName());
            int port = request.getLocalPort();
            if (port != 80) {
                redirect.append(":" + port);
            }
            String contextPath = request.getContextPath();
            if (!"/".equals(contextPath)) {
                redirect.append(contextPath);
            }
            redirect.append("/result/" + sessionId + ".pdf");
            response.sendRedirect(redirect.toString());
        }
    }

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        process(request, response);
    }
    
    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        process(request, response);
    }
}

The servlet supports two URL patterns, form and pdf. The form pattern simply forwards to a form. The pdf pattern uses a PDFAction and calls its createDocument method. This method takes a file path and a text input. All PDFs are stored in the result directory under the application directory and the user’s session identifier is used as the file name. The text input becomes the content of the generated PDF file. Finally, the pdf action is redirected to the generated PDF file. Here is the code that creates the redirection URL and redirects the browser to the new URL:

            // redirect to the new pdf
            StringBuilder redirect = new 
                    StringBuilder();
            redirect.append(request.getScheme() + "://"); //http or https
            redirect.append(request.getLocalName()); // the domain
            int port = request.getLocalPort();
            if (port != 80) {
                redirect.append(":" + port);
            }
            String contextPath = request.getContextPath();
            if (!"/".equals(contextPath)) {
                redirect.append(contextPath);
            }
            redirect.append("/result/" + sessionId + ".pdf");
            response.sendRedirect(redirect.toString());

You can test appdesign4 by invoking this URL:

http://localhost:8080/appdesign4/form

This will send a form to the browser (See Figure 11.7)

Figure 11.7: The PDF form

If you enter some input in the text field and press the Submit button, the server will create a PDF file and send a redirect to your browser to fetch it. (See Figure 11.8)

Figure 11.8: A PDF file

Note that the redirection URL will be in this format.

http://localhost:8080/appdesign4/result/sessionId.pdf

Thanks to the dependency injector, each component in appdesign4 can be tested independently. For example, the PDFActionTest class in Listing 11.16 can be run to test the class’s createDocument method.

Listing 11.16: The PDFActionTest class

package test;
import action.PDFAction;
import util.DependencyInjector;

public class PDFActionTest {
    public static void main(String[] args) {
        DependencyInjector dependencyInjector = new DependencyInjector();
        dependencyInjector.start();
        PDFAction pdfAction = (PDFAction) dependencyInjector.getObject(
                PDFAction.class);
        pdfAction.createPDF("/home/janeexample/Downloads/1.pdf", 
                "Testing PDFAction....");
        dependencyInjector.shutDown();
    }
}

If you are using a Java 7 EE container like Glassfish, it is possible to have the container inject dependencies to a servlet. The servlet in appdesign4 would look like this:

public class ControllerServlet extends HttpServlet {
    @Inject PDFAction pdfAction;
    ...

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            ServletException {
        ...
    }

    @Override
    public void doPost(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            ServletException {
        ...
    }
}

Summary

In this chapter you have learned about the Model 2 architecture, which is based on the MVC pattern, and how to write Model 2 applications using either a servlet controller or a filter dispatcher. These two types of Model 2 applications were demonstrated in appdesign1 and appdesign2, respectively. One clear advantage of using a servlet as the controller over a filter is that you can configure the servlet as a welcome page. In a Model 2 application, JSP pages are often used as the view, even though other technologies such as Apache Velocity and FreeMarker can also be used. If JSP pages are used as the view in a Model 2 architecture, those pages are used to display values only and no scripting elements should be present in them.

In this chapter you have also built a simple MVC framework incorporating a validator and equips it with a dependency injector. While the homemade framework serves as a good educational tool, going forward you should base your MVC projects on a mature MVC framework like Struts 2, JavaServer Faces or Spring MVC and not try to reinvent the wheel.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.22.169