Monday, May 8, 2017

E-Commerce com Servlets, JSP e JPA: parte 2

Na primeira parte do nosso projeto de loja virtual criamos as entidades e configuramos o framework de persistência Java Persistence API (JPA) que gerou automaticamente todas as tabelas no banco de dados e as respectivas relações entre elas a partir das classes no pacote model. Esse processo do JPA de gerar todas as tabelas prontamente configuradas no banco de dados é chamado de mapeamento objeto-relacional.

A camada de modelo dados de nosso projeto já está pronta. Nesta segunda parte vamos criar camada view, que é a camada responsável por gerar a interface de comunicação entre o usuário e a nossa aplicação. Para tanto utilizaremos os frameworks Java Server Pages (JSP) e Servlets.

O Diagrama de Casos de Uso abaixo nos mostra todas as possíveis ações que o usuário pode realizar no nosso sistema:


As ações em um diagrama de casos de uso podem ser mapeadas para um método na classse responsável, geralmente chamada service. Por exemplo, quando usuário selecionar uma categoria, o sistema deve mostrar-lhe algum tipo de lista contendo todos os produtos da categoria selecionada. Para satisfazer esse caso de uso poderíamos criar um método cuja assinatura é parecida com esta List<Product> getProducts(Categorie cat).

JSP: criando a interface com o usuário

Todas as páginas de nossa aplicação terão o design semelhante, com um cabeçalho, exibindo o nome da loja; um painel de menus do lado esquerdo, mostrando as categorias; e no centro o conteúdo variável, listas de produtos, descrições, carinho de compras, etc. Como mostrado na figura:



Com o NetBenas aberto crie a pasta pages dentro de Web Pages, que é onde ficarão todas as páginas web da nossa aplicação. Em seguida clique com o botão direito do mouse na pasta pagesnew, JSP... Crie as páginas:

  • header.jsp (o cabeçalho que será usado por todas as páginas)
  • menu.jsp (o menu lateral que será usado por todas as páginas)
  • home.jsp (página inicial)
  • productsByCat.jsp (exibe os produtos por filtrados por categoria)
  • productDetails.jsp (exibe os detalhes de um produto selecionado)
  • shoppingCart.jsp (exibe os produtos no carrinho de compras)
  • checkout.jsp (exibe os dados do pedido para o usuário confirmar a compra)
  • login.jsp (para fazer pedido usuário precisa fazer login)
  • register.jsp (para fazer login o usuário precisa informar os dados e cadastrar)
  • thankyou.jsp (mensagem de agradecimento após a compra)
Crie também um pacote chamado control. Dentro dele crie uma classe chamada ServletController. Está classe será responsável por gerenciar a navegação entre as páginas da aplicação:



Controlando navegação de páginas

Nossa aplicação segue o padrão Model-View-Controller (MVC). Uma servlet  intercepta toda requisição HTTP e encaminha a resposta para ser apresentada pela página JSP correspondente baseado na URL da requisição, nos input parameters e no estado da aplicação. O valor do parâmetro action em cada página JSP  define o comportamento da servlet.


ServletController

Declaração da classe e o método init():
//imports omitidos
//@WebServlet diz para o servidor que esta classe é uma servlet, e qual é a sua url no browser
//@WebInitParam indica parâmetros de inicialização
@WebServlet(name = "ServletController", urlPatterns = {"/servletController"},
                    initParams = {
                        @WebInitParam(name = "controller", value = "servletController"),
                        @WebInitParam(name = "imagesUrl", value = "images/")})
public class ServletController extends HttpServlet{

    @Override
    public void init(ServletConfig config) throws ServletException {

      //configura os parâmetros de inicialização em ServletContext,
      //de modo que eles ficarão acessíveis de qualquer parte da aplicação, em qualquer momento
        ServletContext context = config.getServletContext();
        context.setAttribute("controller", config.getInitParameter("controller"));
        context.setAttribute("imagesUrl", config.getInitParameter("imagesUrl"));
    }
}

