diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 55555ef68761fb4980cbebaad70284d496acc1c6..d9e817e76f8c4529d37bca4f59ae746c48be36fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,13 +1,9 @@ variables: MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository" +# To use the registry, DOCKER_AUTH_CONFIG must be set image: registry.forgemia.inra.fr/agroclim/common/docker-projets-java:latest -# hack to ignore failing runners -# https://forgemia.inra.fr/adminforgemia/support/-/issues/216 -default: - tags: ["test"] - stages: - build - test diff --git a/sql/schema.tables.sql b/sql/schema.tables.sql index 3df03ed88412159808abf3060ff9bb5a8e917418..85a7f2d37b9e00b9f6f62837cfb7a979fe4356a2 100644 --- a/sql/schema.tables.sql +++ b/sql/schema.tables.sql @@ -203,6 +203,16 @@ COMMENT ON COLUMN dailyvalue.computedvalue IS 'Value of the indicator.'; COMMENT ON COLUMN dailyvalue.comparedvalue IS 'Value of the indicator compared to the normal.'; CREATE INDEX IF NOT EXISTS "IX_dailyvalue" ON dailyvalue USING btree (date, cell); +CREATE TABLE IF NOT EXISTS dailyvisit( + id SERIAL, + date DATE NOT NULL, + environment VARCHAR NOT NULL, + number INTEGER NOT NULL, + CONSTRAINT "PK_dailyvisit" PRIMARY KEY (id), + CONSTRAINT "UK_dailyvisit" UNIQUE (date, environment) +); +COMMENT ON TABLE dailyvisit IS 'Number of visits per day.'; + CREATE OR REPLACE VIEW v_i18n AS SELECT l.languagetag, i.i18nkey, diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDao.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDao.java new file mode 100644 index 0000000000000000000000000000000000000000..f8709715009068f78b7b16193945536a6be63202 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDao.java @@ -0,0 +1,25 @@ +package fr.agrometinfo.www.server.dao; + +import java.util.List; + +import fr.agrometinfo.www.server.model.DailyVisit; + +/** + * DAO for dailyvisit table. + * + * @author Olivier Maury + */ +public interface DailyVisitDao { + /** + * @return all entities + */ + List<DailyVisit> findAll(); + + /** + * Increment the daily value if exists or add a new row with 1 as total number + * of visits. + * + * @param environment code + */ + void increment(String environment); +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernate.java new file mode 100644 index 0000000000000000000000000000000000000000..a24a802b55691e9b96ecbc1da318845dc959acc1 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernate.java @@ -0,0 +1,51 @@ +package fr.agrometinfo.www.server.dao; + +import fr.agrometinfo.www.server.model.DailyVisit; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; + +/** + * Hibernate implementation of {@link DailyVisit}. + * + * @author Olivier Maury + */ +@ApplicationScoped +public class DailyVisitDaoHibernate extends DaoHibernate<DailyVisit> implements DailyVisitDao { + /** + * Statement to insert a new row. + */ + private static final String INSERT_SQL = """ + INSERT INTO dailyvisit (date, environment, number) VALUES (CURRENT_DATE, :environment, 1) + """; + /** + * Statement to increment. + */ + private static final String UPDATE_SQL = """ + UPDATE dailyvisit SET number=number+1 WHERE date=CURRENT_DATE and environment=:environment + """; + + /** + * Constructor. + */ + public DailyVisitDaoHibernate() { + super(DailyVisit.class); + } + + @Transactional + @Override + public final void increment(final String environment) { + // INSERT INTO ... ON CONFLICT is not available in H2 + // MERGE is available in PostgreSQL >= 14 and H2 + doInJpaTransaction(em -> { + var query = em.createNativeQuery(UPDATE_SQL); + query.setParameter("environment", environment); + final int nb = query.executeUpdate(); + if (nb == 0) { + query = em.createNativeQuery(INSERT_SQL); + query.setParameter("environment", environment); + query.executeUpdate(); + } + }); + } + +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DaoHibernate.java index 2545bae33f3ef449b846defe0643c2933f919865..b9b9c20ad5c038bd46df971558c3d2c943b7fedc 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DaoHibernate.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DaoHibernate.java @@ -6,6 +6,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.hibernate.engine.spi.SessionImplementor; @@ -86,6 +87,19 @@ public abstract class DaoHibernate<T> { } } + /** + * Use a consumer to execute JPA operations in a transaction. + * + * @param consumer consumer with JPA operations + */ + protected final void doInJpaTransaction(final Consumer<ScopedEntityManager> consumer) { + try (ScopedEntityManager em = getScopedEntityManager()) { + em.executeTransaction(() -> { + consumer.accept(em); + }); + } + } + /** * Find by primary key. * diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyVisit.java b/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyVisit.java new file mode 100644 index 0000000000000000000000000000000000000000..80b3a47aea2e0be6fc275319e0c0054fd9770083 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/model/DailyVisit.java @@ -0,0 +1,44 @@ +package fr.agrometinfo.www.server.model; + +import java.time.LocalDate; + +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; + +/** + * Number of visits per day. + * + * @author Olivier Maury + */ +@Data +@Entity +@Table(name = "dailyvisit") +public class DailyVisit { + /** + * PK. + */ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private long id; + /** + * Date. + */ + @Column(name = "date", nullable = false) + private LocalDate date = LocalDate.now(); + /** + * Environment code. + */ + @Column(name = "environment") + private String environment; + /** + * Number of visits per day. + */ + @Column(name = "number") + private Integer number; +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java index 9718829424c558c858f230a98db06e41e0b2f0ba..8a7b184e3ff956b1718537e2854a12b8a95c4c6e 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java @@ -5,7 +5,9 @@ import java.util.List; import fr.agrometinfo.www.server.AgroMetInfoConfiguration; import fr.agrometinfo.www.server.AgroMetInfoConfiguration.ConfigurationKey; +import fr.agrometinfo.www.server.dao.DailyVisitDao; import fr.agrometinfo.www.server.exception.AgroMetInfoException; +import fr.agrometinfo.www.server.model.DailyVisit; import fr.agrometinfo.www.server.service.MailService; import fr.agrometinfo.www.server.service.MailService.Mail; import fr.agrometinfo.www.server.util.AppVersion; @@ -49,6 +51,12 @@ public class ApplicationResource implements ApplicationService { @Inject private AgroMetInfoConfiguration configuration; + /** + * DAO for {@link DailyVisit}. + */ + @Inject + private DailyVisitDao dailyVisitDao; + /** * Mail service. */ @@ -65,6 +73,8 @@ public class ApplicationResource implements ApplicationService { @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Override public Date getApplicationDate() { + // this is the first call, so register the visit. + dailyVisitDao.increment(configuration.get(ConfigurationKey.ENVIRONMENT)); return applicationDate; } diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernateTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b21a22ad62de6716ed996d8839a13a05e6f3a1a2 --- /dev/null +++ b/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyVisitDaoHibernateTest.java @@ -0,0 +1,39 @@ +package fr.agrometinfo.www.server.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test DailyVisitDao Hibernate implementation. + */ +class DailyVisitDaoHibernateTest { + /** + * DAO to test. + */ + private final DailyVisitDao dao = new DailyVisitDaoHibernate(); + + @Test + void increment() { + final String environment = "test"; + + var actual = dao.findAll(); + assertNotNull(actual, "List of daily visits must not be null!"); + assertTrue(actual.isEmpty()); + + dao.increment(environment); + actual = dao.findAll(); + assertNotNull(actual, "List of daily visits must not be null!"); + assertFalse(actual.isEmpty()); + assertEquals(1, actual.get(0).getNumber()); + + dao.increment(environment); + actual = dao.findAll(); + assertNotNull(actual, "List of daily visits must not be null!"); + assertFalse(actual.isEmpty()); + assertEquals(2, actual.get(0).getNumber()); + } +} diff --git a/www-server/src/test/resources/META-INF/persistence.xml b/www-server/src/test/resources/META-INF/persistence.xml index 74353e4ccf49232e47ee293e7d1de0e1c3351f87..55e45d5f4a3201beda796c044bc38de54791015a 100644 --- a/www-server/src/test/resources/META-INF/persistence.xml +++ b/www-server/src/test/resources/META-INF/persistence.xml @@ -10,6 +10,7 @@ <class>fr.agrometinfo.www.server.model.Cell</class> <class>fr.agrometinfo.www.server.model.ColorSequence</class> <class>fr.agrometinfo.www.server.model.DailyValue</class> + <class>fr.agrometinfo.www.server.model.DailyVisit</class> <class>fr.agrometinfo.www.server.model.Department</class> <class>fr.agrometinfo.www.server.model.Indicator</class> <class>fr.agrometinfo.www.server.model.Period</class>