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
pages,
new,
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.