From 22782bd4b8a0a6698aedc80d48c1e9b166ac0b8d Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 22 Apr 2024 17:12:09 +0200
Subject: [PATCH 1/2] Personnaliser les pages d'erreur. fixes #39

---
 pom.xml                                       |  2 +-
 www-server/pom.xml                            | 55 ++++++-----
 .../www/server/ErrorHandlerServlet.java       | 93 +++++++++++++++++++
 .../fr/agrometinfo/www/server/i18n.properties |  5 +
 .../agrometinfo/www/server/i18n_fr.properties |  5 +
 .../fr/agrometinfo/www/server/jsp.properties  |  9 ++
 .../agrometinfo/www/server/jsp_fr.properties  |  9 ++
 www-server/src/main/webapp/WEB-INF/error.jsp  | 12 +++
 .../src/main/webapp/WEB-INF/tags/page.tag     | 84 +++++++++++++++++
 www-server/src/main/webapp/WEB-INF/web.xml    |  3 +
 10 files changed, 254 insertions(+), 23 deletions(-)
 create mode 100644 www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java
 create mode 100644 www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties
 create mode 100644 www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties
 create mode 100644 www-server/src/main/webapp/WEB-INF/error.jsp
 create mode 100644 www-server/src/main/webapp/WEB-INF/tags/page.tag

diff --git a/pom.xml b/pom.xml
index 79ab203..1ef5fe5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,7 +91,7 @@
       <artifactId>junit-jupiter-params</artifactId>
       <version>${junit.version}</version>
       <scope>test</scope>
-	</dependency>
+    </dependency>
   </dependencies>
 
   <dependencyManagement>
diff --git a/www-server/pom.xml b/www-server/pom.xml
index 5f69e6e..a98d207 100644
--- a/www-server/pom.xml
+++ b/www-server/pom.xml
@@ -125,14 +125,14 @@
     </dependency>
     <!-- Jakarta mail -->
     <dependency>
-        <groupId>jakarta.mail</groupId>
-        <artifactId>jakarta.mail-api</artifactId>
-        <version>2.1.3</version>
+      <groupId>jakarta.mail</groupId>
+      <artifactId>jakarta.mail-api</artifactId>
+      <version>2.1.3</version>
     </dependency>
     <dependency>
-        <groupId>org.eclipse.angus</groupId>
-        <artifactId>jakarta.mail</artifactId>
-        <version>2.0.3</version>
+      <groupId>org.eclipse.angus</groupId>
+      <artifactId>jakarta.mail</artifactId>
+      <version>2.0.3</version>
     </dependency>
     <!-- JPA -->
     <dependency>
@@ -155,17 +155,28 @@
       <artifactId>postgresql</artifactId>
       <version>42.7.3</version>
     </dependency>
+    <!-- JSTL -->
+    <dependency>
+      <groupId>jakarta.servlet.jsp.jstl</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
+      <version>3.0.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl</artifactId>
+      <version>3.0.0</version>
+    </dependency>
     <!-- fast-serialization -->
     <!-- https://mvnrepository.com/artifact/de.ruedigermoeller/fst -->
     <dependency>
-        <groupId>com.fasterxml.jackson.core</groupId>
-        <artifactId>jackson-core</artifactId>
-        <version>2.16.1</version>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+      <version>2.16.1</version>
     </dependency>
     <dependency>
-        <groupId>de.ruedigermoeller</groupId>
-        <artifactId>fst</artifactId>
-        <version>3.0.4-jdk17</version>
+      <groupId>de.ruedigermoeller</groupId>
+      <artifactId>fst</artifactId>
+      <version>3.0.4-jdk17</version>
     </dependency>
     <!-- SAVA -->
     <dependency>
@@ -182,20 +193,20 @@
     </dependency>
     <!-- Tests for Jersey -->
     <dependency>