O método doGet() geralmente é usado para exibir informações de consulta com base em parâmetros. Não é um método adequado para para enviar enviar informações sensíveis pois o valor dos parâmetros aparece na URL:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

 //recebe o parâmetro action e com base no valor decide para qual página despachar
 String action = req.getParameter("action");
 String url = "/pages/home.jsp";
 
 if(action != null){

   //já existe uma sessão aberta
   ClientService clientService = (ClientService) req.getSession().getAttribute("clientService");
 
   if(action.equals("productsByCat")){
      url = "/pages/productsByCat.jsp";
      String categoryId = req.getParameter("categoryId");
      req.setAttribute("categoryId", categoryId); 
   }
   else if(action.equals("productDetails")){
      url = "/pages/productDetails.jsp";
      String productId = req.getParameter("productId");
      req.setAttribute("productId", productId);
   }
   else if(action.equals("addProductToCart")){ 
      clientService.addProductToCart(req.getParameter("productId"));
      url = "/pages/shoppingCart.jsp";
   }
   else if(action.equals("displayShoppingCart")){
      url = "/pages/shoppingCart.jsp";
   }

   //o usuário quer efetivar o pedido, mas antes precisa fazer login, 
   //se não tiver login, terá que se cadastrar
   else if(action.equals("checkout")){
      boolean logged = clientService.isLogged();
      if(logged)
         url = "/pages/checkout.jsp";
      else
         url = "/pages/login.jsp";
      }
      else if(action.equals("register")){
         url = "/pages/register.jsp";
      }
   }
   //direciona para a página adequada
   req.getRequestDispatcher(url).forward(req, resp); 
}

O método doPost() é usado para enviar informações de formulário. Na nossa aplicação ele utilizado para login, cadastro do usuário, finalizar o pedido e mudar o estado do carrinho de compras.
@Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
   //recupera action e decide o que fazer com base no valor 
   String action = req.getParameter("action");
   String url = "/pages/shoppingCart.jsp";
 
   if(action != null){
 
      ClientService clientService = (ClientService) req.getSession().getAttribute("clientService");
 
      if(action.equals("updateCart")){
         String quantity = req.getParameter("quantity");
         String productId = req.getParameter("productId"); 
         clientService.updateShoppingCart(productId, quantity);
      } 
      else if(action.equals("deleteProduct")){
         String productId = req.getParameter("productId");
         clientService.deleteProduct(productId);
     }
     else if(action.equals("register")){
        clientService.register(req); 
        url = "/pages/checkout.jsp"; 
     }
     else if(action.equals("checkout")){ 
        EOrder order = clientService.proceedCheckout();
        req.setAttribute("order", order);
        url = "/pages/thankyou.jsp";
     }
     else if(action.equals("login")){
        clientService.login(req.getParameter("email"), req.getParameter("login"));
        if(clientService.isLogged())
           url = "/pages/checkout.jsp";
        else
           url = "/pages/login.jsp";
        }
    } 
    req.getRequestDispatcher(url).forward(req, resp);
 }
Páginas JSP

As páginas header.jsp e menu.jsp estarão presentes em todas as páginas, de modo que elas serão páginas de composição através da declaração tag jsp:include nas outras páginas, assim não é necessário escrever o mesmo código repetidas vezes.

header.jsp
<center>
<table>
    <tr>
        <td><img src="${applicationScope['imagesUrl']}stark.png" style="width: 80px; height: 70px;" /></td>
        <td>
            <a href="${applicationScope['controller']}">
                <h4>STARK HOUSE E-commerce - BuyDigital!</h4>
            </a>   
        </td>
        <td>
            <a href="${applicationScope['controller']}?action=displayShoppingCart">
                <img src="${applicationScope['imagesUrl']}shopping_cart.png" style="width: 50px; height: 50px;" />
            </a>           
        </td>       
    </tr>
</table>
</center>
menu.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="categoryService" class="business.CategoryService" scope="session"/>
<jsp:useBean id="clientService" class="business.ClientService" scope="session"/>
<form>
 <table>
     <c:if test="${clientService.logged}">
         <tr>
             Bem Vindo ${clientService.client.fullName}
         </tr>
     </c:if>             
    <tr>
        <td>
            <input type="text" name="search"/>           
        </td>
        <td><input type="button" value="SEARCH"/></td>
    </tr>   
    <c:forEach items="${categoryService.categories}" var="category">
        <tr>
            <td>
