Travail d'intégration

Travail d’intégration #

Ce travail d’intégration vous permet de démontrer l’acquisition des compétences développées tout au long du cours. Vous devez créer une application web complète qui intègre plusieurs technologies : HTML5, JavaScript, AJAX, Java, JSON, XML, SVG, MathML, YAML, Maven et les styles CSS. Vous utilisez les libraires Java Gson, Jackson et Apache POI.

Avertissement. Vous devez avoir fait toutes les activités du cours avant de tenter de faire l’activité d’intégration.

Objectif #

Créer un serveur web Java qui sert une page HTML5 permettant à l’utilisateur d’entrer une liste de 10 entiers et de dix étiquettes (par exemple, les dix provinces canadiennes). Ce serveur reçoit ces données via AJAX, génère un graphique à barres SVG avec des styles appropriés, puis retourne le graphique intégré dans un document JSON.

Technologies à utiliser #

Pour réaliser ce projet, vous utiliserez plusieurs technologies. Le serveur sera développé en Java avec HttpServer. Pour le traitement des données XML, vous emploierez Jackson, tandis que Gson sera utilisé pour manipuler les données JSON. Apache POI sera utilisé pour générer des documents Excel au format Open XML. XSLT sera utilisé pour transformer les données XML en Markdown. L’interface utilisateur sera créée en HTML5 valide, avec des graphiques vectoriels SVG stylisés en CSS. La communication asynchrone se fera via AJAX, et les styles CSS3 seront appliqués tant à l’interface qu’aux éléments SVG.

Cahier des charges #

Interface utilisateur (HTML5) #

La page doit contenir 10 champs numériques pour saisir les valeurs et 10 champs texte pour les étiquettes associées. Quatre boutons permettent d’interagir avec les différents endpoints du serveur : générer le graphique (qui affiche le SVG directement dans la page), télécharger le fichier SVG, télécharger un document Excel au format Open XML, et afficher le contenu Markdown généré. Une zone d’affichage est réservée pour montrer le résultat SVG, et une autre zone (initialement cachée) pour afficher le Markdown. Toutes les générations de contenu se font du côté serveur en Java, en utilisant les technologies appropriées pour chaque format.

Indices. Il peut être possible récupérer une réponse JSON d’un serveur avec du code JavaScript similaire à celui-ci.

fetch('/route')
  .then(response => response.json())
  .then(data => {
    // code de traitement ici
  });

Il peut être possible d’insérer du SVG dans la page courante avec un code semblable à celui-ci.

const container = document.getElementById('svg-container');
const parser = new DOMParser();
const svgDoc = parser.parseFromString(data.svg, 'image/svg+xml');
const svgElement = svgDoc.documentElement;  // The root <svg> element
container.appendChild(svgElement);

Maven #

Maven est utilisé pour la gestion des dépendances et la construction du projet Java. Vous devez créer un fichier pom.xml à la racine du projet. Un fichier possible est celui-ci.

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>graph-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.15.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.15.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.4</version>
        </dependency>
    </dependencies>
</project>

La structure des fichiers doit suivre les conventions Maven :

  • pom.xml à la racine du projet.
  • Les fichiers Java (comme GraphServer.java) dans src/main/java/.
  • Les ressources statiques (comme index.html) dans src/main/resources/.

Pour compiler le projet, utilisez mvn compile.

Configuration YAML #

Votre service web doit être configurable avec un fichier YAML. Le fichier de configuration config.yml doit être placé dans le répertoire src/main/resources/ du projet Maven. Ce fichier contiendra les paramètres du serveur tels que le port, les chemins par défaut, et d’autres options de configuration.

Exemple de fichier config.yml :

server:
  port: 8080
  host: localhost
graph:
  defaultWidth: 800
  defaultHeight: 600
  colors:
    primary: "#3498db"
    secondary: "#e74c3c"

Pour charger ce fichier YAML dans votre code Java, utilisez Jackson avec YAMLFactory :

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

// Dans votre classe serveur
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
Config config = yamlMapper.readValue(new File("src/main/resources/config.yml"), Config.class);

Serveur Java #

Le serveur doit servir la page HTML statique sur la route racine /. Il recevra les données XML sur l’endpoint /api/graph via une requête POST. Le serveur validera que exactement 10 entiers sont fournis, générera un SVG avec un graphique à barres, puis retournera un document JSON contenant le SVG. De plus, un endpoint /api/svg devra retourner le SVG pur directement. Un endpoint /api/excel devra générer et retourner un fichier Excel Open XML avec les données saisies. Enfin, un endpoint /api/markdown devra transformer les données XML en Markdown à l’aide d’XSLT et retourner le résultat.

