Compare commits

..

35 Commits

Author SHA1 Message Date
Vincent Guillet
58a10fd4b8 Refactor Hibernate configuration to use a static local environment variable and simplify controller instantiation 2025-05-23 16:39:02 +02:00
Vincent Guillet
180e1e1622 Refactor Hibernate configuration and update data factory methods for clarity 2025-05-23 16:15:59 +02:00
Vincent Guillet
8bfc801b26 Add RestClient and DataFactory for HTTP requests and test data generation 2025-05-23 15:44:24 +02:00
Vincent Guillet
dc73603cda Refactor services and controllers to use ID for updates and standardize DAO method names 2025-05-23 14:50:27 +02:00
Vincent Guillet
c1617cbd38 Add server initialization in main application class 2025-05-23 11:26:30 +02:00
Vincent Guillet
a803a9ef22 Add dependency-reduced POM file for project configuration 2025-05-23 11:26:16 +02:00
Vincent Guillet
ed83c35f3c Add ApiApplication class to configure Jersey and register Jackson feature 2025-05-23 11:26:07 +02:00
Vincent Guillet
b63f1ef054 Add UserController class for user management endpoints 2025-05-23 11:25:56 +02:00
Vincent Guillet
8b8fae6e24 Update User class to use EAGER fetching for articles relationship 2025-05-23 11:25:44 +02:00
Vincent Guillet
adbdf0d619 Add ServerConfig class to initialize and start the server 2025-05-23 11:23:35 +02:00
Vincent Guillet
0887925477 Add Jackson JSR310 module dependency for Java 8 date/time support 2025-05-23 11:23:07 +02:00
Vincent Guillet
153380f541 update 2025-05-23 10:38:13 +02:00
Vincent Guillet
4cc46d7ac3 Add 'target/' to .gitignore to exclude build artifacts 2025-05-20 13:46:53 +02:00
Vincent Guillet
7fcdee6e2b remove target output 2025-05-20 13:46:29 +02:00
Vincent Guillet
3c330e9800 Refactor Publication class to use JOINED inheritance strategy and IDENTITY generation type 2025-05-20 13:44:51 +02:00
Vincent Guillet
47ee6f7ef1 Add Jakarta Validation API dependency and update Reflections dependency 2025-05-20 13:44:41 +02:00
Vincent Guillet
eaba56b92d Add Hibernate configuration file for database setup 2025-05-20 13:44:30 +02:00
Vincent Guillet
7f3157b7a2 Add Pair annotation for custom validation of even numbers 2025-05-20 13:44:10 +02:00
Vincent Guillet
5de8594095 Add PairValidator class for custom validation of even integers 2025-05-20 13:43:20 +02:00
Vincent Guillet
3da2712bdf Add HibernateConfig class for database configuration and refactor App to use BigDecimal for Ad prices 2025-05-20 13:43:09 +02:00
Vincent Guillet
24eab5f2cc Update Ad model to use BigDecimal for price and add email validation 2025-05-20 13:42:54 +02:00
Vincent Guillet
5f279e3e8a Add Ad model integration in App and implement database cleanup method 2025-05-19 14:44:53 +02:00
Vincent Guillet
a7701a59e5 Remove MySQL dialect property from Hibernate configuration and add mapping for Ad model 2025-05-19 14:44:44 +02:00
Vincent Guillet
0e219efac4 Add Publication abstract class for inheritance in content models 2025-05-19 14:44:35 +02:00
Vincent Guillet
02b837a0ea Refactor Article class to extend Publication and add views attribute 2025-05-19 14:44:26 +02:00
Vincent Guillet
e368538fed Remove unused Optional import from ArticleDao 2025-05-19 14:44:14 +02:00
Vincent Guillet
1a5b7b1a35 Update User entity to enable orphan removal for associated articles 2025-05-19 14:44:05 +02:00
Vincent Guillet
eafd02fc07 Add Ad model, DAO, and service for managing advertisements 2025-05-19 14:43:55 +02:00
Vincent Guillet
ad130294fd Refactor ArticleService and UserService to use records for improved readability 2025-05-19 14:43:41 +02:00
Vincent Guillet
bd62504d87 Refactor Article search functionality to support pagination and enhance output formatting 2025-05-15 17:14:54 +02:00
Vincent Guillet
14d1264091 Disable SQL logging in Hibernate configuration 2025-05-15 15:25:05 +02:00
Vincent Guillet
0f8f09e0d7 Update docker-compose to expose MySQL on port 3366 2025-05-15 15:24:59 +02:00
Vincent Guillet
7c89d637f7 Add Article service and DAO with criteria-based search functionality 2025-05-15 15:24:46 +02:00
Vincent Guillet
8fd72d599d first commit with existing project files 2025-05-15 12:02:44 +02:00
Vincent Guillet
ff55029ce2 first commit with existing project files 2025-05-15 11:37:34 +02:00
28 changed files with 1330 additions and 0 deletions