<!-- parâmetro action e categoryId são enviados à servlet controller quando o usuário usar este link -->
  <a href="${applicationScope['controller']}?action=productsByCat&categoryId=${category.id}">

                                                ${category.name}</a>
            </td>
        </tr>
    </c:forEach>    
 </table>      
</form>
Nas páginas JSP a expressão ${applicationScope['attributeName']} representa um atributo de contexto da apĺicação, aqueles mesmos que foram definidos no método initi() da Servlet. Como o próprio nome sugere, tais atributos têm escopo de aplicação, ficam disponíveis enquanto a aplicação rodar no servidor.

O valor do parâmetro action vai variando de acordo com o link do qual ele é enviado para o controller. Na página menu.jsp seu valor é productsByCat, indicando ao controller para carregar uma exibição de produtos filtrados por categoria. O id da categoria é informado em um segundo parâmetro chamado categoryId.

A tag jsp:useBean permite que você crie ou localize uma instância de uma classe. Como o escopo informado é sessão, o bean será criado apenas uma vez, e ficará diponível enquanto durar a sessão do usuário.

Home.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>JSP Page</title>
   </head>
   <body>
       <table style="border: 1px solid #cccccc; width: 1000px">
           <tr> 
               <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
           </tr>
           <tr>
              <td><jsp:include page="menu.jsp" flush="true" /></td>
              <td> 
                   <img src="${applicationScope['imagesUrl']}store.jpg" style="width: 150px; height: 150px;" />
              </td>
          </tr>
       </table>
   </body>
</html>

productsByCat.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<jsp:useBean id="productService" class="business.ProductService" scope="session"/>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
               <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
            </tr>
            <tr>
               <td><jsp:include page="menu.jsp" flush="true" /></td>
               <td> 
                  <table style="width: 500px">
                      <tr>
                          <th>Name</th> 
                          <th>Price</th>
                       </tr>
                      <c:forEach items="${productService.getProducts(requestScope.categoryId)}" var="product"> 
                          <tr>
                              <td>${product.name}</td>
                              <td>${product.price}</td>
                              <td>
         <a href="${applicationScope['controller']}?action=productDetails&productId=${product.id}">
                                 Details...</a>
                              </td>
                          </tr> 
                     </c:forEach>
                  </table> 
              </td>
           </tr>
        </table>
   </body>
</html>
A página productsByCat lista os produtos de uma categoria. A tag <c:ForEach/> realiza um loop pela coleção retornada pela chamada do método getProducts() do bean productService.

productsDetails.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<jsp:useBean id="productService" class="business.ProductService" scope="session"/>
<c:set var="product" value="${productService.getProduct(requestScope.productId)}" scope="request" />
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body> 
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
                <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
            </tr>
            <tr>
               <td><jsp:include page="menu.jsp" flush="true" /></td>
            <td> 
                <table>
                    <th colspan="2">
                        ${product.name}
                    </th>
                    <tr>
                        <td colspan="2">${product.description}</td>
                    </tr>
                    <tr style="float: left;">
                        <td>Price $</td>
                        <td>${product.price}</td>
                    </tr>
                    <tr>
                        <td>
                            <a href="${applicationScope['controller']}?action=addProductToCart&productId=${product.id}">
                                Add To Cart</a>
                        </td>
                    </tr>
                 </table> 
              </td>
            </tr>
        </table>
    </body>
</html>
A tag <c:set/> permite que você assine um valor para um objeto Y ou uma variável qualquer. Você deve referenciar essa variável no restante da página por meio da Expression Language (EL) ${Y}.