Graphique SVG #

Le SVG doit afficher 10 barres verticales représentant les valeurs saisies par l’utilisateur avec les étiquettes associées. Il utilisera des dégradés. Les axes seront étiquetés de manière claire. Le graphique inclura des infobulles au survol des barres. Vous devez par ailleurs include une expression MathML dans votre SVG.

Génération d’un document Excel (Open XML) #

Le serveur doit également fournir une fonction capable de générer et retourner un document Excel au format Open XML contenant les données saisies par l’utilisateur. Ce document devra inclure une feuille de calcul avec les valeurs et les étiquettes dans des colonnes appropriées. Un endpoint supplémentaire, par exemple /api/excel, devra être créé pour permettre le téléchargement du fichier Excel. Vous utiliserez Apache POI pour créer le fichier .xlsx.

Transformation XSLT vers Markdown #

Le serveur doit prendre les données XML reçues et les transformer en Markdown à l’aide d’une feuille de style XSLT. Un endpoint /api/markdown devra retourner le contenu Markdown généré, par exemple sous forme de texte brut ou dans un objet JSON. Vous devrez créer une feuille XSLT qui convertit la structure XML des données (valeurs et étiquettes) en un format Markdown lisible, tel qu’une liste ou un tableau.

Structure proposée #

1. Page HTML (index.html) (point de départ) #

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Générateur de graphiques</title>
</head>
<body>
    <h1>Générateur de graphiques à barres</h1>
    
    <div class="input-group">
        <h3>Valeurs</h3>
        <input type="number" id="val1" placeholder="Valeur 1" min="0" max="100" required>
        <input type="number" id="val2" placeholder="Valeur 2" min="0" max="100" required>
        <input type="number" id="val3" placeholder="Valeur 3" min="0" max="100" required>
        <input type="number" id="val4" placeholder="Valeur 4" min="0" max="100" required>
        <input type="number" id="val5" placeholder="Valeur 5" min="0" max="100" required>
        <input type="number" id="val6" placeholder="Valeur 6" min="0" max="100" required>
        <input type="number" id="val7" placeholder="Valeur 7" min="0" max="100" required>
        <input type="number" id="val8" placeholder="Valeur 8" min="0" max="100" required>
        <input type="number" id="val9" placeholder="Valeur 9" min="0" max="100" required>
        <input type="number" id="val10" placeholder="Valeur 10" min="0" max="100" required>
    </div>
    
    <div class="input-group">
        <h3>Étiquettes</h3>
        <input type="text" id="label1" placeholder="Étiquette 1" required>
        <input type="text" id="label2" placeholder="Étiquette 2" required>
        <input type="text" id="label3" placeholder="Étiquette 3" required>
        <input type="text" id="label4" placeholder="Étiquette 4" required>
        <input type="text" id="label5" placeholder="Étiquette 5" required>
        <input type="text" id="label6" placeholder="Étiquette 6" required>
        <input type="text" id="label7" placeholder="Étiquette 7" required>
        <input type="text" id="label8" placeholder="Étiquette 8" required>
        <input type="text" id="label9" placeholder="Étiquette 9" required>
        <input type="text" id="label10" placeholder="Étiquette 10" required>
    </div>
    
    <button type="button" id="generateGraph">Générer le graphique</button>
    <button type="button" id="downloadSvg">Télécharger SVG</button>
    <button type="button" id="downloadExcel">Télécharger Excel</button>
    <button type="button" id="showMarkdown">Afficher Markdown</button>

    <div id="result"></div>
    <div id="markdownResult" style="display:none;">
        <h3>Markdown généré</h3>
        <textarea id="markdownTextarea" rows="10" cols="80" readonly></textarea>
    </div>

    <script>
        document.getElementById('generateGraph').addEventListener('click', () => generate('/api/graph', 'graph'));
        document.getElementById('downloadSvg').addEventListener('click', () => generate('/api/svg', 'svg'));
        document.getElementById('downloadExcel').addEventListener('click', () => generate('/api/excel', 'excel'));
        document.getElementById('showMarkdown').addEventListener('click', () => generate('/api/markdown', 'markdown'));

        async function generate(endpoint, type) {
            // Collecter les valeurs
            const values = [];
            for (let i = 1; i <= 10; i++) {
                const val = parseInt(document.getElementById('val' + i).value);
                if (isNaN(val)) {
                    alert('Veuillez entrer des valeurs numériques valides');
                    return;
                }
                values.push(val);
            }

            // Collecter les étiquettes
            const labels = [];
            for (let i = 1; i <= 10; i++) {
                const lab = document.getElementById('label' + i).value.trim();
                if (!lab) {
                    alert('Veuillez entrer toutes les étiquettes');
                    return;
                }
                labels.push(lab);
            }

            // Créer le XML
            const doc = document.implementation.createDocument(null, 'data');
            const valuesElement = doc.createElement('values');
            values.forEach(v => {
                const valueElement = doc.createElement('value');
                valueElement.textContent = v;
                valuesElement.appendChild(valueElement);
            });
            doc.documentElement.appendChild(valuesElement);

            const labelsElement = doc.createElement('labels');
            labels.forEach(l => {
                const labelElement = doc.createElement('label');
                labelElement.textContent = l;
                labelsElement.appendChild(labelElement);
            });
            doc.documentElement.appendChild(labelsElement);

            const serializer = new XMLSerializer();
            const xmlData = serializer.serializeToString(doc);

            try {
                const response = await fetch(endpoint, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/xml',
                    },
                    body: xmlData
                });

                if (response.ok) {
                    if (type === 'graph') {
                        const data = await response.json();
                        const container = document.getElementById('result');
                        container.innerHTML = '';
                        const parser = new DOMParser();
                        const svgDoc = parser.parseFromString(data.svg, 'image/svg+xml');
                        const svgElement = svgDoc.documentElement;
                        container.appendChild(svgElement);
                    } else if (type === 'svg') {
                        const svgText = await response.text();
                        const blob = new Blob([svgText], {type: 'image/svg+xml'});
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = 'graph.svg';
                        a.click();
                        URL.revokeObjectURL(url);
                    } else if (type === 'excel') {
                        const blob = await response.blob();
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = 'data.xlsx';
                        a.click();
                        URL.revokeObjectURL(url);
                    } else if (type === 'markdown') {
                        const markdown = await response.text();
                        document.getElementById('markdownTextarea').value = markdown;
                        document.getElementById('markdownResult').style.display = 'block';
                    }
                } else {
                    alert('Erreur du serveur: ' + response.status);
                }
            } catch (error) {
                alert('Erreur: ' + error.message);
            }
        }
    </script>