1
.gitignore vendored
View File

@@ -27,6 +27,7 @@ dist/
build/ build/
coverage/ coverage/
out/ out/
target/
# Environment and secrets # Environment and secrets
.env .env

30
docker-compose.yml Normal file
View File

@@ -0,0 +1,30 @@
services:
mysql:
image: mysql:8.3
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
- "3366:3306"
volumes:
- mysql_data:/var/lib/mysql
app:
build: ./hibernate-project
container_name: app
depends_on:
- mysql
restart: on-failure
environment:
DB_HOST: mysql
DB_PORT: 3306
DB_NAME: testdb
DB_USER: user
DB_PASSWORD: password
volumes:
mysql_data:

View File

@@ -0,0 +1,19 @@
FROM maven:3.9.6-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/hibernate-project-1.0-SNAPSHOT.jar app.jar
ENTRYPOINT [ "java", "-jar", "app.jar" ]

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.humanbooster</groupId>
<artifactId>hibernate-project</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.humanbooster.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>com.humanbooster.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>opentest4j</artifactId>
<groupId>org.opentest4j</groupId>
</exclusion>
<exclusion>
<artifactId>junit-platform-commons</artifactId>
<groupId>org.junit.platform</groupId>
</exclusion>
<exclusion>
<artifactId>apiguardian-api</artifactId>
<groupId>org.apiguardian</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.11.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>apiguardian-api</artifactId>
<groupId>org.apiguardian</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-platform</artifactId>
<version>6.6.13.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>3.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

214
hibernate-project/pom.xml Normal file
View File

@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8"?>
<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.humanbooster</groupId>
<artifactId>hibernate-project</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-platform</artifactId>
<version>6.6.13.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>3.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<!-- Optionally: parameterized tests support -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.3.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<!-- JAX-RS (Jersey) -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<!-- H2 Database (pour tests, sinon MySQL/PostgreSQL) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>runtime</scope>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>11.0.25</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>11.0.25</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.humanbooster.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.humanbooster.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,64 @@
package com.humanbooster;
import com.humanbooster.client.RestClient;
import com.humanbooster.config.HibernateConfig;
import com.humanbooster.config.ServerConfig;
import com.humanbooster.dao.AdDao;
import com.humanbooster.dao.ArticleDao;
import com.humanbooster.dao.UserDao;
import com.humanbooster.factory.DataFactory;
import com.humanbooster.model.Ad;
import com.humanbooster.model.Article;
import com.humanbooster.model.User;
import com.humanbooster.service.AdService;
import com.humanbooster.service.ArticleService;
import com.humanbooster.service.UserService;
import org.hibernate.SessionFactory;
import java.util.List;
public class App {
public static final boolean LOCAL_ENVIRONMENT = true;
public static void main(String[] args) {
System.out.println("Démarrage de l'application");
RestClient client = new RestClient();
try {
ServerConfig serverConfig = new ServerConfig();
serverConfig.startServer();
} catch (Exception e) {
System.out.println("Erreur lors du démarrage du serveur : " + e.getMessage());
}
SessionFactory sessionFactory = new HibernateConfig().getSessionFactory();
UserService userService = new UserService(new UserDao(sessionFactory));
ArticleService articleService = new ArticleService(new ArticleDao(sessionFactory));
AdService adService = new AdService(new AdDao(sessionFactory));
DataFactory dataFactory = new DataFactory();
cleanDatabase(userService, articleService, adService);
userService.createUser(dataFactory.createUser("Michel", "michel@test.fr"));
dataFactory.createAds().forEach(adService::createAd);
sessionFactory.close();
System.out.print("Fin du programme");
}
static void cleanDatabase(UserService userService, ArticleService articleService, AdService adService) {
List<User> existingUsers = userService.getAllUsers();
existingUsers.forEach(u -> userService.deleteUser(u.getId()));
List<Article> existingArticles = articleService.getAllArticles();
existingArticles.forEach(a -> articleService.deleteArticle(a.getId()));
List<Ad> existingAds = adService.getAllAds();
existingAds.forEach(a -> adService.deleteAd(a.getId()));
}
}

