Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
1 result

Target

Select target project
  • noe.delcroix.etu/m4102_tp3
  • aymane.ismail.etu/m4102_tp3
  • baptiste.hardelin.etu/m4102_tp3
  • harold.debuyst.etu/m4102_tp3
  • paul.daquin.etu/m4102_tp3
  • aurelien.brillet.etu/m4102_tp3
  • thomas.vantroys/m4102_tp3
  • maxime.molines.etu/m4102_tp3
  • jessy.fanguimache.etu/m4102_tp3
  • tom.varingot.etu/m4102_tp3
  • thomas.tourdot.etu/m4102_tp3
  • remy.dekeister.etu/m4102_tp3
  • alban.sannier.etu/m4102_tp3
  • enzo.cocchi.etu/m4102_tp3
  • simon.prevost2.etu/m4102_tp3
  • valentin.bout.etu/m4102_tp3
  • antoine.delaby.etu/m4102_tp3
  • rayd.tarafi.etu/m4102_tp3
  • hugo.dutoit.etu/m4102_tp3
  • antoine.delfosse.etu/m4102_tp3
  • maxime.magnier.etu/m4102_tp3
  • felicien.delannoy.etu/m4102_tp3
  • colin.hocquet.etu/m4102_tp3
  • martin.mille.etu/m4102_tp3
  • sacha.lepage.etu/m4102_tp3
  • tom.delahaye.etu/m4102_tp3
  • amine.allouch.etu/m4102_tp3
  • tanguy.bonnet.etu/m4102_tp3
  • romain.saintmaxent.etu/m4102_tp3
  • david.battais.etu/m4102_tp3
  • matthieu.vannin.etu/m4102_tp3
  • martin.thibaut.etu/m4102_tp3
  • maxime.wallart.etu/m4102_tp3
  • adil.benameur.etu/m4102_tp3
  • trophime.dussart.etu/m4102_tp3
  • mohamed.bourdim.etu/m4102_tp3
  • aubrian.duhayon.etu/m4102_tp3
  • cody.dumortier.etu/m4102_tp3
  • matheo.gallego.etu/m4102_tp3
  • quentin.gillot.etu/m4102_tp3
  • mathis.senicourt.etu/m4102_tp3
  • bastien.cortequisse.etu/m4102_tp3
  • baptiste.lantoine2.etu/m4102_tp3
  • aurelien.carray.etu/m4102_tp3
  • nathan.devigne.etu/m4102_tp3
  • tanguy.carrette.etu/m4102_tp3
  • ayoub.lahouaichri.etu/m4102_tp3
  • maxime.maronet.etu/m4102_tp3
  • bastien.tjampens.etu/m4102_tp3
  • quentin.dubois3.etu/m4102_tp3
  • lucas.ple.etu/m4102_tp3
  • martin.dorange.etu/m4102_tp3
  • thomas.wacquet.etu/m4102_tp3
  • corentin.lebleu.etu/m4102_tp3
  • clotaire.dufresne.etu/m4102_tp3
  • antoine.maille.etu/m4102_tp3
  • thomas.obry.etu/m4102_tp3
  • lucas.hottin.etu/m4102_tp3
  • adrien.lazaro.etu/m4102_tp3
  • lohan.calot.etu/m4102_tp3
  • paul.ripault.etu/m4102_tp3
  • romain.gabet.etu/m4102_tp3
  • julien.desmons2.etu/m4102_tp3
  • alexis.duhem2.etu/m4102_tp3
  • theo.noiriel.etu/m4102_tp3
  • gauthier.solignac.etu/m4102_tp3
  • maxime.boutry.etu/m4102_tp3
  • theo.lange.etu/m4102_tp3
  • ali.ketencimen.etu/m4102_tp3
  • leonard.corre.etu/m4102_tp3
  • simon.saintmichel.etu/m4102_tp3
  • charles.tronel.etu/m4102_tp3
  • william.jacquot.etu/m4102_tp3
  • benoit.lefebvre.etu/m4102_tp3
  • baptiste.carion.etu/m4102_tp3
  • theo.petit.etu/m4102_tp3
  • guillaume.dubois.etu/m4102_tp3
  • florentin.plaisant.etu/m4102_tp3
  • emeric.thebaud.etu/m4102_tp3
  • bastien.moncheaux.etu/m4102_tp3
  • guilhem.heugue.etu/m4102_tp3
  • pierre.delgrange.etu/m4102_tp3
  • hugo.blanquart.etu/m4102_tp3
  • theo.canonne.etu/m4102_tp3
  • benjamin.tellier.etu/m4102_tp3
  • thomas.shipman.etu/m4102_tp3
  • remi.laine.etu/m4102_tp3
  • omar.diallo.etu/m4102_tp3
  • jacques.wagret.etu/m4102_tp3
  • victoire.lenglart.etu/m4102_tp3
  • lucas.constant.etu/m4102_tp3
  • sylvain.bretin.etu/m4102_tp3
  • quentin.verchere.etu/m4102_tp3
  • jade.bellereau.etu/m4102_tp3
  • martin.birlouez.etu/m4102_tp3
  • alexis.vandersteene.etu/m4102_tp3
  • adrien.bassail.etu/m4102_tp3
  • vincent.dheilly.etu/m4102_tp3
  • nathanael.pereira.etu/m4102_tp3
  • julien.delevallez.etu/m4102_tp3
  • loic.bombard.etu/m4102_tp3
  • maxence.bourdin2.etu/m4102_tp3
  • matthias.merlin.etu/m4102_tp3
  • kevin.froissart.etu/m4102_tp3
  • allan.boukhebza.etu/m4102_tp3
  • hugo.wieder.etu/m4102_tp3
  • maxence.becquet.etu/m4102_tp3
  • lucas.derouck.etu/m4102_tp3
  • guillaume.roelandt.etu/m4102_tp3
  • maxime.cassoret.etu/m4102_tp3
  • robin.hotton.etu/m4102_tp3
  • clement.bisson.etu/m4102_tp3
  • florian.dieryckx.etu/m4102_tp3
  • aurelien.gozet.etu/m4102_tp3
  • coline.guyard.etu/m4102_tp3
  • florian.chiraux.etu/m4102_tp3
  • benoit.barbier.etu/m4102_tp3
  • benoit.bankaert.etu/m4102_tp3
  • aymeric.defossez.etu/m4102_tp3
  • geoffrey.vaniscotte.etu/m4102_tp3
  • yacine.messaadi.etu/m4102_tp3
  • alexandre.bouchez.etu/m4102_tp3
  • quentin.dinel.etu/m4102_tp3
  • madeline.carpentier.etu/m4102_tp3
  • neo.lefranc.etu/m4102_tp3
  • ryan.rossez.etu/m4102_tp3
  • lucas.tavernier.etu/m4102_tp3
  • robin.gallifa.etu/m4102_tp3
  • laurent.strachowski.etu/m4102_tp3
  • xavier.lezzoche.etu/m4102_tp3
  • gautier.lantoine.etu/m4102_tp3
  • yohan.tryoen.etu/m4102_tp3
  • julien.michot.etu/m4102_tp3
  • antoine.pochet.etu/m4102_tp3
  • yanis.sadi.etu/m4102_tp3
  • gireg.lezoraine.etu/m4102_tp3
  • louis.bellefemine.etu/m4102_tp3
  • tom.danion.etu/m4102_tp3
  • carmelo.dellaterra.etu/m4102_tp3
  • ophelie.kaminski.etu/m4102_tp3
  • vincent.sailliot.etu/m4102_tp3
  • hugo.briatte.etu/m4102_tp3
  • quentin.prognon.etu/m4102_tp3
  • clement.dessingue.etu/m4102_tp3
  • hugo.derigny.etu/m4102_tp3
  • hugo.dautricourt.etu/m4102_tp3
  • yvan.peter/m4102_tp3