shoppingCart.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<jsp:useBean id="clientService" class="business.ClientService" scope="session"/>
<jsp:useBean id="productService" class="business.ProductService" scope="session"/>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>               
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
                <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
            </tr>           
             <tr>
                <td><jsp:include page="menu.jsp" flush="true" /></td>
                <td>
                    <c:if test="${clientService.shoppingCart.size() != null}" >
                    <table>
                        <tr>
                        <th>Name</th>
                        <th>Description</th>
                        <th>Price</th>
                        <th>Quantity</th>
                        <th>Subtotal</th>                       
                        </tr>
                        <c:forEach items="${clientService.shoppingCart.keySet()}" var="idProduct" >                           
                            <c:set var="product" value="${productService.getProduct(idProduct)}" />
                            <tr>
                                <td>${product.name}</td>
                                <td>${product.description}</td>
                                <td>${product.price}</td>                               
                                <form method="post" action="${applicationScope['controller']}">
                                <td>
                                    <input type="hidden" name="action" value="updateCart" />
                                    <input type="text" name="quantity" size="2"
                                           value="${clientService.shoppingCart.get(product.id)}" />
                                </td>
                                <td>${(product.price * clientService.shoppingCart.get(product.id))}</td>
                                <td>                                   
                                    <input type="submit" value="Update" />
                                    <input type="hidden" name="productId" value="${product.id}" />
                                    <input type="hidden" name="action" value="updateShoppingCart" />                                   
                                </td>
                                </form>
                                <form method="post" action="${applicationScope['controller']}">
                                <td>                                   
                                    <input type="submit" value="Delete" />
                                    <input type="hidden" name="productId" value="${product.id}" />
                                    <input type="hidden" name="action" value="deleteProduct" />                                   
                                </td>
                                </form>
                            </tr>                           
                        </c:forEach>
                    </table>
<a href="${applicationScope['controller']}?action=checkout" >Proceed Checkout</a>
                    </c:if>
                    <c:if test="${clientService.shoppingCart == null}">            
                        Shopping Cart is Empty!            
                    </c:if>
                </td>
             </tr>                       
        </table>       
    </body>
</html>
shoppingCart.jsp representa o carrinho de compras. A medida em que o usuário escolhe os produtos, eles são armazenados na sessão na forma de um map chave/valor representando produto/quantidade respectivamente. Não é uma boa prática utilizar código java puro em paginas JSP, dê prefêrencia para as tagLibs JSTL, como temos feitos até agora com as tags <c:if>, <c:set><c:forEach> etc.

checkout.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<jsp:useBean id="clientService" class="business.ClientService" scope="session"/>
<c:set var="client" value="${clientService.getClient()}" scope="request"  />

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Register</title>
    </head>
    <body>
        <table  align="center">
            <tr>
                <td>
                    <jsp:include page="header.jsp" flush="true" />
                </td>
            </tr>    
        <tr>        
        <td>
            <form method="post" action="${applicationScope['controller']}">
            <input type="hidden" name="action" value="checkout"/>
            <table  style="border: 1px solid #cccccc; width: 450px;">
                <tr>
                    <td>Name:</td>
                    <td colspan="3">${client.fullName}</td>
                </tr>
                <tr>
                    <td>Email</td>
                    <td colspan="3">${client.email}</td>
                </tr>
                <tr>
                    <td align="center" colspan="4"><b>Your Order Details</b></td>                    
                </tr>
                <tr style="font-weight: bold;">
                    <td>Product</td>
                    <td>Price</td>
                    <td>Quantity</td>
                    <td>Total Item</td>
                </tr>
                <c:forEach items="${clientService.shoppingCart.keySet()}" var="idProduct" >                    
                    <c:set var="product" value="${productService.getProduct(idProduct)}" />                    
                    <tr>
                        <td>${product.name}</td>
                        <td>${product.price}</td>
                        <td>${clientService.shoppingCart.get(product.id)}</td>
                        <td>${(product.price * clientService.shoppingCart.get(product.id))}</td>
                    </tr>                  
                </c:forEach>
                <tr>
                    <td colspan="2">Total Order</td>
                    <td colspan="2">${clientService.totalOrder}</td>
                </tr>
                <tr>
                    <td colspan="2">Credit Card Number:</td>
                    <td colspan="2">${client.creditCard}</td>
                </tr>
                <tr>
                    <td colspan="4"><input type="submit" value="Confirm Order"/></td>
                </tr>
            </table>
        </form>
        </td>
        </tr>
        </table>
    </body>