View File

@@ -0,0 +1,63 @@
package com.humanbooster.client;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class RestClient {
private final HttpClient httpClient = HttpClient.newHttpClient();
public String sendGetRequest(String url, String method, String body) {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json");
switch (method.toUpperCase()) {
case "GET":
builder.GET();
break;
case "POST":
builder.POST(HttpRequest.BodyPublishers.ofString(body));
break;
case "PUT":
builder.PUT(HttpRequest.BodyPublishers.ofString(body));
break;
case "DELETE":
builder.DELETE();
break;
default:
throw new IllegalArgumentException("Invalid HTTP method: " + method);
}
HttpRequest request = builder.build();
HttpResponse<String> response;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return parseResponse(response.body());
} catch (Exception e) {
System.err.println("Error occurred while sending the request: " + e.getMessage());
}
return null;
}
private String parseResponse(String response) {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode jsonNode = mapper.readTree(response);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
} catch (Exception e) {
System.err.println("Error occurred while parsing the response: " + e.getMessage());
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
package com.humanbooster.config;
import com.humanbooster.App;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.reflections.Reflections;
import jakarta.persistence.Entity;
public class HibernateConfig {
public SessionFactory getSessionFactory() {
SessionFactory sessionFactory;
if (App.LOCAL_ENVIRONMENT) {
Configuration config = new Configuration()
.setProperty("hibernate.connection.url", "jdbc:mysql://127.0.0.1:3306/testdb")
.setProperty("hibernate.connection.username", "admin")
.setProperty("hibernate.connection.password", "admin")
.setProperty("hibernate.connection.driver_class", "com.mysql.cj.jdbc.Driver")
.setProperty("hibernate.hbm2ddl.auto", "update")
.setProperty("hibernate.show_sql", "false")
.setProperty("hibernate.format_sql", "true");
Reflections reflections = new Reflections("com.humanbooster.model");
for (Class<?> clazz : reflections.getTypesAnnotatedWith(Entity.class)) {
config.addAnnotatedClass(clazz);
}
return sessionFactory = config.buildSessionFactory();
} else {
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure()
.build();
Metadata metadata = new MetadataSources(registry).buildMetadata();
return sessionFactory = metadata.buildSessionFactory();
}
}
}

View File

@@ -0,0 +1,37 @@
package com.humanbooster.config;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
public class ServerConfig extends ResourceConfig {
public ServerConfig() {
packages("com.humanbooster");
register(JacksonFeature.class);
}
public void startServer() throws Exception {
System.out.println("Lancement du serveur...");
ResourceConfig config = this;
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
Server server = new Server(80);
ServletContextHandler context = new ServletContextHandler(server, "/");
context.setServer(server);
context.addServlet(servlet, "/*");
try {
server.start();
System.out.println("Serveur démarré sur le port 80");
server.join();
} catch (Exception e) {
System.out.println("Echec lors du lancement du serveur: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,20 @@
package com.humanbooster.controller;
import com.humanbooster.config.HibernateConfig;
import com.humanbooster.dao.ArticleDao;
import com.humanbooster.dao.GenericDao;
import com.humanbooster.model.Article;
import jakarta.ws.rs.Path;
import org.hibernate.SessionFactory;
@Path("/articles")
public class ArticleController extends GenericControllerImpl<Article, Long> {
public ArticleController() {
this(new HibernateConfig().getSessionFactory(), new ArticleDao(new HibernateConfig().getSessionFactory()));
}
public ArticleController(SessionFactory sessionFactory, GenericDao<Article, Long> dao) {
super(sessionFactory, dao);
}
}

View File

@@ -0,0 +1,11 @@
package com.humanbooster.controller;
import java.util.List;
public interface GenericController<T, ID> {
void create(T entity);
T read(ID id);
void update(ID id);
void delete(ID id);
List<T> getAll();
}

View File

@@ -0,0 +1,55 @@
package com.humanbooster.controller;
import com.humanbooster.config.HibernateConfig;
import com.humanbooster.dao.GenericDao;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.hibernate.SessionFactory;
import java.util.List;
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public abstract class GenericControllerImpl<T, ID> implements GenericController<T, ID>, GenericDao<T, ID> {
protected final SessionFactory sessionFactory;
private final GenericDao<T, ID> dao;
public GenericControllerImpl(SessionFactory sessionFactory, GenericDao<T, ID> dao) {
this.sessionFactory = sessionFactory;
this.dao = dao;
}
@POST
@Override
public void create(T entity) {
dao.create(entity);
}
@GET
@Path("/{id}")
@Override
public T read(@PathParam("id") ID id) {
return dao.read(id);
}
@PUT
@Path("/{id}")
@Override
public void update(@PathParam("id") ID id) {
dao.update(id);
}
@DELETE
@Path("/{id}")
@Override
public void delete(@PathParam("id") ID id) {
dao.delete(id);
}
@GET
@Override
public List<T> getAll() {
return dao.getAll();
}
}

View File

@@ -0,0 +1,23 @@
package com.humanbooster.controller;
import com.humanbooster.config.HibernateConfig;
import com.humanbooster.dao.UserDao;
import com.humanbooster.model.User;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.hibernate.SessionFactory;
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserController extends GenericControllerImpl<User, Long> {
public UserController() {
this(new HibernateConfig().getSessionFactory(), new UserDao(new HibernateConfig().getSessionFactory()));
}
public UserController(SessionFactory sessionFactory, UserDao userDao) {
super(sessionFactory, userDao);
}
}

View File

@@ -0,0 +1,11 @@
package com.humanbooster.dao;
import com.humanbooster.model.Ad;
import org.hibernate.SessionFactory;
public class AdDao extends GenericDaoImpl<Ad, Long> {
public AdDao(SessionFactory sessionFactory) {
super(sessionFactory, Ad.class);
}
}

View File

@@ -0,0 +1,74 @@
package com.humanbooster.dao;
import com.humanbooster.model.Article;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.util.ArrayList;
import java.util.List;
public class ArticleDao extends GenericDaoImpl<Article, Long> {
public ArticleDao(SessionFactory sessionFactory) {
super(sessionFactory, Article.class);
}
public Article findByAuthor(String author) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
Article article = session.createQuery("FROM Article WHERE author = :author", Article.class)
.setParameter("author", author)
.uniqueResult();
session.getTransaction().commit();
return article;
}
}
public Article findByTitle(String title) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
Article article = session.createQuery("FROM Article WHERE title = :title", Article.class)
.setParameter("title", title)
.uniqueResult();
session.getTransaction().commit();
return article;
}
}
public List<Article> findByCriteria(String keyword, Long authorId, int page, int size) {
try (Session session = sessionFactory.openSession()) {
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Article> query = cb.createQuery(Article.class);
Root<Article> root = query.from(Article.class);
query.select(root);
List<Predicate> predicates = new ArrayList<>();
if (keyword != null && !keyword.isEmpty()) {
predicates.add(
cb.like(root.get("title"), "%" + keyword + "%")
);
}
if (authorId != null) {
predicates.add(
cb.equal(root.get("author").get("id"), authorId)
);
}
query.where(predicates.toArray(Predicate[]::new));
query.orderBy(cb.asc(root.get("title")));
return session.createQuery(query)
.setFirstResult(page * size)
.setMaxResults(size)
.getResultList();
}
}
}

View File

@@ -0,0 +1,11 @@
package com.humanbooster.dao;
import java.util.List;
public interface GenericDao<T, ID> {
void create(T entity);
T read(ID id);
void update(ID id);
void delete(ID id);
List<T> getAll();
}

View File

@@ -0,0 +1,66 @@
package com.humanbooster.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.util.List;
public abstract class GenericDaoImpl<T, ID> implements GenericDao<T, ID> {
protected final Class<T> entityClass;
protected SessionFactory sessionFactory;
public GenericDaoImpl(SessionFactory sessionFactory, Class<T> entityClass) {
this.sessionFactory = sessionFactory;
this.entityClass = entityClass;
}
@Override
public void create(T entity) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.persist(entity);
session.getTransaction().commit();
}
}
@Override
public T read(ID id) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
T entity = session.get(entityClass, id);
session.getTransaction().commit();
return entity;
}
}
@Override
public void update(ID id) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
T entity = session.get(entityClass, id);
session.merge(entity);
session.getTransaction().commit();
}
}
@Override
public void delete(ID id) {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
T entity = session.find(entityClass, id);
if (entity != null) session.remove(entity);
session.getTransaction().commit();
}
}
@Override
public List<T> getAll() {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
List<T> entities = session.createQuery("from " + entityClass.getName(), entityClass).list();
session.getTransaction().commit();
return entities;
}
}
}