</body>
</html>

2. Serveur Java (GraphServer.java) (point de départ possible) #

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

public class GraphServer {
    private static final Gson gson = new Gson();
    private static final ObjectMapper jsonMapper = new ObjectMapper();
    private static final XmlMapper xmlMapper = new XmlMapper();

    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        server.createContext("/", exchange -> {
            try {
                if ("GET".equals(exchange.getRequestMethod())) {
                    String html = new String(Files.readAllBytes(Paths.get("index.html")), "UTF-8");
                    exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");
                    exchange.sendResponseHeaders(200, html.getBytes("UTF-8").length);
                    try (OutputStream os = exchange.getResponseBody()) {
                        os.write(html.getBytes("UTF-8"));
                    }
                } else {
                    exchange.sendResponseHeaders(405, -1);
                }
            } catch (Exception e) {
                exchange.sendResponseHeaders(500, -1);
            }
        });

        // Endpoint pour générer le graphique
        server.createContext("/api/graph", exchange -> {
            if ("POST".equals(exchange.getRequestMethod())) {
                try {
                    // Lire les données XML
                    InputStreamReader reader = new InputStreamReader(exchange.getRequestBody(), "UTF-8");
                    StringBuilder xmlBuilder = new StringBuilder();
                    char[] buffer = new char[1024];
                    int length;
                    while ((length = reader.read(buffer)) != -1) {
                        xmlBuilder.append(buffer, 0, length);
                    }
                    String xmlInput = xmlBuilder.toString();

                    // Parser avec jackson

                    // Convertir en liste d'entiers
                    int[] values = new int[10];
                    for (int i = 0; i < 10; i++) {
                        values[i] = valuesNode.get(i).asInt();
                    }

                    String[] labels = //... à compléter pour les étiquettes

                    // Générer le SVG
                    String svgContent = generateSVG(values, labels);

                    // Créer la réponse JSON
                    JsonObject responseJson = new JsonObject();
                    responseJson.addProperty("svg", svgContent);
                    String jsonResponse = gson.toJson(responseJson);

                    exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
                    exchange.sendResponseHeaders(200, jsonResponse.getBytes("UTF-8").length);
                    try (OutputStream os = exchange.getResponseBody()) {
                        os.write(jsonResponse.getBytes("UTF-8"));
                    }

                } catch (Exception e) {
                    exchange.sendResponseHeaders(500, -1);
                }
            } else if ("OPTIONS".equals(exchange.getRequestMethod())) {
                exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
                exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "POST, OPTIONS");
                exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type");
                exchange.sendResponseHeaders(200, -1);
            } else {
                exchange.sendResponseHeaders(405, -1);
            }
        });

        // Endpoint pour retourner le SVG pur
        server.createContext("/api/svg", exchange -> {
            if ("POST".equals(exchange.getRequestMethod())) {
                try {
                    // Lire les données XML (même logique que /api/graph)
                    InputStreamReader reader = new InputStreamReader(exchange.getRequestBody(), "UTF-8");
                    StringBuilder xmlBuilder = new StringBuilder();
                    char[] buffer = new char[1024];
                    int length;
                    while ((length = reader.read(buffer)) != -1) {
                        xmlBuilder.append(buffer, 0, length);
                    }
                    String xmlInput = xmlBuilder.toString();

                    // Parser avec jackson (à compléter)

                    // Convertir en liste d'entiers
                    int[] values = new int[10];
                    for (int i = 0; i < 10; i++) {
                        values[i] = valuesNode.get(i).asInt();
                    }

                    String[] labels = //... à compléter pour les étiquettes

                    // Générer le SVG
                    String svgContent = generateSVG(values, labels);

                    // Retourner le SVG directement
                    exchange.getResponseHeaders().set("Content-Type", "image/svg+xml; charset=UTF-8");
                    exchange.sendResponseHeaders(200, svgContent.getBytes("UTF-8").length);
                    try (OutputStream os = exchange.getResponseBody()) {
                        os.write(svgContent.getBytes("UTF-8"));
                    }

                } catch (Exception e) {
                    exchange.sendResponseHeaders(500, -1);
                }
            } else if ("OPTIONS".equals(exchange.getRequestMethod())) {
                exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
                exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "POST, OPTIONS");
                exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type");
                exchange.sendResponseHeaders(200, -1);
            } else {
                exchange.sendResponseHeaders(405, -1);
            }
        });

        server.setExecutor(null);
        server.start();
        System.out.println("Serveur de graphiques démarré sur http://localhost:8080/");
    }

    private static String generateSVG(int[] values, String[] labels) {
    //...
    }

    private static String generateHTMLResponse(String svgContent) {
        //...
    }
}