-        <groupId>org.glassfish.jersey.test-framework</groupId>
-        <artifactId>jersey-test-framework-core</artifactId>
-        <scope>test</scope>
+      <groupId>org.glassfish.jersey.test-framework</groupId>
+      <artifactId>jersey-test-framework-core</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
-        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
-        <scope>test</scope>
+      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-        <groupId>org.mockito</groupId>
-        <artifactId>mockito-core</artifactId>
-        <version>${mockito-core.version}</version>
-        <scope>test</scope>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>${mockito-core.version}</version>
+      <scope>test</scope>
     </dependency>
   </dependencies>
 
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java b/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java
new file mode 100644
index 0000000..90a9e3b
--- /dev/null
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java
@@ -0,0 +1,93 @@
+package fr.agrometinfo.www.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import fr.agrometinfo.www.server.AgroMetInfoConfiguration.ConfigurationKey;
+import fr.agrometinfo.www.server.util.LocaleUtils;
+import jakarta.inject.Inject;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet to display user friendly error messages.
+ *
+ * @author Olivier Maury
+ */
+@WebServlet(urlPatterns = "/errorHandler")
+public final class ErrorHandlerServlet extends HttpServlet {
+    /**
+     * UID.
+     */
+    private static final long serialVersionUID = 1003353995341179825L;
+
+    /**
+     * Application config.
+     */
+    @Inject
+    private AgroMetInfoConfiguration config;
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
+            throws IOException, ServletException {
+        final String appUrl = config.get(ConfigurationKey.APP_URL) + "../";
+        final Exception exception = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+
+        final Locale locale = LocaleUtils.getLocale(request);
+        final I18n i18n = new I18n("fr.agrometinfo.www.server.i18n", locale);
+        String body;
+        final String title = i18n.format(response.getStatus(), "http.status.title");
+        switch (response.getStatus()) {
+        case HttpServletResponse.SC_BAD_REQUEST:
+            body = "<p>La demande n'a pas pu être comprise par le serveur à cause d'une syntaxe mal formée.<br />"
+                    + "Vous NE DEVRIEZ PAS répéter la demande sans modifications.</p>";
+            break;
+        case HttpServletResponse.SC_NOT_FOUND:
+            body = "<p>La page que vous cherchez n'existe pas, mais vous n'êtes pas "
+                    + "pour autant dans une impasse. Voici quelques options disponibles :</p>" //
+                    + "<ul>"
+                    + "<li>Assurez-vous d'utiliser la bonne adresse URL.</li>" + "<li>Consultez <a href=\"" + appUrl
+                    + "\" target=\"_blank\">la documentation</a>.</li>"
+                    + "<li>Obtenez de l'aide en écrivant une demande de support dans l'application ou <a href=\""
+                    + appUrl + "/contact.html\">contactez-nous</a>.</li>" //
+                    + "</ul>";
+            break;
+        case HttpServletResponse.SC_INTERNAL_SERVER_ERROR:
+            body = "<p>Le serveur a rencontré une condition inattendue qui l'empêche de satisfaire la demande.</p>"
+                    + "<p><b>Erreur&nbsp;:</b> " + exception + "</p>" + "<p><b>Cause&nbsp;:</b> " + exception.getCause()
+                    + "</p>";
+            break;
+        case HttpServletResponse.SC_SERVICE_UNAVAILABLE:
+            body = "<p>Le serveur est incapable de traiter actuellement la demande à cause d'une surcharge temporaire "
+                    + "ou de la maintenance du serveur.<br />"
+                    + "Il s'agit d'une condition temporaire qui sera levée après un certain temps.</p>";
+            break;
+        default:
+            body = "<p><b>Erreur&nbsp;:</b> " + exception + "</p>";
+            if (exception != null) {
+                body += "<p><b>Message d'erreur&nbsp;:</b>" + exception.getMessage() + "</p>";
+            }
+            body += "<p><b>Status code:</b> " + response.getStatus() + "</p>";
+            body += "<p><b>Request URI:</b> " + request.getScheme() + "://" + request.getServerName()
+            + request.getRequestURI() + "</p>";
+            body += "<p><b>URL:</b> " + request.getRequestURI() + "</p>";
+            body += "<p><b>Context path:</b> " + getServletContext().getContextPath() + "</p>";
+            if (exception != null) {
+                body += "<p><b>Cause:</b> " + exception.getCause() + "</p>";
+            }
+            break;
+        }
+        request.setAttribute("appUrl", appUrl);
+        request.setAttribute("body", body);
+        request.setAttribute("locale", locale.getLanguage());
+        request.setAttribute("statusCode", response.getStatus());
+        request.setAttribute("title", title);
+
+        response.setContentType("text/html;charset=UTF-8");
+        getServletContext().getRequestDispatcher("/WEB-INF/error.jsp").forward(request, response);
+    }
+}
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
index e3e82d9..1f20f45 100644
--- a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
@@ -5,3 +5,8 @@ error.MAIL.SEND_FAILED=The e-mail "{0}" for the recipients {1} was not sent: {2}
 error.MAIL.SEND_FAILED_INVALID=the e-mail "{0}" was not sent to these invalid addresses: {1}.
 error.MAIL.SEND_FAILED_VALID=The e-mail "{0}" was not sent to these valid addresses: {1}.
 error.START=Start