View File

@@ -0,0 +1,23 @@
package com.humanbooster.dao;
import com.humanbooster.model.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class UserDao extends GenericDaoImpl<User,Long> {
public UserDao(SessionFactory sessionFactory) {
super(sessionFactory, User.class);
}
public User findByEmail(String email) {
try(Session session = sessionFactory.openSession()){
session.beginTransaction();
User user = session.createQuery("FROM User WHERE email = :email", User.class)
.setParameter("email", email)
.uniqueResult();
session.getTransaction().commit();
return user;
}
}
}

View File

@@ -0,0 +1,43 @@
package com.humanbooster.factory;
import com.humanbooster.model.Ad;
import com.humanbooster.model.Article;
import com.humanbooster.model.User;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
public class DataFactory {
public User createUser(String name, String email) {
User user = new User(name, email, null);
user.setArticles(List.of(
new Article("Article 1", "Contenu de l'article 1", LocalDate.now(), user, 0),
new Article("Article 2", "Contenu de l'article 2", LocalDate.now(), user, 0)
));
return user;
}
public List<Ad> createAds() {
return List.of(
(new Ad(
"Ad 1",
"Contenu de l'annonce 1",
LocalDate.now(),
LocalDate.now().plusDays(7),
"contact@example.com",
BigDecimal.valueOf(12))),
(new Ad(
"Ad 2",
"Contenu de l'annonce 2",
LocalDate.now(),
LocalDate.now().plusDays(10),
"contact@example.com",
BigDecimal.valueOf(6.7)))
);
}
}