Le SVG doit utiliser des styles CSS appropriés. Pour les barres, vous emploierez un dégradé linéaire linearGradient. Les animations utiliseront les propriétés transition et transform au survol. Les couleurs devront être cohérentes, idéalement avec des variables CSS. La typographie sera soignée avec des polices et tailles appropriées pour les étiquettes. Pour l’accessibilité, n’oubliez pas d’ajouter des éléments <title> pour les infobulles.

Types MIME pour les réponses du serveur #

Lors de la création des endpoints du serveur, il est essentiel de définir correctement les en-têtes Content-Type pour indiquer le type de contenu retourné. Voici les types MIME appropriés pour chaque format :

  • SVG : Utilisez image/svg+xml pour indiquer que la réponse contient du SVG. Exemple en Java :

    exchange.getResponseHeaders().set("Content-Type", "image/svg+xml; charset=UTF-8");
    
  • JSON : Utilisez application/json pour les données JSON. Exemple en Java :

    exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
    
  • Open XML (Excel) : Pour un fichier .xlsx, utilisez application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. Pour permettre le téléchargement, ajoutez également un en-tête Content-Disposition. Exemple :

    exchange.getResponseHeaders().set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    exchange.getResponseHeaders().set("Content-Disposition", "attachment; filename=\"data.xlsx\"");
    
  • Markdown : Utilisez text/plain. Exemple :

    exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
    

Ces en-têtes permettent aux navigateurs et clients de traiter correctement les réponses, que ce soit pour l’affichage inline (SVG), le téléchargement (Excel) ou le rendu textuel (Markdown).

À remettre #

Vous devrez vérifier que exactement 10 valeurs sont fournies dans la requête. Vous devez vérifier que les étiquettes sont présentes. La gestion des erreurs devra être appropriée, avec des messages d’erreur clairs en cas de données invalides.

La page web doit transmettre du XML. Elle doit recevoir du JSON de la part du serveur. Jackson devra être utilisé de manière appropriée pour le XML et Gson pour le JSON.

Vous devrez remettre le code source Java complet avec des commentaires explicatifs, incluant la génération du SVG, du document Excel Open XML et de la transformation XSLT vers Markdown. La page HTML avec le formulaire et le JavaScript devra également être fournie. Une documentation expliquant l’architecture et les technologies utilisées sera nécessaire. Des exemples de données d’entrée et de résultats de sortie (SVG, Excel et Markdown) devront être inclus. Enfin, des tests montrant le fonctionnement avec différentes valeurs devront être présentés.