147 results
Select Git revision
  • master
1 result
Show changes
Commits on Source (18)
Showing
with 955 additions and 680 deletions
......@@ -206,5 +206,7 @@ local.properties
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.project
.settings
.classpath
# End of https://www.gitignore.io/api/eclipse,visualstudiocode
# Développement REST avec Jersey
## Mise en place de l'environnement
Ce tutoriel utilise [Apache Maven](http://maven.apache.org/) pour
l'automatisation des tâches de développement (compilation, tests,
déploiement...).
Si votre machine se trouve derrière un
proxy, vous devrez mettre la configuration suivante dans le fichier
`~/.m2/settings.xml` (à adapter à votre environnement) :
<settings>
<proxies>
<proxy>
<id>lille1-proxy</id>
<active>true</active>
<protocol>http</protocol>
<host>cache.univ-lille.fr</host>
<port>3128</port>
</proxy>
<proxy>
<id>lille1-proxy-sec</id>
<active>true</active>
<protocol>https</protocol>
<host>cache.univ-lille.fr</host>
<port>3128</port>
</proxy>
</proxies>
</settings>
En local, vous pouvez également recopier le fichier `/home/public/peter/maven/settings.xml`.
# Projet REST avec Jersey
## Récupération du projet initial
Pour récupérer le projet vous pouvez utiliser la commande `git clone
......@@ -43,8 +13,11 @@ La classe `ApiV1` sera le point d'entrée de notre application REST qui
permet de configurer le chemin de l'URI (`@ApplicationPath`) ainsi que
les paquetages Java qui contiennent les ressources.
~~~
.
├── architecture.svg
├── pom.xml
├── README.md
└── src
├── main
│ ├── java
......@@ -56,13 +29,17 @@ les paquetages Java qui contiennent les ressources.
│ │ ├── BDDFactory.java
│ │ ├── beans
│ │ ├── dao
│ │ │ ├── UUIDArgumentFactory.java
│ │ │ └── UUIDArgument.java
│ │ ├── dto
│ │ │ └── IngredientDto.java
│ │ ├── Main.java
│ │ └── resources
│ ├── resources
│ │ ── ingredients.json
│ └── logback.xml
│ └── webapp
│ └── WEB-INF
│ │ ├── BDDClearRessource.java
│ │ ── IngredientResource.java
│ └── resources
│ ├── ingredients.json
│ └── logging.properties
└── test
├── java
│ └── fr
......@@ -71,7 +48,8 @@ les paquetages Java qui contiennent les ressources.
│ └── pizzaland
│ └── IngredientResourceTest.java
└── resources
└── logback-test.xml
└── logging.properties
~~~
## Développement d'une ressource *ingredients*
......@@ -79,31 +57,26 @@ les paquetages Java qui contiennent les ressources.
Nous pouvons tout d'abord réfléchir à l'API REST que nous allons offrir pour la ressource *ingredients*. Celle-ci devrait répondre aux URI suivantes :
| Opération | URI | Action réalisée | Retour |
|:----------|:------------|:----------------------------------------------|:----------------------------------------------|
| GET | /ingredients | récupère l'ensemble des ingrédients | 200 et un tableau d'ingrédients |
| GET | /ingredients/{id} | récupère l'ingrédient d'identifiant id | 200 et l'ingrédient |
| | | | 404 si id est inconnu |
| GET | /ingredients/{id}/name | récupère le nom de l'ingrédient | 200 et le nom de l'ingrédient |
| | | d'identifiant id | 404 si id est inconnu |
| POST | /ingredients | création d'un ingrédient | 201 et l'URI de la ressource créée + représentation |
| | | | 400 si les informations ne sont pas correctes |
| | | | 409 si l'ingrédient existe déjà (même nom) |
| DELETE | /ingredients/{id} | destruction de l'ingrédient d'identifiant id | 204 si l'opération à réussi |
| | | | 404 si id est inconnu |
| URI | Opération | MIME | Requête | Réponse |
| :----------------------- | :---------- | :--------------------------------------------- | :-- | :---------------------------------------------------- |
| /ingredients | GET | <-application/json<br><-application/xml | | liste des ingrédients (I2) |
| /ingredients/{id} | GET | <-application/json<br><-application/xml | | un ingrédient (I2) ou 404 |
| /ingredients/{id}/name | GET | <-text/plain | | le nom de l'ingrédient ou 404 |
| /ingredients | POST | <-/->application/json<br>->application/x-www-form-urlencoded | Ingrédient (I1) | Nouvel ingrédient (I2)<br>409 si l'ingrédient existe déjà (même nom) |
| /ingredients/{id} | DELETE | | | |
Un ingrédient comporte uniquement un identifiant et un nom. Sa
représentation JSON prendra donc la forme suivante :
représentation JSON (I2) prendra donc la forme suivante :
{
"id": 1,
"id": "f38806a8-7c85-49ef-980c-149dcd81d306",
"name": "mozzarella"
}
Lors de la création, l'identifiant n'est pas connu car il sera fourni
par la base de données. Aussi on aura une
représentation JSON qui comporte uniquement le nom :
par le JavaBean qui représente un ingrédient. Aussi on aura une
représentation JSON (I1) qui comporte uniquement le nom :
{ "name": "mozzarella" }
......@@ -122,7 +95,7 @@ objets doivent respecter un ensemble de conventions :
- la classe est sérialisable
- elle fournit au moins un constructeur vide
- les attributs privés de la classe sont manipulables via des
méthodes publiques **get**_Attribut_ et **set**_Attribut
méthodes publiques **get**_Attribut_ et **set**_Attribut_
Les DTO et la classe `Ingredient`décrits dans la suite sont des
JavaBeans.
......@@ -169,6 +142,7 @@ Le code suivant qui se trouve dans la classe `IngredientResourceTest`
montre la mise en place de l'environnement (`configure()`) et l'amorce
d'un premier test.
~~~java
public class IngredientResourceTest extends JerseyTest {
@Override
......@@ -189,10 +163,11 @@ d'un premier test.
assertEquals(0, ingredients.size());
}
~~~
En héritant de JerseyTest, votre classe de test se comporte comme un
[`Client`
JAX-RS](https://docs.oracle.com/javaee/7/api/javax/ws/rs/client/Client.html). La
JAX-RS](https://jakarta.ee/specifications/platform/9/apidocs/jakarta/ws/rs/client/client). La
méthode `target()` notamment permet de préparer une requête sur une
URI particulière.
......@@ -207,24 +182,15 @@ DTO `IngredientDto`.
Pour commencer, on se contentera de l'implémentation minimale
suivante :
~~~java
package fr.ulille.iut.pizzaland.dto;
public class IngredientDto {
public IngredientDto() {
}
}
A ce stade, vous pouvez lancer un premier test au moyen de la commande
`mvn test`. Évidemment, ce test va échouer.
$ mvn test
Results :
Failed tests: testGetEmptyList(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<200> but was:<404>
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
~~~
Pour réussir, ce premier test, nous allons mettre en place la
ressource `IngredientResource`.
......@@ -232,6 +198,7 @@ ressource `IngredientResource`.
Une première implémentation de la ressource pourra avoir la forme
suivante :
~~~java
@Path("/ingredients")
public class IngredientResource {
......@@ -247,55 +214,64 @@ suivante :
return new ArrayList<IngredientDto>();
}
}
~~~
Avec cette première implémentation, on va pouvoir tester notre
ressource :
~~~
$ mvn test
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
~~~
### Récupérer un ingrédient existant
Nous allons continuer en ajoutant la possibilité de récupérer un
ingrédient particulier à partir de son identifiant.
Pour cela voici un premier test qui permettra de vérifier cela :
~~~java
import fr.ulille.iut.pizzaland.beans.Ingredient;
@Test
public void testGetExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setId(1);
ingredient.setName("mozzarella");
ingredient.setName("Chorizo");
Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get();
Response response = target("/ingredients/1").request().get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class));
assertEquals(ingredient, result);
}
}
~~~
Vous pourrez vérifier que le test échoue au moyen de la commande `mvn test`
Afin de réussir ce test, nous devons compléter la classe IngredientDto
avec les getter/setter correspondant aux propriétés de l'object JSON.
~~~java
public class IngredientDto {
private long id;
private UUID id;
private String name;
public IngredientDto() {}
public long getId() {
return id;
public IngredientDto() {
}
public void setId(long id) {
public void setId(UUID id) {
this.id = id;
}
public UUID getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
......@@ -303,48 +279,57 @@ avec les getter/setter correspondant aux propriétés de l'object JSON.
public String getName() {
return name;
}
}
~~~
Du côté de la ressource, on peut fournir une première implémentation :
import javax.ws.rs.PathParam;
~~~java
import jakarta.ws.rs.PathParam;
import fr.ulille.iut.pizzaland.beans.Ingredient;
@GET
@Path("{id}")
public IngredientDto getOneIngredient(@PathParam("id") long id) {
public IngredientDto getOneIngredient(@PathParam("id") UUID id) {
Ingredient ingredient = new Ingredient();
ingredient.setId(1);
ingredient.setName("mozzarella");
ingredient.setId(id); // juste pour avoir le même id pour le test
ingredient.setName("Chorizo");
return Ingredient.toDto(ingredient);
}
~~~
Pour cette méthode, nous avons introduit la classe `Ingredient`. Ce
JavaBean représente un ingrédient manipulé par la ressource.
Voici une implémentation pour cette classe :
~~~java
package fr.ulille.iut.pizzaland.beans;
import java.util.UUID;
import fr.ulille.iut.pizzaland.dto.IngredientDto;
public class Ingredient {
private long id;
private UUID id = UUID.randomUUID();
private String name;
public Ingredient() {
}
public Ingredient(long id, String name) {
public Ingredient(String name) {
this.name = name;
}
public Ingredient(UUID id, String name) {
this.id = id;
this.name = name;
}
public void setId(long id) {
public void setId(UUID id) {
this.id = id;
}
public long getId() {
public UUID getId() {
return id;
}
......@@ -372,34 +357,22 @@ Voici une implémentation pour cette classe :
return ingredient;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Ingredient other = (Ingredient) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Ingredient [id=" + id + ", name=" + name + "]";
}
}
~~~
Les méthodes `toDto()` et `fromDto()` permettrons de faire la conversion entre le Bean `Ingredient` et le DTO qui représente ce qui sera transféré dans la requête/réponse HTTP.
Pour les différents beans que nous allons écrire générez également les méthodes `hashCode()` et `equals()` et `toString()`.
Le test devrait maintenant réussir :
~~~
$ mvn test
~~~
## Introduction de la persistence
Pour aller plus loin et mettre en place la création des ingrédients il
......@@ -409,12 +382,15 @@ base de données.
Pour cela nous allons devoir implémenter le DAO (Data Access Object) `IngredientDao` :
~~~java
package fr.ulille.iut.pizzaland.dao;
import java.util.List;
import java.util.UUID;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
......@@ -422,15 +398,21 @@ Pour cela nous allons devoir implémenter le DAO (Data Access Object) `Ingredien
public interface IngredientDao {
@SqlUpdate("CREATE TABLE IF NOT EXISTS ingredients (id INTEGER PRIMARY KEY, name VARCHAR UNIQUE NOT NULL)")
@SqlUpdate("CREATE TABLE IF NOT EXISTS ingredients (id VARCHAR(128) PRIMARY KEY, name VARCHAR UNIQUE NOT NULL)")
void createTable();
@SqlUpdate("DROP TABLE IF EXISTS ingredients")
void dropTable();
@SqlUpdate("INSERT INTO ingredients (name) VALUES (:name)")
@GetGeneratedKeys
long insert(String name);
@SqlUpdate("INSERT INTO ingredients (id, name) VALUES (:id, :name)")
void insert(@BindBean Ingredient ingredient);
@SqlUpdate("DELETE FROM ingredients WHERE id = :id")
void remove(@Bind("id") UUID id);
@SqlQuery("SELECT * FROM ingredients WHERE name = :name")
@RegisterBeanMapper(Ingredient.class)
Ingredient findByName(@Bind("name") String name);
@SqlQuery("SELECT * FROM ingredients")
@RegisterBeanMapper(Ingredient.class)
......@@ -438,16 +420,16 @@ Pour cela nous allons devoir implémenter le DAO (Data Access Object) `Ingredien
@SqlQuery("SELECT * FROM ingredients WHERE id = :id")
@RegisterBeanMapper(Ingredient.class)
Ingredient findById(long id);
Ingredient findById(@Bind("id") UUID id);
}
~~~
JDBI fonctionne par annotations :
- Les annotations `sqlUpdate` et `SqlQuery` correspondent à des
requêtes SQL en modification ou non.
- `@GetGeneratedKeys` permet de renvoyer la clé primaire générée par
la base de données.
- `@RegisterBeanMapper` permet d'associer une classe à un résultat
(les champs de la table sont associés aux propriétés du bean).
- `@Bind` permet d'associer un paramètre de méthode à un paramètre nommé dans la requête SQL.
Reprenons maintenant le code déjà écrit pour aller chercher les
ingrédients dans une base de données (nous utiliserons `Sqlite`).
......@@ -457,9 +439,10 @@ Nous allons utiliser le DAO pour insérer des données dans la table
afin de réaliser nos tests. Nous utiliserons une base de données de
tests qui est définie via la classe `BDDFactory`.
Les méthodes `setEnvUp` et `tearEnvDown` permettent de créer et
Dans la classe `IngredientResourceTest`Les méthodes `setEnvUp` et `tearEnvDown` permettent de créer et
détruire la base de données entre chaque test.
~~~java
import fr.ulille.iut.pizzaland.dao.IngredientDao;
public class IngredientResourceTest extends JerseyTest {
......@@ -485,33 +468,32 @@ détruire la base de données entre chaque test.
@Test
public void testGetExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
long id = dao.insert(ingredient.getName());
ingredient.setId(id);
Response response = target("/ingredients/" + id).request().get();
Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class));
assertEquals(ingredient, result);
}
~~~
### La ressource avec la base de données
import fr.ulille.iut.pizzaland.BDDFactory;
import fr.ulille.iut.pizzaland.dao.IngredientDao;
import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException;
~~~java
@Produces("application/json")
@Path("/ingredients")
public class IngredientResource {
private static final Logger LOGGER = Logger.getLogger(IngredientResource.class.getName());
private IngredientDao ingredients;
@Context
public UriInfo uriInfo;
public IngredientResource() {
ingredients = BDDFactory.buildDao(IngredientDao.class);
ingredients.createTable();
......@@ -522,58 +504,65 @@ détruire la base de données entre chaque test.
LOGGER.info("IngredientResource:getAll");
List<IngredientDto> l = ingredients.getAll().stream().map(Ingredient::toDto).collect(Collectors.toList());
LOGGER.info(l.toString());
return l;
}
@GET
@Path("{id}")
public IngredientDto getOneIngredient(@PathParam("id") long id) {
@Produces({ "application/json", "application/xml" })
public IngredientDto getOneIngredient(@PathParam("id") UUID id) {
LOGGER.info("getOneIngredient(" + id + ")");
try {
Ingredient ingredient = ingredients.findById(id);
LOGGER.info(ingredient.toString());
return Ingredient.toDto(ingredient);
}
catch ( Exception e ) {
// Cette exception générera une réponse avec une erreur 404
} catch (Exception e) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
}
~~~
### Les tests fonctionnent avec la base de données
Nous pouvons maintenant vérifier que la base fonctionne avec la base
de données :
~~~
$ mvn test
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
~~~
## Complétons maintenant les différents tests
L'implémentation de la classe devrait fonctionner avec le test suivant
:
~~~java
@Test
public void testGetNotExistingPizza() {
Response response = target("/pizzas/125").request().get();
public void testGetNotExistingIngredient() {
Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(),response.getStatus());
}
~~~
~~~
$ mvn test
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
~~~
### Implementation de la méthode POST
Il va falloir implémenter la méthode POST pour la création des
ingrédients. Commençons par les différents tests : création, création
de deux ingrédients identiques et création d'ingrédient sans nom.
~~~java
import fr.ulille.iut.pizzaland.dto.IngredientCreateDto;
@Test
......@@ -581,33 +570,24 @@ de deux ingrédients identiques et création d'ingrédient sans nom.
IngredientCreateDto ingredientCreateDto = new IngredientCreateDto();
ingredientCreateDto.setName("Chorizo");
Response response = target("/ingredients")
.request()
.post(Entity.json(ingredientCreateDto));
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
// On vérifie le code de status à 201
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
IngredientDto returnedEntity = response.readEntity(IngredientDto.class);
// On vérifie que le champ d'entête Location correspond à
// l'URI de la nouvelle entité
assertEquals(target("/ingredients/" +
returnedEntity.getId()).getUri(), response.getLocation());
// On vérifie que le nom correspond
assertEquals(target("/ingredients/" + returnedEntity.getId()).getUri(), response.getLocation());
assertEquals(returnedEntity.getName(), ingredientCreateDto.getName());
}
@Test
public void testCreateSameIngredient() {
IngredientCreateDto ingredientCreateDto = new IngredientCreateDto();
ingredientCreateDto.setName("Chorizo");
dao.insert(ingredientCreateDto.getName());
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
Response response = target("/ingredients")
.request()
.post(Entity.json(ingredientCreateDto));
IngredientCreateDto ingredientCreateDto = Ingredient.toCreateDto(ingredient);
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus());
}
......@@ -616,22 +596,23 @@ de deux ingrédients identiques et création d'ingrédient sans nom.
public void testCreateIngredientWithoutName() {
IngredientCreateDto ingredientCreateDto = new IngredientCreateDto();
Response response = target("/ingredients")
.request()
.post(Entity.json(ingredientCreateDto));
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus());
}
~~~
Nous utiliserons un DTO spécifique `IngredientCreateDto` dans la
mesure où nous n'aurons que le nom de l'ingrédient pour la création.
La classe [`javax.ws.rs.client.Entity<T>`](https://docs.oracle.com/javaee/7/api/javax/ws/rs/client/Entity.html) permet de définir le corps de
La classe [`jakarta.ws.rs.client.Entity<T>`](https://jakarta.ee/specifications/platform/9/apidocs/jakarta/ws/rs/client/entity) permet de définir le corps de
la requête POST et le type de données associée (ici `application/json`).
Nous devons également fournir une implémentation de
`IngredientCreateDto` pour pouvoir compiler notre code :
~~~java
package fr.ulille.iut.pizzaland.dto;
public class IngredientCreateDto {
......@@ -647,10 +628,12 @@ Nous devons également fournir une implémentation de
return name;
}
}
~~~
Nous pouvons maintenant compiler notre code de test et constater que
ceux-ci échouent.
~~~
$ mvn test
Results :
......@@ -660,12 +643,13 @@ ceux-ci échouent.
testCreateIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<201> but was:<405>
Tests run: 6, Failures: 3, Errors: 0, Skipped: 0
~~~
Nous pouvons maintenant implémenter notre méthode POST dans la
ressource :
import javax.ws.rs.POST;
~~~java
import jakarta.ws.rs.POST;
import fr.ulille.iut.pizzaland.dto.IngredientCreateDto;
@POST
......@@ -677,30 +661,33 @@ Nous pouvons maintenant implémenter notre méthode POST dans la
try {
Ingredient ingredient = Ingredient.fromIngredientCreateDto(ingredientCreateDto);
long id = ingredients.insert(ingredient.getName());
ingredient.setId(id);
ingredients.insert(ingredient);
IngredientDto ingredientDto = Ingredient.toDto(ingredient);
URI uri = uriInfo.getAbsolutePathBuilder().path("" + id).build();
URI uri = uriInfo.getAbsolutePathBuilder().path(ingredient.getId().toString()).build();
return Response.created(uri).entity(ingredientDto).build();
}
catch ( Exception e ) {
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
}
}
~~~
Comme nous vérifions qu'il n'y a pas déjà un ingrédient avec le nom
fourni, nous devont ajouter une méthode `findbyName` à notre DAP
fourni, nous devont ajouter une méthode `findbyName` à notre DAO
~~~java
@SqlQuery("SELECT * FROM ingredients WHERE name = :name")
@RegisterBeanMapper(Ingredient.class)
Ingredient findByName(String name);
Ingredient findByName(@Bind("name") String name);
~~~
Nous avons également besoin de rajouter les méthodes de conversion
pour ce DTO à notre bean `Ingredient` :
~~~java
import fr.ulille.iut.pizzaland.dto.IngredientCreateDto;
public static IngredientCreateDto toCreateDto(Ingredient ingredient) {
......@@ -716,14 +703,17 @@ Nous avons également besoin de rajouter les méthodes de conversion
return ingredient;
}
~~~
Nous pouvons maintenant vérifier nos tests :
~~~
$ mvn test
Results :
Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
~~~
Vous aurez peut-être un affichage d'exception liée au test de création
de doublon, toutefois le test est réussi puisqu'il a levé une
......@@ -732,36 +722,37 @@ exception qui a été traduite par un code d'erreur HTTP 406.
### Implémentation de la méthode DELETE
Les tests liés à la méthode DELETE sont les suivants :
~~~java
@Test
public void testDeleteExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
long id = dao.insert(ingredient.getName());
ingredient.setId(id);
dao.insert(ingredient);
Response response = target("/ingredients/" + id).request().delete();
Response response = target("/ingredients/").path(ingredient.getId().toString()).request().delete();
assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus());
Ingredient result = dao.findById(id);
Ingredient result = dao.findById(ingredient.getId());
assertEquals(result, null);
}
@Test
public void testDeleteNotExistingIngredient() {
Response response = target("/ingredients/125").request().delete();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response.getStatus());
Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().delete();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
~~~
Après avoir constaté que ces tests échouent, nous pouvons fournir une
implémentation pour la méthode DELETE :
import javax.ws.rs.DELETE;
~~~java
import jakarta.ws.rs.DELETE;
@DELETE
@Path("{id}")
public Response deleteIngredient(@PathParam("id") long id) {
public Response deleteIngredient(@PathParam("id") UUID id) {
if ( ingredients.findById(id) == null ) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
......@@ -770,12 +761,15 @@ implémentation pour la méthode DELETE :
return Response.status(Response.Status.ACCEPTED).build();
}
~~~
Nous devons également implémenter la méthode remove dans
`IngredientDao` :
~~~java
@SqlUpdate("DELETE FROM ingredients WHERE id = :id")
void remove(long id);
void remove(@Bind("id") UUID id);
~~~
Avec cette implémentation, nos tests réussissent.
......@@ -783,13 +777,14 @@ Avec cette implémentation, nos tests réussissent.
Commençons par les tests correspondant à cette URI (GET
/ingredients/{id}/name)
~~~java
@Test
public void testGetIngredientName() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
long id = dao.insert(ingredient.getName());
dao.insert(ingredient);
Response response = target("ingredients/" + id + "/name").request().get();
Response response = target("ingredients").path(ingredient.getId().toString()).path("name").request().get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
......@@ -798,23 +793,81 @@ Commençons par les tests correspondant à cette URI (GET
@Test
public void testGetNotExistingIngredientName() {
Response response = target("ingredients/125/name").request().get();
Response response = target("ingredients").path(UUID.randomUUID().toString()).path("name").request().get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
~~~
L'implémentation correspondant à ce test est simple :
~~~java
@GET
@Path("{id}/name")
public String getIngredientName(@PathParam("id") long id) {
public String getIngredientName(@PathParam("id") UUID id) {
Ingredient ingredient = ingredients.findById(id);
if (ingredient == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return ingredient.getName();
}
~~~
### Implémentation d'une méthode de création avec des données de formulaire
La création d'un ingrédient pourrait également se faire via un
formulaire Web. Dans ce cas, le type de représentation sera
`application/x-www-form-urlencoded`.
On peut déjà préparer un test pour cette méthode de création :
~~~java
@Test
public void testCreateWithForm() {
Form form = new Form();
form.param("name", "chorizo");
Entity<Form> formEntity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE);
Response response = target("ingredients").request().post(formEntity);
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
String location = response.getHeaderString("Location");
String id = location.substring(location.lastIndexOf('/') + 1);
Ingredient result = dao.findById(UUID.fromString(id));
assertNotNull(result);
}
~~~
On peut maintenant fournir une implémentation pour cette méthode :
~~~java
@POST
@Consumes("application/x-www-form-urlencoded")
public Response createIngredient(@FormParam("name") String name) {
Ingredient existing = ingredients.findByName(name);
if (existing != null) {
throw new WebApplicationException(Response.Status.CONFLICT);
}
try {
Ingredient ingredient = new Ingredient();
ingredient.setName(name);
ingredients.insert(ingredient);
IngredientDto ingredientDto = Ingredient.toDto(ingredient);
URI uri = uriInfo.getAbsolutePathBuilder().path("" + ingredient.getId()).build();
return Response.created(uri).entity(ingredientDto).build();
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
}
}
~~~
# Créer une base de données de test
Nous avons maintenant implémenté et testé toutes les méthodes prévues
......@@ -826,27 +879,36 @@ d'une variable d'environnement : `PIZZAENV`.
Quand cette variable aura la valeur `withdb`, nous allons remplir la
base au démarrage avec le code suivant :
~~~java
package fr.ulille.iut.pizzaland;
import org.glassfish.jersey.server.ResourceConfig;
import fr.ulille.iut.pizzaland.beans.Ingredient;
import fr.ulille.iut.pizzaland.beans.Pizza;
import fr.ulille.iut.pizzaland.dao.IngredientDao;
import fr.ulille.iut.pizzaland.dao.PizzaDao;
import fr.ulille.iut.pizzaland.dto.PizzaCreateDto;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbBuilder;
import jakarta.ws.rs.ApplicationPath;
@ApplicationPath("api/v1/")
public class ApiV1 extends ResourceConfig {
packages("fr.ulille.iut.pizzaland");
private static final Logger LOGGER = Logger.getLogger(ApiV1.class.getName());
public ApiV1() {
packages("fr.ulille.iut.pizzaland");
String environment = System.getenv("PIZZAENV");
if ( environment != null && environment.equals("withdb") ) {
LOGGER.info("Loading with database");
Jsonb jsonb = JsonbBuilder.create();
try {
FileReader reader = new FileReader( getClass().getClassLoader().getResource("ingredients.json").getFile() );
List<Ingredient> ingredients = JsonbBuilder.create().fromJson(reader, new ArrayList<Ingredient>(){}.getClass().getGenericSuperclass());
......@@ -855,30 +917,68 @@ base au démarrage avec le code suivant :
ingredientDao.dropTable();
ingredientDao.createTable();
for ( Ingredient ingredient: ingredients) {
ingredientDao.insert(ingredient.getName());
ingredientDao.insert(ingredient);
}
} catch ( Exception ex ) {
throw new IllegalStateException(ex);
}
}
}
}
~~~
Dans un terminal, nous pouvons maintenant fixer la variable
d'environnemnet et démarrer notre serveur REST au moyen de la
commande `mvn jetty:run` :
commande `mvn exec:java` :
~~~
$ export PIZZAENV=withdb
$ mvn jetty:run
$ mvn exec:java
~~~
Dans un autre terminal, nous pouvons utiliser `curl` pour tester nos
différentes méthodes :
~~~
$ curl -i localhost:8080/api/v1/ingredients
HTTP/1.1 200 OK
Date: Sun, 09 Feb 2020 22:08:05 GMT
Content-Type: application/json
Content-Length: 319
Server: Jetty(9.4.26.v20200117)
Content-Length: 760
[{"id":"f38806a8-7c85-49ef-980c-149dcd81d306","name":"mozzarella"},{"id":"d36903e1-0cc0-4bd6-a0ed-e0e9bf7b4037","name":"jambon"},{"id":"bc5b315f-442f-4ee4-96de-486d48f20c2f","name":"champignons"},{"id":"6a04320c-3a4f-4570-96d3-61faf3f898b0","name":"olives"},{"id":"c77deeee-d50d-49d5-9695-c98ec811f762","name":"tomate"},{"id":"c9375542-8142-43f6-b54d-0d63597cf614","name":"merguez"},{"id":"dee27dd6-f9b6-4d03-ac4b-216b5c9c8bd7","name":"lardons"},{"id":"657f8dd4-6bc1-4622-9af7-37d248846a23","name":"fromage"},{"id":"070d8077-a713-49a0-af37-3936b63d5ff2","name":"oeuf"},{"id":"5d9ca5c4-517f-40fd-aac3-5a823d680c1d","name":"poivrons"},{"id":"52f68024-24ec-46c0-8e77-c499dba1e27e","name":"ananas"},{"id":"dfdf6fae-f1b2-45fa-8c39-54e522c1933f","name":"reblochon"}]
~~~
# Implémentation de la ressource Pizza
Maintenant que vous avez une ressource `ingrédients` fonctionnelle, vous pouvez passer à l'implémentation de la ressource `Pizzas`. Pour cette ressource, vous devrez d'abord définir l'API dans le fichier `pizzas.md` (URI, méthodes, représentations). Ensuite, vous pourrez développer la ressource avec les tests associés.
Il est fortement recommandé d'adopter la même approche que pour `Ingredient` en développement progressivement les tests puis les fonctionnalitées associées.
## Note sur la base de données
Une pizza comprend des ingrédients. Pour développer cette ressource,
vous aurez donc besoin d'un table d'association au niveau de la base
de données. Cela pourra être géré au niveau du DAO grâce à
[JDBI](https://jdbi.org/#_default_methods). Cet extrait de code montre
comment faire :
~~~java
import org.jdbi.v3.sqlobject.transaction.Transaction;
public interface PizzaDao {
@SqlUpdate("CREATE TABLE IF NOT EXISTS Pizzas ....")
void createPizzaTable();
@SqlUpdate("CREATE TABLE IF NOT EXISTS PizzaIngredientsAssociation .....")
void createAssociationTable();
@Transaction
default void createTableAndIngredientAssociation() {
createAssociationTable();
createPizzaTable();
}
~~~
[{"id":1,"name":"mozzarella"},{"id":2,"name":"jambon"},{"id":3,"name":"champignons"},{"id":4,"name":"olives"},{"id":5,"name":"tomate"},{"id":6,"name":"merguez"},{"id":7,"name":"lardons"},{"id":8,"name":"fromage"},{"id":9,"name":"oeuf"},{"id":10,"name":"poivrons"},{"id":11,"name":"ananas"},{"id":12,"name":"reblochon"}]
Vous écrivez les différentes méthodes annotées avec `@SqlUpdate` ou
`@SqlQuery`. Vous utilisez ensuite ces méthodes au sein d'une méthode
ayant le mot clé `default`. C'est cette méthode que vous utiliserez
dans votre ressource.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fr.ulille.iut.pizzaland</groupId>
<artifactId>pizzaland_jdbi</artifactId>
<packaging>war</packaging>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>pizzaland</name>
<build>
<finalName>pizzaland</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<inherited>true</inherited>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-version}</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
<name>pizzaland_jdbi</name>
<dependencyManagement>
<dependencies>
......@@ -57,7 +23,7 @@
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-bom</artifactId>
<type>pom</type>
<version>3.12.0</version>
<version>${jdbi.version}</version>
<scope>import</scope>
</dependency>
</dependencies>
......@@ -66,63 +32,132 @@
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<artifactId>jersey-container-servlet</artifactId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<!-- uncomment this to get JSON support -->
<!-- uncomment this to get JSON support: -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
<!-- -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-jetty</artifactId>
<version>2.30</version>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.30.1</version>
<version>${sqlite-jdbc.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jdbi/jdbi3-core -->
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlobject</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlite</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-testing</artifactId>
<version>3.12.0</version>
<version>3.27.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.2</version>
<scope>runtime</scope>
</dependency>
<!-- dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
</dependency-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<inherited>true</inherited>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>fr.ulille.iut.pizzaland.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>fr.ulille.iut.pizzaland.Main</mainClass>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<jersey.version>2.30</jersey.version>
<jetty-version>9.4.26.v20200117</jetty-version>
<jersey.version>3.0.3</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<shade.version>3.2.4</shade.version>
<lombok.version>1.18.16</lombok.version>
<jdbi.version>3.27.0</jdbi.version>
<sqlite-jdbc.version>3.36.0.3</sqlite-jdbc.version>
<jaxb.version>2.4.0-b180830.0359</jaxb.version>
</properties>
</project>
......@@ -4,7 +4,7 @@ import org.glassfish.jersey.server.ResourceConfig;
import java.util.logging.Logger;
import javax.ws.rs.ApplicationPath;
import jakarta.ws.rs.ApplicationPath;
@ApplicationPath("api/v1/")
public class ApiV1 extends ResourceConfig {
......
......@@ -3,30 +3,42 @@ package fr.ulille.iut.pizzaland;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Properties;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.sqlite3.SQLitePlugin;
import org.jdbi.v3.sqlobject.SqlObjectPlugin;
import fr.ulille.iut.pizzaland.dao.UUIDArgumentFactory;
public class BDDFactory {
private static Jdbi jdbi = null;
private static String dbPath = "jdbc:sqlite:"
+ System.getProperty("java.io.tmpdir")
+ System.getProperty("file.separator")
+ System.getProperty("user.name")
+ "_";
public static Jdbi getJdbi() {
if ( jdbi == null ) {
jdbi = Jdbi.create("jdbc:sqlite:"
+ System.getProperty("java.io.tmpdir")
+ System.getProperty("file.separator") + "pizza.db")
Properties properties = new Properties();
properties.setProperty("foreign_keys", "true");
jdbi = Jdbi.create(dbPath + "pizzas.db", properties)
.installPlugin(new SQLitePlugin())
.installPlugin(new SqlObjectPlugin());
.installPlugin(new SqlObjectPlugin()).registerArgument(new UUIDArgumentFactory());
}
return jdbi;
}
public static void setJdbiForTests() {
if ( jdbi == null ) {
jdbi = Jdbi.create("jdbc:sqlite:"
+ System.getProperty("java.io.tmpdir")
+ System.getProperty("file.separator") + "pizza_test.db")
Properties properties = new Properties();
properties.setProperty("foreign_keys", "true");
jdbi = Jdbi.create(dbPath + "pizza_test.db", properties)
.installPlugin(new SQLitePlugin())
.installPlugin(new SqlObjectPlugin());
}
......@@ -40,6 +52,17 @@ public class BDDFactory {
return exist;
}
public static void dropTables() throws SQLException {
Handle handle = getJdbi().open();
DatabaseMetaData dbm = handle.getConnection().getMetaData();
ResultSet tables = dbm.getTables(null, null, "", null);
ArrayList<String> tableNames = new ArrayList<String>();
while ( tables.next() ) {
tableNames.add(tables.getString("TABLE_NAME"));
}
tableNames.forEach(name -> handle.execute("drop table " + name));
}
public static <T> T buildDao(Class<T> daoClass) {
return getJdbi().onDemand(daoClass);
}
......
package fr.ulille.iut.pizzaland;
import java.io.IOException;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.server.ResourceConfig;
/**
* Main class.
*
*/
public class Main {
// Base URI the Grizzly HTTP server will listen on
public static final String BASE_URI = "http://localhost:8080/api/v1/";
private static final LogManager logManager = LogManager.getLogManager();
static {
try {
logManager.readConfiguration(Main.class.getClassLoader().getResourceAsStream("logging.properties"));
}
catch ( Exception ex ) {
ex.printStackTrace();
}
}
/**
* Starts Grizzly HTTP server exposing JAX-RS resources defined in this
* application.
*
* @return Grizzly HTTP server.
*/
public static HttpServer startServer() {
// create a resource config that scans for JAX-RS resources and providers
// in fr.ulille.iut.todo package
/* ResourceConfig rc = new ResourceConfig().packages("fr.ulille.iut.pizzaland");
// Activation des log des requêtes et réponses
String logging = System.getenv("LOG_MESSAGES");
if ( logging != null && logging.equalsIgnoreCase("true") ) {
rc.register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), Level.INFO,
LoggingFeature.Verbosity.PAYLOAD_ANY, 10000));
}
String debug = System.getenv("DEBUG_INTERNAL");
if ( debug != null && debug.equalsIgnoreCase("true") ) {
rc.register(DebugMapper.class);
}*/
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), new ApiV1());
}
/**
* Main method.
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
System.out.println(String.format(
"Jersey app started with WADL available at " + "%sapplication.wadl\nexport LOG_MESSAGES=true pour voir les requêtes et réponses\nexport DEBUG_INTERNAL=true pour voir les erreurs 500\nHit enter to stop it...",
BASE_URI));
System.in.read();
server.stop();
}
}
package fr.ulille.iut.pizzaland.dao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import org.jdbi.v3.core.argument.Argument;
import org.jdbi.v3.core.statement.StatementContext;
public class UUIDArgument implements Argument {
private final UUID value;
public UUIDArgument(UUID value) {
this.value = value;
}
@Override
public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
statement.setString(position, value.toString());
}
}
package fr.ulille.iut.pizzaland.dao;
import java.sql.Types;
import java.util.UUID;
import org.jdbi.v3.core.argument.AbstractArgumentFactory;
import org.jdbi.v3.core.argument.Argument;
import org.jdbi.v3.core.config.ConfigRegistry;
public class UUIDArgumentFactory extends AbstractArgumentFactory<UUID> {
public UUIDArgumentFactory() {
super(Types.VARCHAR);
}
@Override
protected Argument build(UUID value, ConfigRegistry config) {
return (position, statement, ctx) -> statement.setString(position, value.toString());
}
}
package fr.ulille.iut.pizzaland.resources;
import java.sql.SQLException;
import fr.ulille.iut.pizzaland.BDDFactory;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
/**
* BDDClearRessource
*/
@Path("clearDatabase")
public class BDDClearRessource {
@GET
public void clearDatabase() throws SQLException {
BDDFactory.dropTables();
}
}
......@@ -5,11 +5,11 @@ import java.util.List;
import java.util.ArrayList;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import fr.ulille.iut.pizzaland.dto.IngredientDto;
......@@ -27,6 +27,6 @@ public class IngredientResource {
public List<IngredientDto> getAll() {
LOGGER.info("IngredientResource:getAll");
return null;
return new ArrayList<IngredientDto>();
}
}
[
{ "name": "mozzarella"},
{ "name": "jambon"},
{ "name": "champignons"},
{ "name": "olives"},
{ "name": "tomate"},
{ "name": "merguez"},
{ "name": "lardons"},
{ "name": "fromage"},
{ "name": "oeuf"},
{ "name": "poivrons"},
{ "name": "ananas"},
{ "name": "reblochon"}
{ "id": "f38806a8-7c85-49ef-980c-149dcd81d306", "name": "mozzarella"},
{ "id": "d36903e1-0cc0-4bd6-a0ed-e0e9bf7b4037", "name": "jambon"},
{ "id": "bc5b315f-442f-4ee4-96de-486d48f20c2f", "name": "champignons"},
{ "id": "6a04320c-3a4f-4570-96d3-61faf3f898b0", "name": "olives"},
{ "id": "c77deeee-d50d-49d5-9695-c98ec811f762", "name": "tomate"},
{ "id": "c9375542-8142-43f6-b54d-0d63597cf614", "name": "merguez"},
{ "id": "dee27dd6-f9b6-4d03-ac4b-216b5c9c8bd7", "name": "lardons"},
{ "id": "657f8dd4-6bc1-4622-9af7-37d248846a23", "name": "fromage"},
{ "id": "070d8077-a713-49a0-af37-3936b63d5ff2", "name": "oeuf"},
{ "id": "5d9ca5c4-517f-40fd-aac3-5a823d680c1d", "name": "poivrons"},
{ "id": "52f68024-24ec-46c0-8e77-c499dba1e27e", "name": "ananas"},
{ "id": "dfdf6fae-f1b2-45fa-8c39-54e522c1933f", "name": "reblochon"}
]
<configuration scan="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="fr.ulille.iut.pizzaland" level="debug"/>
</configuration>
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
\ No newline at end of file
......@@ -9,10 +9,10 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Response;
import static org.junit.Assert.assertEquals;
......@@ -21,7 +21,7 @@ import java.util.logging.Logger;
/*
* JerseyTest facilite l'écriture des tests en donnant accès aux
* méthodes de l'interface javax.ws.rs.client.Client.
* méthodes de l'interface jakarta.ws.rs.client.Client.
* la méthode configure() permet de démarrer la ressource à tester
*/
public class IngredientResourceTest extends JerseyTest {
......@@ -32,7 +32,8 @@ public class IngredientResourceTest extends JerseyTest {
return new ApiV1();
}
// Les méthodes setEnvUp() et tearEnvDown() serviront à terme à initialiser la base de données
// Les méthodes setEnvUp() et tearEnvDown() serviront à terme à initialiser la
// base de données
// et les DAO
// https://stackoverflow.com/questions/25906976/jerseytest-and-junit-throws-nullpointerexception
......@@ -55,13 +56,13 @@ public class IngredientResourceTest extends JerseyTest {
// On vérifie le code de la réponse (200 = OK)
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
// On vérifie la valeur retournée (liste vide)
// L'entité (readEntity() correspond au corps de la réponse HTTP.
// La classe javax.ws.rs.core.GenericType<T> permet de définir le type
// de la réponse lue quand on a un type complexe (typiquement une liste).
// La classe jakarta.ws.rs.core.GenericType<T> permet de définir le type
// de la réponse lue quand on a un type paramétré (typiquement une liste).
List<IngredientDto> ingredients;
ingredients = response.readEntity(new GenericType<List<IngredientDto>>(){});
ingredients = response.readEntity(new GenericType<List<IngredientDto>>() {
});
assertEquals(0, ingredients.size());
......
<configuration scan="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="fr.ulille.iut.pizzaland" level="debug"/>
</configuration>
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
\ No newline at end of file