View File

@@ -0,0 +1,55 @@
package com.humanbooster.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.Email;
import java.math.BigDecimal;
import java.time.LocalDate;
@Entity
public class Ad extends Publication {
@Column(nullable = false)
private LocalDate expirationDate;
@Column(nullable = false)
@Email
private String contactEmail;
private BigDecimal price;
public Ad() {
}
public Ad(String title, String content, LocalDate publishDate, LocalDate expirationDate, String contactEmail, BigDecimal price) {
super(title, content, publishDate);
this.expirationDate = expirationDate;
this.contactEmail = contactEmail;
this.price = price;
}
public LocalDate getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(LocalDate expirationDate) {
this.expirationDate = expirationDate;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}

View File

@@ -0,0 +1,45 @@
package com.humanbooster.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import java.time.LocalDate;
@Entity
public class Article extends Publication {
@ManyToOne
@JoinColumn(name="author_id", nullable = false)
@JsonBackReference
private User author;
private int views = 0;
public Article() {}
public Article (String title, String content, LocalDate publishDate, User author, int views) {
super(title, content, publishDate);
this.author = author;
this.views = views;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Article ID: ").append(super.getId()).append("\n");
sb.append("Title: ").append(super.getTitle()).append("\n");
sb.append("Content: ").append(super.getContent()).append("\n");
sb.append("Author: ").append(author != null ? author.getName() : "Unknown").append("\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,65 @@
package com.humanbooster.model;
import jakarta.persistence.*;
import java.time.LocalDate;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Publication {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private LocalDate publishDate;
public Publication() {
}
public Publication(String title, String content, LocalDate publishDate) {
this.title = title;
this.content = content;
this.publishDate = publishDate;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDate getPublishDate() {
return publishDate;
}
public void setPublishDate(LocalDate publishDate) {
this.publishDate = publishDate;
}
}

View File

@@ -0,0 +1,65 @@
package com.humanbooster.model;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@Entity
public class User {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@NotNull
private String email;
@OneToMany(mappedBy="author", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JsonManagedReference
private List<Article> articles;
public User() {}
public User(String name, String email, List<Article> articles) {
this.name = name;
this.email = email;
this.articles = articles;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Article> getArticles() {
return articles;
}
public void setArticles(List<Article> articles) {
this.articles = articles;
}
}

View File

@@ -0,0 +1,29 @@
package com.humanbooster.service;
import com.humanbooster.dao.AdDao;
import com.humanbooster.model.Ad;
import java.util.List;
public record AdService (AdDao adDao) {
public void createAd(Ad ad) {
adDao.create(ad);
}
public Ad getAdById(Long id) {
return adDao.read(id);
}
public void updateAd(Long id) {
adDao.update(id);
}
public void deleteAd(Long id) {
adDao.delete(id);
}
public List<Ad> getAllAds() {
return adDao.getAll();
}
}

View File

@@ -0,0 +1,41 @@
package com.humanbooster.service;
import com.humanbooster.dao.ArticleDao;
import com.humanbooster.model.Article;
import java.util.List;
public record ArticleService(ArticleDao articleDao) {
public void createArticle(Article article) {
articleDao.create(article);
}
public void getArticleById(Long id) {
articleDao.read(id);
}
public void updateArticle(Long id) {
articleDao.update(id);
}
public void deleteArticle(Long id) {
articleDao.delete(id);
}
public List<Article> getAllArticles() {
return articleDao.getAll();
}
public Article findArticleByAuthor(String author) {
return articleDao.findByAuthor(author);
}
public Article findArticleByTitle(String title) {
return articleDao.findByTitle(title);
}
public List<Article> findArticlesByCriteria(String keyword, Long authorId, int page, int size) {
return articleDao.findByCriteria(keyword, authorId, page, size);
}
}

View File

@@ -0,0 +1,33 @@
package com.humanbooster.service;
import com.humanbooster.dao.UserDao;
import com.humanbooster.model.User;
import java.util.List;
public record UserService (UserDao userDao) {
public void createUser(User user) {
userDao.create(user);
}
public void getUserById(Long id) {
userDao.read(id);
}
public void updateUser(Long id) {
userDao.update(id);
}
public void deleteUser(Long id) {
userDao.delete(id);
}
public List<User> getAllUsers() {
return userDao.getAll();
}
public User findUserByEmail(String email) {
return userDao.findByEmail(email);
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="jakarta.persistence.jdbc.driver">com.mysql.cj.jdbc.Driver</property>
<property name="jakarta.persistence.jdbc.url">jdbc:mysql://mysql:3306/testdb?useSSL=false&amp;allowPublicKeyRetrieval=true</property>
<property name="jakarta.persistence.jdbc.user">root</property>
<property name="jakarta.persistence.jdbc.password">root</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.show_sql">false</property>
<property name="hibernate.format_sql">true</property>
<mapping class="com.humanbooster.model.User"/>
<mapping class="com.humanbooster.model.Article"/>
<mapping class="com.humanbooster.model.Ad"/>
</session-factory>
</hibernate-configuration>

View File

@@ -0,0 +1,14 @@
### GET request to example server
GET http://localhost/users
###
POST http://localhost/users/
Content-Type: application/json
{
"name": "John Doe",
"email": "john.doe@example.com"
}
###
DELETE http://localhost/articles/13