+http.status.title=Error
+http.status.title[\=400]=Bad request
+http.status.title[\=404]=Document not found
+http.status.title[\=500]=Internal server error
+http.status.title[\=503]=Service unavailable
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
index ef082e9..b26453f 100644
--- a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
@@ -5,3 +5,8 @@ error.MAIL.SEND_FAILED=Le courriel « {0} » dont les destinataires sont {1} n'a
 error.MAIL.SEND_FAILED_INVALID=Le courriel « {0} » n'a pas été envoyé à ces adresses qui sont invalides : {1}.
 error.MAIL.SEND_FAILED_VALID=Le courriel « {0} » n'a pas été envoyé à ces adresses qui sont valides : {1}.
 error.START=Démarrage
+http.status.title=Erreur
+http.status.title[\=400]=Mauvaise demande
+http.status.title[\=404]=Document non trouvé
+http.status.title[\=500]=Erreur interne du serveur
+http.status.title[\=503]=Service indisponible
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties
new file mode 100644
index 0000000..dd42edf
--- /dev/null
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties
@@ -0,0 +1,9 @@
+# Ce fichier est encodé en UTF-8
+page.cookies=Cookies
+page.error=Error page
+page.legal-notice=Legal notice
+page.privacy=Privacy policy
+page.credits=Credits
+page.citation=Citations
+page.release-notes=Release notes
+page.contact=Contact us
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties
new file mode 100644
index 0000000..1092f58
--- /dev/null
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties
@@ -0,0 +1,9 @@
+# Ce fichier est encodé en UTF-8
+page.cookies=Cookies
+page.error=Page d'erreur
+page.legal-notice=Mentions légales
+page.privacy=Politique de confidentialité
+page.credits=Crédits
+page.citation=Citations
+page.release-notes=Notes de version
+page.contact=Contactez-nous
diff --git a/www-server/src/main/webapp/WEB-INF/error.jsp b/www-server/src/main/webapp/WEB-INF/error.jsp
new file mode 100644
index 0000000..c060ee8
--- /dev/null
+++ b/www-server/src/main/webapp/WEB-INF/error.jsp
@@ -0,0 +1,12 @@
+<%@page language="java" isErrorPage="true"
+        contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<fmt:setLocale value="${locale}" />
+<fmt:setBundle basename="fr.agrometinfo.www.server.jsp" />
+<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
+<t:page>
+        <jsp:attribute name="appUrl">${appUrl}</jsp:attribute>
+        <jsp:attribute name="locale">${locale}</jsp:attribute>
+        <jsp:attribute name="title"><fmt:message key="page.error" /> ${statusCode} − ${title}</jsp:attribute>
+        <jsp:body>${body}</jsp:body>
+</t:page>
diff --git a/www-server/src/main/webapp/WEB-INF/tags/page.tag b/www-server/src/main/webapp/WEB-INF/tags/page.tag
new file mode 100644
index 0000000..b54979f
--- /dev/null
+++ b/www-server/src/main/webapp/WEB-INF/tags/page.tag
@@ -0,0 +1,84 @@
+<%@tag description="Overall Page template" pageEncoding="UTF-8"%>
+<%@attribute name="appUrl" required="true" %>
+<%@attribute name="locale" required="true" %>
+<%@attribute name="title" required="true" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<fmt:setLocale value="${locale}" />
+<fmt:setBundle basename="fr.agrometinfo.www.server.jsp" />
+<!DOCTYPE html>
+<html lang="fr" dir="ltr">
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0">
+        <meta name="description" content="AgroMetInfo - mise à disposition d’indicateurs agroclimatiques et des indicateurs de suivi de culture d’hiver (culture type  blé tendre) et de printemps (culture type maïs) par le modèle STICS en temps réel sous forme de cartes et de graphiques.">
+        <link rel="license" href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/">
+        <link href="${appUrl}images/favicon.ico" rel="icon" type="image/x-icon" sizes="any">
+        <link href="${appUrl}css/fontawesome-all.min.css" rel="stylesheet" media="print" onload="this.media = 'all';
+                this.onload = null;">
+        <link href="${appUrl}css/home.css" rel="stylesheet">
+        <link href="${appUrl}css/fonts.css" rel="stylesheet" media="print" onload="this.media = 'all';
+                this.onload = null;">
+        <title>AgroMetInfo − ${title}</title>
+        <script>
+          const baseUriFull='${appUrl}';
+          const localStorageThemeKey = baseUriFull + 'variant';
+          function setTheme(theme) {
+            document.documentElement.setAttribute("data-theme", theme);
+            localStorage.setItem(localStorageThemeKey, "zen-" + theme);
+            var classes = "fas fa-toggle-on";
+            if (theme == "dark") {
+              classes = "fas fa-toggle-off";
+            }
+            var elem = document.getElementById("toggle-theme").className = classes;
+          }
+          function getTheme() {
+            var theme = "light";
+            if (localStorage.getItem(localStorageThemeKey)) {
+              if (localStorage.getItem(localStorageThemeKey) == "zen-dark") {
+                theme = "dark";
+              }
+            } else if (document.documentElement.getAttribute("data-theme")) {
+              if (document.documentElement.getAttribute("data-theme") == "dark") {
+                theme = "dark";
+              }
+            } else if(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
+              theme = "dark";
+            }
+            return theme;
+          }
+          function toggleTheme() {
+            const theme = getTheme();
+            if (theme === 'dark') {
+              setTheme('light');
+            } else {
+              setTheme('dark');
+            }
+          }
+          setTheme(getTheme());
+        </script>
+    </head>
+    <body>
+    	<nav>
+    		    <ul>
+    		    	<li class="brand">
+    		    		<a href="${appUrl}"><img alt="logo" src="${appUrl}images/logo_etat-agrometinfo.svg" /></a>
+  		    		</li>
+		    	</ul>
+    	</nav>
+        <article>
+            <jsp:doBody/>
+        </article>
+        <footer>
+            <ul>
+                <li><a href="${appUrl}cookies-use.html"><fmt:message key="page.cookies" /></a></li>
+                <li><a href="${appUrl}legal-notice.html"><fmt:message key="page.legal-notice" /></a></li>
+                <li><a href="${appUrl}privacy.html"><fmt:message key="page.privacy" /></a></li>
+                <li><a href="${appUrl}credits.html"><fmt:message key="page.credits" /></a></li>
+                <li><a href="${appUrl}citation.html"><fmt:message key="page.citation" /></a></li>
+                <li><a href="${appUrl}release-notes.html"><fmt:message key="page.release-notes" /></a></li>
+                <li><a href="${appUrl}contact.html"><fmt:message key="page.contact" /></a></li>
+            </ul>
+            <div class="license">Sauf mention contraire, tous les textes de ce site sont sous <a href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/" target="_blank">licence etalab-2.0</a>.</div>
+        </footer>
+    </body>
+</html>
diff --git a/www-server/src/main/webapp/WEB-INF/web.xml b/www-server/src/main/webapp/WEB-INF/web.xml
index aa82e1c..1687cf8 100644
--- a/www-server/src/main/webapp/WEB-INF/web.xml
+++ b/www-server/src/main/webapp/WEB-INF/web.xml
@@ -38,6 +38,9 @@
         <servlet-name>OpenApi</servlet-name>
         <url-pattern>/openapi/*</url-pattern>
     </servlet-mapping>
+    <error-page>
+        <location>/errorHandler</location>
+    </error-page>
     <!-- Default page to serve -->
     <welcome-file-list>
         <welcome-file>index.html</welcome-file>
-- 
GitLab


From 0e44c332a3cfb457a53e785d9ac491b8561fe84b Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 22 Apr 2024 17:13:16 +0200
Subject: [PATCH 2/2] Personnaliser les pages d'erreur. fixes #39

---
 pom.xml            | 2 +-
 www-client/pom.xml | 2 +-
 www-server/pom.xml | 2 +-
 www-shared/pom.xml | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1ef5fe5..838689e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>fr.agrometinfo</groupId>
   <artifactId>www</artifactId>
-  <version>2.0.0-alpha-2</version>
+  <version>2.0.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <name>AgroMetInfo web app</name>
   <description>Website for AgroMetInfo in Jakarta EE 10 and GWT.</description>
diff --git a/www-client/pom.xml b/www-client/pom.xml
index 384a0dc..f937fee 100644
--- a/www-client/pom.xml
+++ b/www-client/pom.xml
@@ -7,7 +7,7 @@
     <parent>
         <groupId>fr.agrometinfo</groupId>
         <artifactId>www</artifactId>
-        <version>2.0.0-alpha-2</version>
+        <version>2.0.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>www-client</artifactId>
diff --git a/www-server/pom.xml b/www-server/pom.xml
index a98d207..37ecee7 100644
--- a/www-server/pom.xml
+++ b/www-server/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>fr.agrometinfo</groupId>
     <artifactId>www</artifactId>
-    <version>2.0.0-alpha-2</version>
+    <version>2.0.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>www-server</artifactId>
diff --git a/www-shared/pom.xml b/www-shared/pom.xml
index 6d48e0d..205c0f1 100644
--- a/www-shared/pom.xml
+++ b/www-shared/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>fr.agrometinfo</groupId>
     <artifactId>www</artifactId>
-    <version>2.0.0-alpha-2</version>
+    <version>2.0.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>www-shared</artifactId>
-- 
GitLab