# Projecte d'exemple: Aplicació web amb Spring MVC
## Introducció
Una aplicació web és un programari que funciona a través d'un navegador i es comunica amb un servidor mitjançant el protocol HTTP, seguint un esquema de petició (request) i resposta (response).
En tota aplicació web distingirem:
* Un **front-end**: la part que veu l'usuari i amb la qual hi interacciona mitjançant el navegador. Sol programar-se fent servir HTML, CSS i JavaScript.
* Un **back-end**: la part que s’executa al servidor, gestionant la lògica de l’aplicació, processant les peticions del front-end i accedint a la base de dades quan cal. Pot estar desenvolupat amb tecnologies com Node.js, Java (Spring Boot), Python (Django, Flask) o PHP.

Font: [Evertop: front-end vs back-end](https://www.evertop.pl/en/frontend-vs-backend/)
## Protocol HTTP
És un protocol netament Client/Servidor, sense estat (stateless), on cada petició és independent d'altres i el servidor no emmagatzema informació prèvia sobre aquestes.
Com a mètodes que veurem i que formen part del CRUD habitual tenim:
* GET: utilitzat per obtenir dades des d'un recurs, com poden ser dades des d'una base de dades, contingut d'una pàgina web, imatges, etc.
* POST: utilitzat per passar dades als recursos que les han de procesar. Sovint ho veurem en el context de passar dades que han de ser inserides en una base de dades.
* PUT/PATCH: utilitzar per modificar dades o recursos ja existents. El PUT implica una modificació sencera mentres que el mètode PATCH implica una modificació parcial.
* DELETE: utilitzar per esborrar dades o recursos ja existents.
Quan vulguem executar aplicacions web, sovint hi accedirem des del navegador indicant:
```
http(s)://{nom_maquina/ip_maquina}:port/ruta
```
On nom_maquina/ip_maquina correspon al nom que té el servidor o bé a l'adreça ip que té aquest servidor. Pot fer-se servir tant un com l'altre. El paràmetre port correspon al valor del port on funciona l'aplicació i la ruta (endpoint) correspon a la funcionalitat a la qual volem accedir. Exemples freqüents:
```
https://localhost:8080/ #accedim a l'index/landing page
https://localhost:8080/crearusuari #accedim a la vista/form de crear un nou usuari
https://localhost:8080/llistarusuaris #accedim a la vista/form de llistar tots els usuaris
```
Per últim, tant a la petició com a la resposta distingirem les capçaleres (head) com el cos (body).
Per posar un exemple, la capçalera de la request indica el mètode (en aquest cas GET), particularitats de l'explorador de la màquina client, etc.

I per posar un altre exemple visible tenim el cos de la resposta (body) el qual se'ns mostra en un format ja conegut:

Mereix parar l'atenció en els codis d'status:
* 1xx (De caire informatiu)
* 2xx (Resposta amb èxit: 200, 204, etc.).
* 3xx (Redirecció).
* 4xx (Error de client, un dels més conegut és el 404).
* 5xx (Error de servidor, per exemple el 500 - internal server error).
El mateix inspector de l'explorador ens pot mostrar quin és el codi d'status:

## Spring MVC i webflux
L'exemple que es detallarà a continuació segueix el paradigma de Model-Vista-Controlador **MVC** el qual té com a característiques principals:
1) Que tracta de forma **síncrona** les peticions (cada petició és un fil i fins que no la finalitza no allibera el fil).
2) Que segueix l'especificació servlet (conjunt de regles a seguir per manejar peticions HTTP amb aplicacions desenvolupades en Java). La implementació que utilitzarem i veurem és Apache Tomcat.
3) El model de programació és imperatiu.
Fins Spring 4.0 aquest ha estat el paradigma ofert, a partir d'aquesta versió ja podem trobar **webflux**, el qual té com a característiques la reactivitat i l'asincronicitat de les peticions. Això fa que sigui el més idoni per aplicacions amb alta demanda on no hi ha d'haver bloquejos i temps de resposta raonables.
## Projecte d'exemple amb Spring MVC. Passos
El primer que farem és anar a l'[spring initialzr](https://start.spring.io/index.html) per obtenir l'esquelet del nostre projecte.
Podeu posar el group i l'artifact id que considereu més convenients.
Les dependències que de moment necessitarem són aquestes (ja ampliarem més endavant):

