From 5165735b356523e37189647256348cddbddcd390 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Tue, 20 Feb 2024 17:55:46 +0100 Subject: [PATCH 1/5] Ajouter la table simulation. refs #47 --- sql/init_data.h2.sql | 5 ++ sql/migration.sql | 11 ++++ sql/schema.tables.sql | 10 ++++ .../www/server/dao/SimulationDao.java | 17 ++++++ .../server/dao/SimulationDaoHibernate.java | 29 ++++++++++ .../www/server/model/DailyValue.java | 2 +- .../www/server/model/Simulation.java | 55 +++++++++++++++++++ .../dao/SimulationDaoHibernateTest.java | 27 +++++++++ .../test/resources/META-INF/persistence.xml | 1 + 9 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java create mode 100644 www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java create mode 100644 www-server/src/main/java/fr/agrometinfo/www/server/model/Simulation.java create mode 100644 www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java diff --git a/sql/init_data.h2.sql b/sql/init_data.h2.sql index 6869996..6f76bd4 100644 --- a/sql/init_data.h2.sql +++ b/sql/init_data.h2.sql @@ -115,3 +115,8 @@ INSERT INTO normalvalue (indicator, cell, doy, computedvalue) JOIN indicator AS i ON i.code=t.indicator JOIN period AS p ON p.id=i.period WHERE p.code=t.period; + +-- simulation +INSERT INTO simulation (date, simulationid, started, ended) VALUES + ('2024-02-19', 1, '2024-02-20 12:00:00', '2024-02-20 12:30:00'), + ('2024-02-20', 2, '2024-02-21 13:00:00', NULL); \ No newline at end of file diff --git a/sql/migration.sql b/sql/migration.sql index 761d96d..e92b32b 100644 --- a/sql/migration.sql +++ b/sql/migration.sql @@ -144,6 +144,17 @@ END $BODY$ language plpgsql; +-- +-- 47 +-- +CREATE OR REPLACE FUNCTION upgrade20240220() RETURNS boolean AS $BODY$ +BEGIN + INSERT INTO simulation (date, simulationid, started, ended) VALUES + ('2024-02-19', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), + RETURN true; +END +$BODY$ +language plpgsql; --- -- -- Keep this call at the end to apply migration functions. diff --git a/sql/schema.tables.sql b/sql/schema.tables.sql index 1c1fdd9..ee92d5e 100644 --- a/sql/schema.tables.sql +++ b/sql/schema.tables.sql @@ -1,6 +1,16 @@ -- Schema for AgroMetInfo database -- MUST be compatible with H2 and PostgreSQL +CREATE TABLE IF NOT EXISTS simulation ( + id SERIAL, + date DATE NOT NULL, + simulationid INTEGER NOT NULL, + started TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + ended TIMESTAMP, + CONSTRAINT "PK_simulation" PRIMARY KEY (id) +); +COMMENT ON TABLE simulation IS 'Simulation run to produce the values.'; + CREATE TABLE IF NOT EXISTS locale ( id SERIAL, languagetag VARCHAR(20) NOT NULL, diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java new file mode 100644 index 0000000..9f8492f --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java @@ -0,0 +1,17 @@ +package fr.agrometinfo.www.server.dao; + +import java.time.LocalDateTime; + +import fr.agrometinfo.www.server.model.Simulation; + +/** + * DAO for {@link Simulation}. + * + * @author Olivier Maury + */ +public interface SimulationDao { + /** + * @return date of last simulation successfully run + */ + LocalDateTime findLastSimulationEnd(); +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java new file mode 100644 index 0000000..25733ff --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java @@ -0,0 +1,29 @@ +package fr.agrometinfo.www.server.dao; + +import java.time.LocalDateTime; + +import fr.agrometinfo.www.server.model.Simulation; +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Hibernate implementation of {@link SimulationDao}. + * + * @author Olivier Maury + */ +@ApplicationScoped +public class SimulationDaoHibernate extends DaoHibernate<Simulation> implements SimulationDao { + + /** + * Constructor. + */ + public SimulationDaoHibernate() { + super(Simulation.class); + } + + @Override + public final LocalDateTime findLastSimulationEnd() { + final var jpql = "SELECT MAX(t.ended) FROM Simulation t WHERE t.ended IS NOT NULL"; + return super.findOneByJPQL(jpql, null, LocalDateTime.class); + } + +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyValue.java b/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyValue.java index 650b655..3f8cf9c 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyValue.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyValue.java @@ -49,7 +49,7 @@ public class DailyValue { @Column(name = "date", nullable = false) private final LocalDate date = LocalDate.now(); /** - * ID: SAFRAN cell number. + * PK. */ @Id @GeneratedValue(strategy = GenerationType.AUTO) diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/model/Simulation.java b/www-server/src/main/java/fr/agrometinfo/www/server/model/Simulation.java new file mode 100644 index 0000000..f3cb4e0 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/model/Simulation.java @@ -0,0 +1,55 @@ +package fr.agrometinfo.www.server.model; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +/** + * Simulation run to produce the values. + * + * @author Olivier Maury + */ +@Data +@Entity +@Table(name = "simulation") +public class Simulation { + /** + * Simulation start. + */ + @Column(name = "started", nullable = false) + private LocalDateTime created; + + /** + * Simulated date. + */ + @Column(name = "date", nullable = false) + private LocalDate date; + + /** + * Simulation end. + */ + @Column(name = "ended", nullable = true) + private LocalDateTime ended; + + /** + * PK. + */ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private long id; + + /** + * Simulation ID. + */ + @Column(name = "simulationid") + private long simulationId; + +} diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java new file mode 100644 index 0000000..d956079 --- /dev/null +++ b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java @@ -0,0 +1,27 @@ +package fr.agrometinfo.www.server.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Test; + +/** + * Test SimulationDao Hibernate implementation. + */ +class SimulationDaoHibernateTest { + /** + * DAO to test. + */ + private final SimulationDao dao = new SimulationDaoHibernate(); + + /** + * Ensure reading is OK. + */ + @Test + void findLastSimulationEnd() { + final var actual = dao.findLastSimulationEnd(); + final var expected = LocalDateTime.parse("2024-02-20T12:30:00"); + assertEquals(expected, actual); + } +} diff --git a/www-server/src/test/resources/META-INF/persistence.xml b/www-server/src/test/resources/META-INF/persistence.xml index 1de816e..74353e4 100644 --- a/www-server/src/test/resources/META-INF/persistence.xml +++ b/www-server/src/test/resources/META-INF/persistence.xml @@ -16,6 +16,7 @@ <class>fr.agrometinfo.www.server.model.Pra</class> <class>fr.agrometinfo.www.server.model.PraDailyValue</class> <class>fr.agrometinfo.www.server.model.Region</class> + <class>fr.agrometinfo.www.server.model.Simulation</class> <properties> <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:agrometinfo;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM '../sql/schema.types.h2.sql'\;RUNSCRIPT FROM '../sql/schema.tables.sql'\;RUNSCRIPT FROM '../sql/init_data.h2.sql';" /> <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" /> -- GitLab From 6421b2aff1440e8946b9e61a9be6a127902ead55 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Wed, 21 Feb 2024 11:21:40 +0100 Subject: [PATCH 2/5] Ajouter SimulationDao.findLastSimulatedDate(). refs #47 --- .../fr/agrometinfo/www/server/dao/SimulationDao.java | 6 ++++++ .../www/server/dao/SimulationDaoHibernate.java | 7 +++++++ .../www/server/dao/SimulationDaoHibernateTest.java | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java index 9f8492f..b1dfc6c 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDao.java @@ -1,5 +1,6 @@ package fr.agrometinfo.www.server.dao; +import java.time.LocalDate; import java.time.LocalDateTime; import fr.agrometinfo.www.server.model.Simulation; @@ -10,6 +11,11 @@ import fr.agrometinfo.www.server.model.Simulation; * @author Olivier Maury */ public interface SimulationDao { + /** + * @return the last simulated date + */ + LocalDate findLastSimulatedDate(); + /** * @return date of last simulation successfully run */ diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java index 25733ff..9b8c8c6 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java @@ -1,5 +1,6 @@ package fr.agrometinfo.www.server.dao; +import java.time.LocalDate; import java.time.LocalDateTime; import fr.agrometinfo.www.server.model.Simulation; @@ -20,6 +21,12 @@ public class SimulationDaoHibernate extends DaoHibernate<Simulation> implements super(Simulation.class); } + @Override + public LocalDate findLastSimulatedDate() { + final var jpql = "SELECT MAX(t.date) FROM Simulation t WHERE t.ended IS NOT NULL"; + return super.findOneByJPQL(jpql, null, LocalDate.class); + } + @Override public final LocalDateTime findLastSimulationEnd() { final var jpql = "SELECT MAX(t.ended) FROM Simulation t WHERE t.ended IS NOT NULL"; diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java index d956079..bca9611 100644 --- a/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java +++ b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java @@ -2,6 +2,7 @@ package fr.agrometinfo.www.server.dao; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.LocalDate; import java.time.LocalDateTime; import org.junit.jupiter.api.Test; @@ -15,6 +16,16 @@ class SimulationDaoHibernateTest { */ private final SimulationDao dao = new SimulationDaoHibernate(); + /** + * Ensure reading is OK. + */ + @Test + void findLastSimulatedDate() { + final var actual = dao.findLastSimulatedDate(); + final var expected = LocalDate.parse("2024-02-19"); + assertEquals(expected, actual); + } + /** * Ensure reading is OK. */ -- GitLab From 9bc754c2e2319cea7192da1b351b0132307def3f Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Wed, 21 Feb 2024 17:03:56 +0100 Subject: [PATCH 3/5] =?UTF-8?q?Mettre=20en=20cache=20les=20r=C3=A9ponses?= =?UTF-8?q?=20des=20web=20services.=20refs=20#47?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/migration.sql | 2 +- src/site/markdown/development.md | 9 + www-server/pom.xml | 12 ++ .../www/server/AgroMetInfoConfiguration.java | 23 ++ .../server/dao/SimulationDaoHibernate.java | 6 +- .../www/server/rs/IndicatorResource.java | 116 +++++++++- .../www/server/service/CacheService.java | 200 ++++++++++++++++++ www-server/src/main/resources/log4j2.xml | 1 + www-server/src/main/tomcat10xconf/context.xml | 1 + .../agrometinfo/www/shared/dto/ChoiceDTO.java | 8 +- .../www/shared/dto/IndicatorDTO.java | 8 +- .../agrometinfo/www/shared/dto/PeriodDTO.java | 4 + .../www/shared/dto/SimpleFeature.java | 8 +- .../www/shared/dto/SummaryDTO.java | 7 +- .../src/main/java/org/geojson/Feature.java | 4 + .../java/org/geojson/FeatureCollection.java | 5 + .../main/java/org/geojson/GeoJsonObject.java | 8 +- .../src/main/java/org/geojson/LngLatAlt.java | 8 +- .../main/java/org/geojson/MultiPolygon.java | 5 + .../src/main/java/org/geojson/Polygon.java | 5 + 20 files changed, 421 insertions(+), 19 deletions(-) create mode 100644 www-server/src/main/java/fr/agrometinfo/www/server/service/CacheService.java diff --git a/sql/migration.sql b/sql/migration.sql index e92b32b..9406e3a 100644 --- a/sql/migration.sql +++ b/sql/migration.sql @@ -150,7 +150,7 @@ language plpgsql; CREATE OR REPLACE FUNCTION upgrade20240220() RETURNS boolean AS $BODY$ BEGIN INSERT INTO simulation (date, simulationid, started, ended) VALUES - ('2024-02-19', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), + ('2024-02-19', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); RETURN true; END $BODY$ diff --git a/src/site/markdown/development.md b/src/site/markdown/development.md index f9fc082..e243a0c 100644 --- a/src/site/markdown/development.md +++ b/src/site/markdown/development.md @@ -57,6 +57,15 @@ To package sources, you must have: validationQuery="select 1" /> ``` +Ensure JVM args contains in the server launch configuration: + +``` +--add-opens=java.base/java.math=ALL-UNNAMED +--add-opens=java.base/java.net=ALL-UNNAMED +--add-opens=java.base/java.text=ALL-UNNAMED +--add-opens=java.sql/java.sql=ALL-UNNAMED +``` + If CodeServer fails to launch in Eclipse, use the script ``` diff --git a/www-server/pom.xml b/www-server/pom.xml index e622b46..cfeac5b 100644 --- a/www-server/pom.xml +++ b/www-server/pom.xml @@ -155,6 +155,18 @@ <artifactId>postgresql</artifactId> <version>42.7.1</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.0</version> + </dependency> + <dependency> + <groupId>de.ruedigermoeller</groupId> + <artifactId>fst</artifactId> + <version>3.0.4-jdk17</version> + </dependency> <!-- SAVA --> <dependency> <groupId>fr.inrae.agroclim</groupId> diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java index 217c016..78b496f 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java @@ -1,12 +1,15 @@ package fr.agrometinfo.www.server; +import java.io.File; import java.util.EnumMap; import java.util.Map; import java.util.Objects; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.servlet.ServletContext; import lombok.Getter; import lombok.NonNull; @@ -28,6 +31,10 @@ public class AgroMetInfoConfiguration { * The application URL. */ APP_URL("app.url"), + /** + * Cache directory path. + */ + CACHE_DIRECTORY("cache.directory"), /** * Target environment (dev, preprod, prod). */ @@ -99,6 +106,15 @@ public class AgroMetInfoConfiguration { return values.get(key); } + /** + * @return cache directory path + */ + @Named("cacheDirectory") + @Produces + public String getCacheDirectory() { + return get(ConfigurationKey.CACHE_DIRECTORY); + } + /** * Initialize configuration from context.xml. */ @@ -112,5 +128,12 @@ public class AgroMetInfoConfiguration { Objects.requireNonNull(value, "Key " + strKey + " must have value in context.xml"); values.put(key, value); } + final File dir = new File(getCacheDirectory()); + if (!dir.exists()) { + LOGGER.info("Creating directory {}", dir.getAbsolutePath()); + if (!dir.mkdirs()) { + LOGGER.fatal("Cache directory {} failed to create!", dir.getAbsolutePath()); + } + } } } diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java index 9b8c8c6..d9d748b 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernate.java @@ -5,6 +5,8 @@ import java.time.LocalDateTime; import fr.agrometinfo.www.server.model.Simulation; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; /** * Hibernate implementation of {@link SimulationDao}. @@ -22,11 +24,13 @@ public class SimulationDaoHibernate extends DaoHibernate<Simulation> implements } @Override - public LocalDate findLastSimulatedDate() { + public final LocalDate findLastSimulatedDate() { final var jpql = "SELECT MAX(t.date) FROM Simulation t WHERE t.ended IS NOT NULL"; return super.findOneByJPQL(jpql, null, LocalDate.class); } + @Named("lastModification") + @Produces @Override public final LocalDateTime findLastSimulationEnd() { final var jpql = "SELECT MAX(t.ended) FROM Simulation t WHERE t.ended IS NOT NULL"; diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java index 706665d..33055df 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java @@ -16,17 +16,20 @@ import java.util.TreeMap; import org.geojson.Feature; import org.geojson.FeatureCollection; +import fr.agrometinfo.www.server.AgroMetInfoConfiguration; import fr.agrometinfo.www.server.dao.CellDao; import fr.agrometinfo.www.server.dao.IndicatorDao; import fr.agrometinfo.www.server.dao.MonthlyValueDao; import fr.agrometinfo.www.server.dao.PraDailyValueDao; import fr.agrometinfo.www.server.dao.PraDao; import fr.agrometinfo.www.server.dao.RegionDao; +import fr.agrometinfo.www.server.dao.SimulationDao; import fr.agrometinfo.www.server.model.Indicator; import fr.agrometinfo.www.server.model.MonthlyValue; import fr.agrometinfo.www.server.model.Pra; import fr.agrometinfo.www.server.model.PraDailyValue; import fr.agrometinfo.www.server.model.Region; +import fr.agrometinfo.www.server.service.CacheService; import fr.agrometinfo.www.server.util.DateUtils; import fr.agrometinfo.www.server.util.LocaleUtils; import fr.agrometinfo.www.shared.dto.ChoiceDTO; @@ -37,7 +40,6 @@ import fr.agrometinfo.www.shared.dto.PeriodDTO; import fr.agrometinfo.www.shared.dto.SimpleFeature; import fr.agrometinfo.www.shared.dto.SummaryDTO; import fr.agrometinfo.www.shared.service.IndicatorService; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; @@ -47,7 +49,9 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; import jakarta.ws.rs.core.Response; import lombok.extern.log4j.Log4j2; @@ -112,6 +116,12 @@ public class IndicatorResource implements IndicatorService { return feature; } + /** + * Cache service for server-side and browser-side. + */ + @Inject + private CacheService cacheService; + /** * DAO for cells. */ @@ -154,6 +164,30 @@ public class IndicatorResource implements IndicatorService { @Inject private RegionDao regionDao; + /** + * JAX-RS request. + */ + @Context + private Request request; + + /** + * HTTP headers for response. + */ + @Context + private HttpHeaders httpHeaders; + + /** + * Application configuration. + */ + @Inject + private AgroMetInfoConfiguration configuration; + + /** + * Dao for Simulation. + */ + @Inject + private SimulationDao simulationDao; + /** * Ensure the value of query parameter is not null and not blank. * @@ -164,7 +198,7 @@ public class IndicatorResource implements IndicatorService { private void checkRequired(final Object value, final String queryParamName) { if (value instanceof final String str && str.isBlank() || value == null) { final var status = Response.Status.BAD_REQUEST; - throw new WebApplicationException( + throw new WebApplicationException(// Response.status(status) // .entity(ErrorResponseDTO.of(status.getStatusCode(), // status.getReasonPhrase(), // @@ -176,14 +210,25 @@ public class IndicatorResource implements IndicatorService { /** * @return indicator categories with their indicators */ + @SuppressWarnings("unchecked") @GET @Path(IndicatorService.PATH_LIST) @Produces(MediaType.APPLICATION_JSON) @Override public List<PeriodDTO> getPeriods() { - // TODO : ajouter un cache (CacheControl, E-Tag et WebFilter) LOGGER.traceEntry(); final var locale = LocaleUtils.getLocale(httpServletRequest); + // HTTP cache headers + cacheService.setCacheKey(IndicatorService.PATH, IndicatorService.PATH_LIST, locale); + cacheService.setHeaders(httpHeaders); + if (!cacheService.needsResponse(request)) { + return List.of(); + } + // cached response + if (cacheService.isCached()) { + return (List<PeriodDTO>) cacheService.getCache(); + } + // final var indicators = praDailyValueDao.findIndicators(); final Map<Long, PeriodDTO> dtos = new LinkedHashMap<>(); for (final Indicator indicator : indicators) { @@ -205,18 +250,33 @@ public class IndicatorResource implements IndicatorService { } final List<PeriodDTO> periods = new ArrayList<>(dtos.values()); Collections.sort(periods, (o1, o2) -> o1.getDescription().compareTo(o2.getDescription())); + cacheService.setCache(periods); return periods; } + @SuppressWarnings("unchecked") @GET @Path(IndicatorService.PATH_REGIONS) @Produces(MediaType.APPLICATION_JSON) @Override public Map<String, String> getRegions() { - return regionDao.findAll().stream()// + // HTTP cache headers + cacheService.setCacheKey(IndicatorService.PATH, IndicatorService.PATH_REGIONS); + cacheService.setHeaders(httpHeaders); + if (!cacheService.needsResponse(request)) { + return Map.of(); + } + // cached response + if (cacheService.isCached()) { + return (Map<String, String>) cacheService.getCache(); + } + // + final Map<String, String> result = regionDao.findAll().stream()// .collect(LinkedHashMap::new, // (map, item) -> map.put(String.valueOf(item.getId()), item.getName()), // Map::putAll); + cacheService.setCache(result); + return result; } @GET @@ -230,8 +290,19 @@ public class IndicatorResource implements IndicatorService { checkRequired(indicatorUid, "indicator"); checkRequired(periodCode, "period"); checkRequired(year, "year"); - final var locale = LocaleUtils.getLocale(httpServletRequest); + // HTTP cache headers + cacheService.setCacheKey(IndicatorService.PATH, IndicatorService.PATH_SUMMARY, locale, indicatorUid, periodCode, + level, id, year); + cacheService.setHeaders(httpHeaders); + if (!cacheService.needsResponse(request)) { + return null; + } + // cached response + if (cacheService.isCached()) { + return (SummaryDTO) cacheService.getCache(); + } + // final var indicator = indicatorDao.findByCodeAndPeriod(indicatorUid, periodCode); if (indicator == null) { final var status = Response.Status.BAD_REQUEST; @@ -322,6 +393,7 @@ public class IndicatorResource implements IndicatorService { dto.setMonthlyValues(monthlyValues); dto.setParentFeature(parentFeature); dto.setPeriod(getTranslation(indicator.getPeriod().getNames(), locale)); + cacheService.setCache(dto); return dto; } @@ -336,6 +408,18 @@ public class IndicatorResource implements IndicatorService { checkRequired(indicatorUid, "indicator"); checkRequired(periodCode, "period"); checkRequired(year, "year"); + // HTTP cache headers + cacheService.setCacheKey(IndicatorService.PATH, IndicatorService.PATH_VALUES, indicatorUid, periodCode, + regionId, year, comparison); + cacheService.setHeaders(httpHeaders); + if (!cacheService.needsResponse(request)) { + return null; + } + // cached response + if (cacheService.isCached()) { + return (FeatureCollection) cacheService.getCache(); + } + // final FeatureCollection collection = new FeatureCollection(); final Indicator indicator = indicatorDao.findByCodeAndPeriod(indicatorUid, periodCode); final LocalDate date = praDailyValueDao.findLastDate(indicator, year); @@ -361,20 +445,30 @@ public class IndicatorResource implements IndicatorService { .map(IndicatorResource::toFeatureWithComparedValue) // .forEach(collection::add); } + cacheService.setCache(collection); return collection; } + @SuppressWarnings("unchecked") @GET @Path(IndicatorService.PATH_YEARS) @Produces(MediaType.APPLICATION_JSON) @Override public List<Integer> getYears() { - return praDailyValueDao.findYears(); - } - - @PostConstruct - public void init() { - LOGGER.traceEntry(); + // HTTP cache headers + cacheService.setCacheKey(IndicatorService.PATH, IndicatorService.PATH_YEARS); + cacheService.setHeaders(httpHeaders); + if (!cacheService.needsResponse(request)) { + return List.of(); + } + // cached response + if (cacheService.isCached()) { + return (List<Integer>) cacheService.getCache(); + } + // + final List<Integer> result = praDailyValueDao.findYears(); + cacheService.setCache(result); + return result; } private Map<Date, Float> toMonthlyValues(final List<MonthlyValue> values) { diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/service/CacheService.java b/www-server/src/main/java/fr/agrometinfo/www/server/service/CacheService.java new file mode 100644 index 0000000..8c038d9 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/service/CacheService.java @@ -0,0 +1,200 @@ +package fr.agrometinfo.www.server.service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.StringJoiner; + +import org.nustaq.serialization.FSTConfiguration; +import org.nustaq.serialization.FSTObjectInput; +import org.nustaq.serialization.FSTObjectOutput; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.ws.rs.core.CacheControl; +import jakarta.ws.rs.core.EntityTag; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.ext.RuntimeDelegate; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; + +/** + * Server-side cache service and HTTP headers managements for browser-side + * cache. + * + * @author Olivier Maury + */ +@RequestScoped +@Log4j2 +public class CacheService { + /** + * Config of FST. + * + * https://github.com/RuedigerMoeller/fast-serialization + * + * FST is 4x faster than JDK serialization for this data. + * + * FST is 5x faster than JDK deserialization for this data, so 3x faster than + * reading from remote database. + */ + private static final FSTConfiguration FSTCONF = FSTConfiguration.createDefaultConfiguration(); + + /** + * Number of milliseconds in a day. + */ + private static final long MILLISECONDS_IN_A_DAY = 60 * 60 * 24 * 1000; + + /** + * Cache directory path. + */ + @Setter + @Inject + @Named("cacheDirectory") + private String cacheDirectory; + + /** + * Date of last modification of indicators values in database. + */ + @Setter + @Inject + @Named("lastModification") + private LocalDateTime lastModification; + + /** + * Key related to object to cache. + */ + private String cacheKey; + + /** + * Cache retention in days. + */ + @Setter + private int nbOfDays = 1; + + /** + * @return object from cache or null. + */ + public Object getCache() { + final File cacheFile = getCacheFile(); + if (!cacheFile.exists()) { + return null; + } + try (FileInputStream fis = new FileInputStream(cacheFile); FSTObjectInput in = new FSTObjectInput(fis)) { + return in.readObject(); + } catch (final IOException | ClassNotFoundException e) { + LOGGER.fatal(e); + } + return null; + } + + /** + * Create the HTTP Cache-Control response header according to lastModification + * and 1 retention day. + * + * @return cache control + */ + private CacheControl getCacheControl() { + final CacheControl cc = new CacheControl(); + final LocalDateTime dayAfter = lastModification.plusDays(nbOfDays); + final Long maxAge = ChronoUnit.SECONDS.between(LocalDateTime.now(), dayAfter); + // max age = time inn seconds + cc.setMaxAge(maxAge.intValue()); + return cc; + } + + private File getCacheFile() { + return Paths.get(cacheDirectory, cacheKey).toFile(); + } + + /** + * Create the HTTP Entity Tag, used as the value of an ETag response header, + * according to cacheKey. + * + * @return Entity Tag + */ + private EntityTag getEtag() { + if (cacheKey == null) { + throw new IllegalStateException("cacheKey must be set before."); + } + return new EntityTag(Integer.toString(cacheKey.hashCode())); + } + + /** + * @return if cache key as related cache on disk and cache is up to date. + */ + public boolean isCached() { + final File cacheFile = getCacheFile(); + return cacheFile.exists() && new Date().getTime() - cacheFile.lastModified() < nbOfDays * MILLISECONDS_IN_A_DAY; + } + + /** + * @param request JAX-RS request + * @return if HTTP headers do not match current Entity Tag + */ + public boolean needsResponse(final Request request) { + if (request == null) { + LOGGER.error("Request must not be null!"); + return true; + } + return request.evaluatePreconditions(getEtag()) == null; + } + + /** + * Store object in cache. + * + * @param object object to cache + */ + public void setCache(final Object object) { + LOGGER.traceEntry("{}", object); + final File cacheFile = getCacheFile(); + LOGGER.info("file Path = {}", cacheFile); + try (FileOutputStream fos = new FileOutputStream(cacheFile); + FSTObjectOutput out = FSTCONF.getObjectOutput(fos)) { + out.writeObject(object); + out.flush(); + } catch (final IOException e) { + LOGGER.fatal(e); + } + LOGGER.traceExit("cached in {}", cacheFile); + + } + + /** + * @param objects objects to create a cache key + */ + public void setCacheKey(final Object... objects) { + final StringJoiner sj = new StringJoiner("-"); + for (final Object object : objects) { + if (object == null) { + sj.add("null"); + } else { + sj.add(object.toString()); + } + } + cacheKey = sj.toString(); + } + + /** + * Write HTTP headers to JAX-RS response. + * + * @param httpHeaders JAX-RS HTTP headers + */ + public void setHeaders(final HttpHeaders httpHeaders) { + if (httpHeaders == null) { + LOGGER.error("HttpHeaders must not be null!"); + return; + } + final var delegate = RuntimeDelegate.getInstance(); + httpHeaders.getRequestHeaders().putSingle("Cache-Control", + delegate.createHeaderDelegate(CacheControl.class).toString(getCacheControl())); + httpHeaders.getRequestHeaders().putSingle("ETag", + delegate.createHeaderDelegate(EntityTag.class).toString(getEtag())); + } +} diff --git a/www-server/src/main/resources/log4j2.xml b/www-server/src/main/resources/log4j2.xml index 8c0d772..8163370 100644 --- a/www-server/src/main/resources/log4j2.xml +++ b/www-server/src/main/resources/log4j2.xml @@ -28,6 +28,7 @@ <AppenderRef ref="console" level="trace" /> <AppenderRef ref="file" level="trace" /> </Root> + <Logger name="fr.agrometinfo" level="trace" /> <Logger name="org.hibernate" level="warn" /> <Logger name="org.jboss" level="warn" /> </Loggers> diff --git a/www-server/src/main/tomcat10xconf/context.xml b/www-server/src/main/tomcat10xconf/context.xml index 0125add..197593d 100644 --- a/www-server/src/main/tomcat10xconf/context.xml +++ b/www-server/src/main/tomcat10xconf/context.xml @@ -3,6 +3,7 @@ <Context path="/www-server" reloadable="true"> <Parameter name="agrometinfo.app.email" value="agrometinfoXXXX@inrae.fr" /> <Parameter name="agrometinfo.app.url" value="http://localhost:8080/www-server/" /> + <Parameter name="agrometinfo.cache.directory" value="/tmp/agrometinfo/" /> <Parameter name="agrometinfo.environment" value="dev" /> <!-- dev / preprod / prod --> <Parameter name="agrometinfo.log.email" value="agrometinfoXXXX@inrae.fr" /> <Parameter name="agrometinfo.smtp.host" value="smtp.inrae.fr" /> diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java index b35993c..b93db59 100644 --- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java @@ -1,5 +1,7 @@ package fr.agrometinfo.www.shared.dto; +import java.io.Serializable; + import org.dominokit.jackson.annotation.JSONMapper; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -10,7 +12,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * @author Olivier Maury */ @JSONMapper -public final class ChoiceDTO { +public final class ChoiceDTO implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585101L; /** * The user wants to compare with normal. diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java index ba05025..1fe02e5 100644 --- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java @@ -1,5 +1,7 @@ package fr.agrometinfo.www.shared.dto; +import java.io.Serializable; + import org.dominokit.jackson.annotation.JSONMapper; /** @@ -8,7 +10,11 @@ import org.dominokit.jackson.annotation.JSONMapper; * @author Olivier Maury */ @JSONMapper -public class IndicatorDTO { +public class IndicatorDTO implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585102L; /** * Localized description. */ diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java index 3f60f35..89af772 100644 --- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java @@ -11,6 +11,10 @@ import org.dominokit.jackson.annotation.JSONMapper; */ @JSONMapper public final class PeriodDTO extends IndicatorDTO { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585103L; /** * The indicators related to this period. */ diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SimpleFeature.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SimpleFeature.java index 0f7d4b3..054c139 100644 --- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SimpleFeature.java +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SimpleFeature.java @@ -1,11 +1,17 @@ package fr.agrometinfo.www.shared.dto; +import java.io.Serializable; + /** * A geographic object. * * @author Olivier Maury */ -public class SimpleFeature { +public class SimpleFeature implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585104L; /** * Unique identifier. */ diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SummaryDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SummaryDTO.java index e48ed9c..ebdb8de 100644 --- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SummaryDTO.java +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/SummaryDTO.java @@ -1,5 +1,6 @@ package fr.agrometinfo.www.shared.dto; +import java.io.Serializable; import java.util.Date; import java.util.Map; @@ -11,7 +12,11 @@ import org.dominokit.jackson.annotation.JSONMapper; * @author Olivier Maury */ @JSONMapper -public class SummaryDTO { +public class SummaryDTO implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585105L; /** * Average daily value of the indicator for the user choice. */ diff --git a/www-shared/src/main/java/org/geojson/Feature.java b/www-shared/src/main/java/org/geojson/Feature.java index 227de20..c0b0e3f 100644 --- a/www-shared/src/main/java/org/geojson/Feature.java +++ b/www-shared/src/main/java/org/geojson/Feature.java @@ -12,6 +12,10 @@ import org.dominokit.jackson.annotation.JSONMapper; */ @JSONMapper public final class Feature extends GeoJsonObject { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585201L; /** * Associated properties. diff --git a/www-shared/src/main/java/org/geojson/FeatureCollection.java b/www-shared/src/main/java/org/geojson/FeatureCollection.java index 702e0d5..8527be6 100644 --- a/www-shared/src/main/java/org/geojson/FeatureCollection.java +++ b/www-shared/src/main/java/org/geojson/FeatureCollection.java @@ -13,6 +13,11 @@ import org.dominokit.jackson.annotation.JSONMapper; */ @JSONMapper public final class FeatureCollection extends GeoJsonObject { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585202L; + /** * The Features within this FeatureCollection. */ diff --git a/www-shared/src/main/java/org/geojson/GeoJsonObject.java b/www-shared/src/main/java/org/geojson/GeoJsonObject.java index 288a6d2..8f4a8d5 100644 --- a/www-shared/src/main/java/org/geojson/GeoJsonObject.java +++ b/www-shared/src/main/java/org/geojson/GeoJsonObject.java @@ -1,11 +1,17 @@ package org.geojson; +import java.io.Serializable; + /** * Base class. * * @author Olivier Maury */ -public abstract class GeoJsonObject { +public abstract class GeoJsonObject implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585203L; /** * GeoJSON object type ("Feature", "Polygon", ...). */ diff --git a/www-shared/src/main/java/org/geojson/LngLatAlt.java b/www-shared/src/main/java/org/geojson/LngLatAlt.java index 64f4ac2..1fe9e5b 100644 --- a/www-shared/src/main/java/org/geojson/LngLatAlt.java +++ b/www-shared/src/main/java/org/geojson/LngLatAlt.java @@ -1,5 +1,7 @@ package org.geojson; +import java.io.Serializable; + import org.dominokit.jackson.annotation.JSONMapper; /** @@ -10,7 +12,11 @@ import org.dominokit.jackson.annotation.JSONMapper; * @author Olivier Maury */ @JSONMapper -public final class LngLatAlt { +public final class LngLatAlt implements Serializable { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585204L; /** * "X" according to the specification, longitude in a geographic CRS. diff --git a/www-shared/src/main/java/org/geojson/MultiPolygon.java b/www-shared/src/main/java/org/geojson/MultiPolygon.java index 5deb3b6..f442384 100644 --- a/www-shared/src/main/java/org/geojson/MultiPolygon.java +++ b/www-shared/src/main/java/org/geojson/MultiPolygon.java @@ -12,6 +12,11 @@ import org.dominokit.jackson.annotation.JSONMapper; */ @JSONMapper public class MultiPolygon extends GeoJsonObject { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585205L; + /** * GeoJSON object type. */ diff --git a/www-shared/src/main/java/org/geojson/Polygon.java b/www-shared/src/main/java/org/geojson/Polygon.java index 673f265..e2c73e5 100644 --- a/www-shared/src/main/java/org/geojson/Polygon.java +++ b/www-shared/src/main/java/org/geojson/Polygon.java @@ -13,6 +13,11 @@ import org.dominokit.jackson.annotation.JSONMapper; */ @JSONMapper public final class Polygon extends GeoJsonObject { + /** + * UID for Serializable. + */ + private static final long serialVersionUID = 5820780438006585206L; + /** * GeoJSON object type. */ -- GitLab From a544a37bf126ae3874f8c95c8a8bbaf59bc75cd9 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Wed, 21 Feb 2024 17:35:52 +0100 Subject: [PATCH 4/5] Tests --- www-server/pom.xml | 15 ++++++++ .../www/server/rs/IndicatorResource.java | 7 ---- .../dao/SimulationDaoHibernateTest.java | 9 +++-- .../www/server/rs/IndicatorResourceTest.java | 36 +++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/www-server/pom.xml b/www-server/pom.xml index cfeac5b..1369e52 100644 --- a/www-server/pom.xml +++ b/www-server/pom.xml @@ -242,6 +242,21 @@ </testResources> <pluginManagement> <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine> +--add-opens=java.base/java.lang=ALL-UNNAMED +--add-opens=java.base/java.math=ALL-UNNAMED +--add-opens=java.base/java.net=ALL-UNNAMED +--add-opens=java.base/java.text=ALL-UNNAMED +--add-opens=java.base/java.util=ALL-UNNAMED +--add-opens=java.base/java.util.concurrent=ALL-UNNAMED +--add-opens=java.sql/java.sql=ALL-UNNAMED + </argLine> + </configuration> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java index 33055df..ef39990 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java @@ -16,7 +16,6 @@ import java.util.TreeMap; import org.geojson.Feature; import org.geojson.FeatureCollection; -import fr.agrometinfo.www.server.AgroMetInfoConfiguration; import fr.agrometinfo.www.server.dao.CellDao; import fr.agrometinfo.www.server.dao.IndicatorDao; import fr.agrometinfo.www.server.dao.MonthlyValueDao; @@ -176,12 +175,6 @@ public class IndicatorResource implements IndicatorService { @Context private HttpHeaders httpHeaders; - /** - * Application configuration. - */ - @Inject - private AgroMetInfoConfiguration configuration; - /** * Dao for Simulation. */ diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java index bca9611..976a082 100644 --- a/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java +++ b/www-server/src/test/java/fr/agrometinfo/www/server/dao/SimulationDaoHibernateTest.java @@ -10,7 +10,12 @@ import org.junit.jupiter.api.Test; /** * Test SimulationDao Hibernate implementation. */ -class SimulationDaoHibernateTest { +public class SimulationDaoHibernateTest { + /** + * Last modification set in SQL. + */ + public static final LocalDateTime LAST_MODIFICATION = LocalDateTime.parse("2024-02-20T12:30:00"); + /** * DAO to test. */ @@ -32,7 +37,7 @@ class SimulationDaoHibernateTest { @Test void findLastSimulationEnd() { final var actual = dao.findLastSimulationEnd(); - final var expected = LocalDateTime.parse("2024-02-20T12:30:00"); + final var expected = LAST_MODIFICATION; assertEquals(expected, actual); } } diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java index 34ae4ec..05469d6 100644 --- a/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java +++ b/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java @@ -3,10 +3,18 @@ package fr.agrometinfo.www.server.rs; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + import org.geojson.FeatureCollection; import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import fr.agrometinfo.www.server.dao.CellDao; @@ -21,6 +29,10 @@ import fr.agrometinfo.www.server.dao.PraDao; import fr.agrometinfo.www.server.dao.PraDaoHibernate; import fr.agrometinfo.www.server.dao.RegionDao; import fr.agrometinfo.www.server.dao.RegionDaoHibernate; +import fr.agrometinfo.www.server.dao.SimulationDao; +import fr.agrometinfo.www.server.dao.SimulationDaoHibernate; +import fr.agrometinfo.www.server.dao.SimulationDaoHibernateTest; +import fr.agrometinfo.www.server.service.CacheService; import jakarta.ws.rs.core.Application; /** @@ -34,6 +46,24 @@ class IndicatorResourceTest extends JerseyTest { */ private static final String SEP = "/"; + /** + * Temporary directory for cache. + */ + private static Path cacheDir; + + @BeforeAll + static void createCacheDir() throws IOException { + cacheDir = Files.createTempDirectory(IndicatorResourceTest.class.getName()); + } + + @AfterAll + static void deleteCacheDir() throws IOException { + Files.walk(cacheDir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + @Override protected final Application configure() { final CellDao cellDao = new CellDaoHibernate(); @@ -42,6 +72,10 @@ class IndicatorResourceTest extends JerseyTest { final IndicatorDao indicatorDao = new IndicatorDaoHibernate(); final MonthlyValueDao monthlyValueDao = new MonthlyValueDaoHibernate(); final RegionDao regionDao = new RegionDaoHibernate(); + final SimulationDao simulationDao = new SimulationDaoHibernate(); + final CacheService cacheService = new CacheService(); + cacheService.setLastModification(SimulationDaoHibernateTest.LAST_MODIFICATION); + cacheService.setCacheDirectory(cacheDir.toString()); return new ResourceConfig(IndicatorResource.class).register(new AbstractBinder() { @Override public void configure() { @@ -51,6 +85,8 @@ class IndicatorResourceTest extends JerseyTest { bind(indicatorDao).to(IndicatorDao.class); bind(monthlyValueDao).to(MonthlyValueDao.class); bind(regionDao).to(RegionDao.class); + bind(simulationDao).to(SimulationDao.class); + bind(cacheService).to(CacheService.class); } }); } -- GitLab From 7d56604ac340f65cda91e95d3d52e0fe4cf66ef0 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Wed, 21 Feb 2024 17:47:34 +0100 Subject: [PATCH 5/5] Documentation --- src/site/markdown/development.md | 3 +++ src/site/markdown/installation.md | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/site/markdown/development.md b/src/site/markdown/development.md index e243a0c..aecd8c1 100644 --- a/src/site/markdown/development.md +++ b/src/site/markdown/development.md @@ -60,9 +60,12 @@ To package sources, you must have: Ensure JVM args contains in the server launch configuration: ``` +--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.math=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED +--add-opens=java.base/java.util=ALL-UNNAMED +--add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.sql/java.sql=ALL-UNNAMED ``` diff --git a/src/site/markdown/installation.md b/src/site/markdown/installation.md index 8cf50a6..2f772c7 100644 --- a/src/site/markdown/installation.md +++ b/src/site/markdown/installation.md @@ -64,3 +64,15 @@ Define credentials to deploy, change `conf/tomcat-users.xml` with <role rolename="manager-script"/> <user username="tomcat-password" password="tomcat" roles="tomcat,manager-gui,manager-script"/> ``` + +Ensure JVM args contains in the server launch configuration (in variable `JAVA_OPTS`) : + +``` +--add-opens=java.base/java.lang=ALL-UNNAMED +--add-opens=java.base/java.math=ALL-UNNAMED +--add-opens=java.base/java.net=ALL-UNNAMED +--add-opens=java.base/java.text=ALL-UNNAMED +--add-opens=java.base/java.util=ALL-UNNAMED +--add-opens=java.base/java.util.concurrent=ALL-UNNAMED +--add-opens=java.sql/java.sql=ALL-UNNAMED +``` \ No newline at end of file -- GitLab