</html>
checkout.jsp exibe um formulário com todos os item que o usário escolheu e o valor total do pedido.

login.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
                <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>=
            </tr>
            <tr>
                <td><jsp:include page="menu.jsp" flush="true" /></td>
                <td> 
                    <table>
                    <form method="post" action="${applicationScope['controller']}">
                        <input type="hidden" name="action" value="login"/>
                        <tr>
                            <th colspan="2">Log in to preceed your Checkout</th>
                        </tr>
                        <tr>
                            <td>Email</td>
                            <td><input type="text" name="email" /></td>
                        </tr>
                        <tr>
                            <td>Login</td>
                            <td><input type="text" name="login" /></td>
                        </tr>
                        <tr>
                            <td><input type="submit" value="Login"/></td> 
                        </tr>
                        <tr>
                            <th colspan="2">
   <a href="${applicationScope['controller']}?action=register">Not Registered Yet?</a>
                            </th>
                        </tr>
                    </form>
                    </table>
                </td>
            </tr>
        </table>
    </body>
</html>
login.jsp pede para que o usuário faça login antes de emitir o pedido, caso ele não seja cadastrado, terá que fazê-lo no formulário de registro.

register.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<jsp:useBean id="clientService" class="business.ClientService" scope="session"/>
<jsp:useBean id="productService" class="business.ProductService" scope="session"/>

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>                
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
                <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
            </tr>
            <tr>
                <td><jsp:include page="menu.jsp" flush="true" /></td>
                <td>                     
            <form action="${applicationScope['controller']}" method="post">
            <input type="hidden" name="action" value="register"/>
            <table>
                <tr>
                    <th colspan="4">Informations Required to Proceed Checkout</th>
                </tr>
                <tr>
                    <td>First Name</td>
                    <td colspan="3"><input type="text" name="fName" required="true" /></td>
                </tr>
                <tr>
                    <td>Last Name</td>
                    <td colspan="3"><input type="text" name="lName" required="true" /></td>
                </tr>
                <tr>
                    <td>Birth Date</td>
       <td colspan="3"><input type="text" name="birthDate" required="true" />dd/mm/yyyy</td>
                </tr>
                <tr>
                    <td>Email</td>
                    <td><input type="text" name="email" required="true" /></td>
                    <td>Login</td>
                    <td><input type="text" name="login" required="true"/></td>
                </tr>
                <tr>
                    <td>Credit Card</td>
                    <td colspan="3"><input type="text" name="creditCard" required="true" /></td>
                </tr>
                <tr>
                    <td colspan="4">
                        <input type="submit" value="Register" />
                    </td>
                </tr>
            </table>
        </form>
                </td>
            </tr>
        </table>        
    </body>
</html>
thankyou.jsp exibe uma mensagem de agradecimento após o usuário terminar o pedido:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<c:set var="order" value="${requestScope.order}" scope="request"  />
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <table style="border: 1px solid #cccccc; width: 1000px">
            <tr>
                <td colspan="2"><jsp:include page="header.jsp" flush="true" /></td>
            </tr>
            <tr>
                <td><jsp:include page="menu.jsp" flush="true" /></td>
                <td> 
                    Thank You for purchasing, ${order.client.firstName}!<br/>
                    Your order ID is ${order.id}, you will receive a confirmation at ${order.client.email}!
                </td>
            </tr>
        </table>
    </body>
</html>
Uma outra estrátégia para enviar o parâmetro action para a ServletController é utilizar campos escondidos com a tag input como por exemplo esta definição na página checkout.jsp
<input type="hidden" name="action" value="checkout"/>

Neste ponto, o projeto não compila porque ela faz referência aos beans de serviço que ainda não foram criados, como a classe ClientService por exemplo. Essas classes serão implementadas na parte 3 do artigo.

Referências
KURNIAWAN, Budi. Java for the Web with Servlets, JSP, and EJB: A Developer's Guide to Scalable J2EE Solutions. 1. ed. Indianapolis: New Riders, 2002. 903 p.
PATZER, Andrew. Foundations of JSP Design Patterns. 1. ed. Berkeley: Apress, 2004. 282 p.

No comments:

Post a Comment