En concret la primera ens proporcionarà l'apache tomcat (entre altres coses) i la segona (thymeleaf) el motor de plantilles que integrat a l'html donarà prou funcionalitat per poder fer que l'aplicació funcioni de forma adequada.
Un cop ja haguem obert el projecte, cal que sobre l'arrel creem la següent estructura de carpetes:

On fixeu-vos que agruparem els components segons l'estereotip que els correspongui. No és obligatori fer-ho però és una bona pràctica.
### Model i repositori
En aquest cas el nostre model té dues classes, una per l'usuari que es connecta i una altra pels llibres, les quals codificarem així:
```
public class Usuaris {
// private int idUsuari;
// private String nomUsuari;
private String usuari;
private String password;
public String getUsuari() {
return usuari;
}
public void setUsuari(String usuari) {
this.usuari = usuari;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Usuaris(String usuari, String password) {
this.usuari = usuari;
this.password = password;
}
public Usuaris() {}
}
```
```
public class Llibre {
private int idLlibre;
private String titol;
private String autor;
private String editorial;
private String datapublicacio;
private String tematica;
public Llibre() {}
public Llibre(int idLlibre, String titol, String autor, String editorial, String datapublicacio, String tematica) {
this.idLlibre = idLlibre;
this.titol = titol;
this.autor = autor;
this.editorial = editorial;
this.datapublicacio = datapublicacio;
this.tematica = tematica;
}
public int getIdLlibre() {
return idLlibre;
}
public void setIdLlibre(int idLlibre) {
this.idLlibre = idLlibre;
}
public String getTitol() {
return titol;
}
public void setTitol(String titol) {
this.titol = titol;
}
public String getAutor() {
return autor;
}
public void setAutor(String autor) {
this.autor = autor;
}
public String getEditorial() {
return editorial;
}
public void setEditorial(String editorial) {
this.editorial = editorial;
}
public String getDatapublicacio() {
return datapublicacio;
}
public void setDatapublicacio(String datapublicacio) {
this.datapublicacio = datapublicacio;
}
public String getTematica() {
return tematica;
}
public void setTematica(String tematica) {
this.tematica = tematica;
}
}
```
En aquest exemple el repositori és cassolà i no és persistent enlloc, més endavant veurem com fer un repositori que permeti accedir a un mecanisme persistent (és a dir, una base de dades):
```
@Repository
public class RepoLlibre {
ArrayList<Llibre> llibres = new ArrayList<Llibre>();
public RepoLlibre() {
llibres.add(new Llibre(1,"HARRY POTTER I EL PRESONER D'AZKABAN","JK Rowling","Salamandra","26/9/2006","fantastica"));
llibres.add(new Llibre(2,"CODI DA VINCI","Dan Brown","Ariel","26/9/2006","ficcio"));
}
public ArrayList<Llibre> getAllLlibres() {
return llibres;
}
public void InsertaLlibre(Llibre llibre) {
llibres.add(llibre);
}
public Llibre getLlibreID(int id) {
Llibre llibre = null;
//Opció clàssica, imperativa
for (Llibre l1:llibres) {
if (l1.getIdLlibre() == id) {
llibre = l1;
}
}
return llibre;
}
}
```
### Les vistes i els controladors
Els controladors dins una aplicació MVC són una peça fonamental, ja que són els que posen d'acord les vistes amb els serveis i altres components d'accés a dades. Sense ànim de ser exhaustius, les principals responsabilitats dels controladors inclouen:
* Interceptació de sol·licituds entrants (des de les vistes).
* Convertir el *payload* de la sol·licitud a l'estructura interna de les dades.
* Enviament de les dades al Model per al seu posterior tractament.
* Obtenir dades processades del model per passar-les a les vistes i que aquestes les renderitzin.
Quan vegem les API REST veurem que també s'utilitzen controladors els quals tenen algunes diferències en quant a missió i entrada i sortida.
Anotacions clau:
* `@Controller` per indicar que aquell component es comportarà com a controlador. Els mètodes que apareixeran dins un controlador estaran relacionats amb les vistes que hi puguin pertànyer a l'àmbit d'una mateixa classe.
* `@GetMapping(/nom_vista_endpoint)` sobre un mètode indicarà que les accions d'aquell mètode que contingui aquesta anotació (aixecar una vista, cridar a un mètode d'un servei, etc.) es faran quan des d'una vista o endpoint s'hagi fet un GET.
* De forma semblant tindrem `@PostMapping(/nom_vista_endpoint)`. Quan aquesta anotació aparegui en un mètode vol dir que les accions d'aquest mètode es faran quan des d'una vista s'hagi fet un POST.
Altres anotacions:
* `@SessionAttributes` s'utilitza per mantenir atributs a la sessió HTTP per totes les peticions que es facin sobre el mateix controlador (sigui quina sigui la vista que utilitza aquest controlador). A l'exemple veureu que els usuaris que accedeixen dins l'aplicació són atributs de la sessió, per tal que pugui funcionar emprem també l'anotació `@ModelAttribute`, la qual farà que s'inicialitzi l'objecte (en aquest cas usuaris) si no existeix la sessió.
* `@RequestParam` serveix per agafar els paràmetres que es puguin passar des d'una vista i que després podran ser utilitzats dins un mètode.
En aquest cas tenim el controlador BookController, l'anirem detallant pas a pas. En aquest punt el GetMapping amb "/" indica que quan es faci un "http://localhost:8080/" al navegador s'aixequi la vista "login.html"
```
@Controller
@SessionAttributes("users")
public class BookController {
@Autowired
RepoLlibre repoll = new RepoLlibre();
@GetMapping("/")
public String iniciar(Model model) {
return "login";
}
```
Dins login.html tenim aquest codi, un petit formulari on es demana usuari i password i un botó que llença un POST (fixeu-vos method="post"):
```
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accés a l'aplicació</title>
</head>
<body>
<center>
<H1>Llibreria del poble</H1>
<form action="/index" method="post">
Login: <input type="text" name="usuari"><br><br>
Password: <input type="text" name="password"><br><br>
<input type=submit name="submit" value="Accedir">
</form>
</center>
</body>
</html>
```
Aquest POST el que farà es anar a cercar al controlador un mètode que tingui `@PostMapping("/index")` ja que és el que hem indicat al form action. El codi que tindrà serà el següent i que afegirem al BookController anterior:
```
@PostMapping("/index")
public String login(@ModelAttribute("users") Usuaris users, Model model) {
model.addAttribute("users", users);
if (users.getUsuari().equals("toni")
&& users.getPassword().equals("h3ll0!!")) {
return "index";
} else {
return "login";
}
}
```
Això el que fa es validar que l'usuari i password siguin uns determinats, sinó retornarà a la vista login. La vista index.htlm serà un index amb un sèrie de referències a altres enllaços:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<body>
<h3>Usuari connectat: <span th:text="${users.usuari}"></span></h3>
<center>
<H1>Llista d'opcions</H1>
<p><a href="consulta">Consulta la llista de llibres</a></p>
<p><a href="inserir">Insereix un nou llibre</a></p>
<p><a href="cercaid">Cerca un llibre per id</a></p>
<form action="/logout" method="post">
<input type=submit name="submit" value="Sortir">
</form>
</center>
</body>
</html>
```
Cadascun d'aquests enllaços el que fan es cridar a mètodes GetMapping que hauran d'afegir-se al controlador:
```
@GetMapping("/index")
public String index(@ModelAttribute("users") Usuaris users, Model model) {
return "index";
}
@GetMapping("/consulta")
public String consulta(@ModelAttribute("users") Usuaris users,Model model) {
ArrayList<Llibre> llibres = repoll.getAllLlibres();
model.addAttribute("llibres", llibres);
return "consulta";
}
@GetMapping("/inserir")
public String inputInserir(@ModelAttribute("users") Usuaris users,Model model) {
return "inserir";
}
@GetMapping("/cercaid")
public String inputCerca(@ModelAttribute("users") Usuaris users, Model model) {
Llibre llibre = new Llibre();
llibre.setIdLlibre(0);
model.addAttribute("llibreErr", true);
model.addAttribute("message", "");
model.addAttribute("llibre", llibre);
return "cercaid";
}
```
Fixeu-vos que després d'afegir atributs o cridar a un repositori (després veurem el codi) el que acaben fent és cridant a una sèrie de vistes.
Comencem per la primera. El `@GetMapping("/index")` té la funció de retornar cap a la pàgina d'index. Veureu que es crida des dels href que hi trobareu al final de les vistes inserir.html, consulta.html i cercaid.html:
```
<p><a href="/index">Tornar enrere</a></p>
```
Tot seguit, el `@GetMapping("/consulta")` el que fa es omplir un arrayList de llibres cridant el mètode getAllLlibres() del repositori. Un cop el té omplert el mapejarà cap a la vista consulta.html fent el model.addAttribute("llibres", llibres). Fixeu-vos que a la vista s'omple una taula a partir d'iterar la llista de llibres enviada des del controlador:
```
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Llistat de llibres</title>
</head>
<body>
<h3>Usuari connectat: <span th:text="${users.usuari}"></span></h3>
<center>
<H1>Llista de llibres</H1>
<table border=1>
<thead>
<tr>
<th>IdLlibre</th>
<th>Títol</th>
<th>Autor</th>
<th>Editorial</th>
<th>Data d'edició</th>
<th>Temàtica</th>
</tr>
</thead>
<tbody>
<tr th:if="${llibres.empty}" >
<td colspan="2">No hi ha llibres disponibles</td>
</tr>
<tr th:each="llibre:${llibres}">
<td><span th:text="${llibre.idLlibre}"></span></td>
<td><span th:text="${llibre.titol}"></span></td>
<td><span th:text="${llibre.autor}"></span></td>
<td><span th:text="${llibre.editorial}"></span></td>
<td><span th:text="${llibre.datapublicacio}"></span></td>
<td><span th:text="${llibre.tematica}"></span></td>
</tr>
</tbody>
</table>
<p><a href="/index">Tornar enrere</a></p>
</center>
</body>
</html>
```
Pel que fa a `@GetMapping("/inserir")`, s'aixeca la vista inserir.html, la qual serà el punt d'entrada per poder inserir un nou llibre:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inserir nou llibre</title>
</head>
<body>
<center>
<H1>Inserció de nou llibre</H1>
<form action="/inserir" method="post">
Id del Llibre: <input type="text" name="idLlibre"><br><br>
Titol: <input type="text" name="titol"><br><br>
Autor: <input type="text" name="autor"><br><br>
Editorial: <input type="text" name="editorial"><br><br>
Data de publicacio: <input type="text" name="datapublicacio"><br><br>
Tematica: <input type="text" name="tematica"><br><br>
<input type=submit name="submit" value="Desar">
</form>
<p><a href="/index">Tornar enrere</a></p>
</center>
</body>
</html>
```
Quan es fa el post es crida al corresponent mètode del controlador que afegirem tot seguit:
```
@PostMapping("/inserir")
public String inserir(@ModelAttribute("users") Usuaris users,
@RequestParam(name = "idLlibre") String idLlibre,
@RequestParam(name = "titol") String titol,
@RequestParam(name = "autor") String autor,
@RequestParam(name = "editorial") String editorial,
@RequestParam(name = "datapublicacio") String datapublicacio,
@RequestParam(name = "tematica") String tematica,
Model model) {
String message = "";
boolean llibreErr = false;
if (idLlibre == null || !idLlibre.matches("\\d+")) {
message = "La id de llibre ha de ser un nombre enter";
llibreErr = true;
model.addAttribute("message", message);
model.addAttribute("llibreErr", llibreErr);
return "inserir";
} else {
int idL = Integer.parseInt(idLlibre);
Llibre llibre = new Llibre(idL,titol,autor,editorial,datapublicacio,tematica);
repoll.InsertaLlibre(llibre);
ArrayList<Llibre> llibres = repoll.getAllLlibres();
model.addAttribute("llibres", llibres);
return "consulta";
}
```
El que fa es això es retornar la vista consulta.html, la qual ens mostra la llista de llibres emmagatzemats de forma tabular. Aquesta vista també es cridava des del mètode consulta (`@GetMapping("/consulta") `) que hem afegit abans. D'aquesta forma es refresca la llista de llibres amb el darrer afegit.
La vista cercaid.html té el següent codi:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<center>
<h1>Cerca de llibre per id</h1>
<form action="/cercaid" method="post">
Id del Llibre: <input type="text" name="idLlibre"><br><br>
<input type=submit name="submit" value="Cerca">
</form>
<br></br>
<div th:if="${llibreErr}">
<p th:text="${message}"></td>
</div>
<table th:if="${!llibreErr}" border=1>
<thead>
<tr>
<th>IdLlibre</th>
<th>Títol</th>
<th>Autor</th>
<th>Editorial</th>
<th>Data d'edició</th>
<th>Temàtica</th>
</tr>
</thead>
<tbody>
<td><span th:text="${llibre.idLlibre}"></span></td>
<td><span th:text="${llibre.titol}"></span></td>
<td><span th:text="${llibre.autor}"></span></td>
<td><span th:text="${llibre.editorial}"></span></td>
<td><span th:text="${llibre.datapublicacio}"></span></td>
<td><span th:text="${llibre.tematica}"></span></td>
</tbody>
</table>
<p><a href="/index">Tornar enrere</a></p>
</center>
</body>
</html>
```
El que fa quan s'aixeca és mostrar un camp on afegir la id del llibre i un botó de cerca. Si el troba el mostrarà de forma tabular i sinó oferirà un missatge d'error. El botó de cerca el que fa es cridar el següent PostMapping que afegirem al controlador:
```
@PostMapping("/cercaid")
public String cercaId(@ModelAttribute("users") Usuaris users,
@RequestParam(name = "idLlibre", required = false) String idLlibre,
Model model) {
int idLlib = 0;
String message = "";
boolean llibreErr = false;
try {
idLlib = Integer.parseInt(idLlibre);
Llibre llibre = repoll.getLlibreID(idLlib);
if(llibre !=null) {
model.addAttribute("llibre", llibre);
} else {
message = "No hi ha cap llibre amb aquesta id";
llibreErr = true;
}
} catch (Exception e) {
message = "La id de llibre ha de ser un nombre enter";
llibreErr = true;
}
model.addAttribute("message", message);
model.addAttribute("llibreErr",llibreErr);
return "cercaid";
}
```
Per últim hi afegirem al controlador el post corresponent a quan es vulgui fer un logout de l'aplicació i el ModelAttribute per inicialitzar els atributs en una mateixa sessió:
```
@PostMapping("/logout")
public String logout(SessionStatus status) {
status.setComplete();
return "redirect:/";
}
@ModelAttribute("users")
public Usuaris getDefaultUser() {
return new Usuaris();
}
```
Amb tot això ja podreu arrencar l'aplicació i accedir-hi des del navegador fent http://localhost:8080/