From 0cfb10884e95fce64df8d9c7a373584ee45407b3 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Fri, 31 Jul 2026 00:06:05 -0300 Subject: [PATCH 1/8] Adding spring-boot-redis-sample project. Confirmed that this project starts having a redis instance up, and EM can work with it, having tested EM's blackbox interaction --- jdk_21_maven/cs/rest/pom.xml | 1 + .../rest/spring-boot-redis-sample/README.md | 267 ++++++++++++++++++ .../cs/rest/spring-boot-redis-sample/pom.xml | 150 ++++++++++ .../SpringBootRedisSampleApplication.java | 57 ++++ .../boot/CreateBookRatings.java | 67 +++++ .../boot/CreateBooks.java | 96 +++++++ .../boot/CreateRoles.java | 39 +++ .../boot/CreateUsers.java | 88 ++++++ .../controller/BookController.java | 91 ++++++ .../controller/UserController.java | 42 +++ .../springbootredissample/model/Book.java | 81 ++++++ .../model/BookRating.java | 38 +++ .../springbootredissample/model/Category.java | 32 +++ .../springbootredissample/model/Role.java | 52 ++++ .../springbootredissample/model/User.java | 128 +++++++++ .../repository/BookRatingRepository.java | 20 ++ .../repository/BookRepository.java | 21 ++ .../repository/CategoryRepository.java | 20 ++ .../repository/RoleRepository.java | 38 +++ .../repository/UserRepository.java | 21 ++ .../src/main/resources/application.properties | 7 + .../src/main/resources/data/books/_books.json | 52 ++++ .../src/main/resources/data/users/users.json | 71 +++++ ...SpringBootRedisSampleApplicationTests.java | 56 ++++ jdk_21_maven/em/embedded/rest/pom.xml | 1 + .../rest/spring-boot-redis-sample/pom.xml | 42 +++ .../EmbeddedEvoMasterController.java | 110 ++++++++ openapi-swagger/spring-boot-redis-sample.json | 207 ++++++++++++++ 28 files changed, 1895 insertions(+) create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/README.md create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplication.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBookRatings.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBooks.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateRoles.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateUsers.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/BookController.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/UserController.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Book.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/BookRating.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Category.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Role.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/User.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRatingRepository.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRepository.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/CategoryRepository.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/RoleRepository.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/UserRepository.java create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/application.properties create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/books/_books.json create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/users/users.json create mode 100644 jdk_21_maven/cs/rest/spring-boot-redis-sample/src/test/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplicationTests.java create mode 100644 jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml create mode 100644 jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java create mode 100644 openapi-swagger/spring-boot-redis-sample.json diff --git a/jdk_21_maven/cs/rest/pom.xml b/jdk_21_maven/cs/rest/pom.xml index 3cb72b60b..a35e941ef 100644 --- a/jdk_21_maven/cs/rest/pom.xml +++ b/jdk_21_maven/cs/rest/pom.xml @@ -14,6 +14,7 @@ person-controller + spring-boot-redis-sample diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/README.md b/jdk_21_maven/cs/rest/spring-boot-redis-sample/README.md new file mode 100644 index 000000000..1eb05a94c --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/README.md @@ -0,0 +1,267 @@ +# Spring Boot Redis Sample + +A comprehensive Spring Boot 3.5.6 application demonstrating Redis integration with caching, data persistence, and +RESTful APIs. + +## Features + +- Spring Boot 3.5.6 with Java 21 +- Redis Stack integration for data storage and caching +- Spring Data Redis for repository management +- Spring Security with BCrypt password encoding +- RESTful API endpoints for Books and Users management +- Redis caching with configurable TTL +- Docker Compose integration for easy Redis setup +- Sample data initialization on startup +- Testcontainers for integration testing + +## Technologies + +- **Spring Boot**: 3.5.6 +- **Java**: 21 +- **Redis**: Redis Stack (latest) +- **Spring Data Redis**: Repository pattern implementation +- **Spring Security**: User authentication and authorization +- **Spring Validation**: Input validation +- **Lombok**: Reduce boilerplate code +- **Docker Compose**: Container orchestration +- **Testcontainers**: Integration testing + +## Prerequisites + +- Java 21 or higher +- Docker and Docker Compose +- Maven 3.x + +## Project Structure + +``` +spring-boot-redis-sample/ +├── src/main/java/ +│ └── id/my/hendisantika/springbootredissample/ +│ ├── SpringBootRedisSampleApplication.java # Main application class +│ ├── boot/ # Data initialization +│ │ ├── CreateBookRatings.java +│ │ ├── CreateBooks.java +│ │ ├── CreateRoles.java +│ │ └── CreateUsers.java +│ ├── controller/ # REST Controllers +│ │ ├── BookController.java +│ │ └── UserController.java +│ ├── model/ # Domain models +│ │ ├── Book.java +│ │ ├── BookRating.java +│ │ ├── Category.java +│ │ ├── Role.java +│ │ └── User.java +│ └── repository/ # Redis repositories +│ ├── BookRatingRepository.java +│ ├── BookRepository.java +│ ├── CategoryRepository.java +│ ├── RoleRepository.java +│ └── UserRepository.java +├── src/main/resources/ +│ ├── application.properties # Application configuration +│ └── data/books/ # Sample book data +├── compose.yaml # Docker Compose configuration +└── pom.xml # Maven dependencies +``` + +## Configuration + +### Application Properties (application.properties:1) + +```properties +spring.application.name=bookstore +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.password=${REDIS_PASSWORD:53cret} +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +app.numberOfRatings=5000 +app.ratingStars=5 +``` + +### Redis Configuration (compose.yaml:1) + +The application uses Redis Stack with the following configuration: + +- Port: 6379 +- Password: 53cret +- Persistence: AOF (Append-Only File) +- Data directory: ./data + +## Getting Started + +### 1. Clone the repository + +```bash +git clone +cd spring-boot-redis-sample2 +``` + +### 2. Start Redis with Docker Compose + +```bash +docker compose up -d +``` + +### 3. Build the application + +```bash +./mvnw clean install +``` + +### 4. Run the application + +```bash +./mvnw spring-boot:run +``` + +The application will start on http://localhost:8080 + +## API Endpoints + +### Books API + +#### Get all books (with pagination) + +```bash +GET /api/books?page=0&size=10 +``` + +Response: + +```json +{ + "total": 2, + "page": 0, + "pages": 1, + "books": [ + ... + ] +} +``` + +#### Get book by ISBN + +```bash +GET /api/books/{isbn} +``` + +#### Get all categories + +```bash +GET /api/books/categories +``` + +### Users API + +#### Get users by email + +```bash +GET /api/users?email=yuji@yopmail.com +``` + +## Caching + +The application uses Redis caching with the following configuration (SpringBootRedisSampleApplication.java:36): + +- Cache TTL: 1 hour +- Cache name prefix: Package name +- Null values caching: Disabled + +### Cached Endpoints + +- `/api/books` - Cached with key: `page-size` +- `/api/books/categories` - Cached + +## Data Models + +### Book (Book.java:1) + +- ISBN (ID) +- Title, Subtitle, Description +- Language, Page Count +- Price, Currency +- Authors (Set) +- Categories (Set) +- Thumbnail, Info Link + +### User (User.java:1) + +- ID +- Name, Email +- Password (BCrypt encrypted) +- Roles (Set) + +### Category (Category.java:1) + +- ID +- Name + +### BookRating (BookRating.java:1) + +- ID +- User reference +- Book reference +- Rating (1-5 stars) + +## Testing + +Run tests with: + +```bash +./mvnw test +``` + +The project includes Testcontainers for integration testing with Redis. + +## Docker Compose Services + +The `compose.yaml` defines a Redis Stack service: + +- Image: redis/redis-stack:latest +- Container name: redis +- Ports: 6379:6379 +- Volume: ./data:/data +- Password protected: 53cret + +## Development + +### Spring Boot DevTools + +The application includes Spring Boot DevTools for: + +- Automatic application restart on code changes +- LiveReload server on port 35729 + +### Sample Data + +On startup, the application automatically creates: + +- 2 Roles (admin, customer) +- 5 Users +- 2 Books +- 5000 Book Ratings + +## Building for Production + +```bash +./mvnw clean package -DskipTests +java -jar target/spring-boot-redis-sample-0.0.1-SNAPSHOT.jar +``` + +## License + +This project is for educational purposes. + +## Author + +- Name: Hendi Santika +- Link: s.id/hendisantika +- Email: hendisantika@yahoo.co.id +- Telegram: @hendisantika34 + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml new file mode 100644 index 000000000..2c6700338 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.1.0 + + + id.my.hendisantika + spring-boot-redis-sample + 0.0.1-SNAPSHOT + spring-boot-redis-sample + spring-boot-redis-sample + + 21 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + org.springframework.security + spring-security-crypto + + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 3.0.3 + + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.testcontainers + testcontainers + 2.0.5 + test + + + org.testcontainers + junit-jupiter + 1.21.4 + test + + + com.redis + testcontainers-redis + 2.2.4 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplication.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplication.java new file mode 100644 index 000000000..8dfd4c35d --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplication.java @@ -0,0 +1,57 @@ +package id.my.hendisantika.springbootredissample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import java.time.Duration; + +@EnableCaching // Enables Spring’s annotation-driven cache management capability +@SpringBootApplication +public class SpringBootRedisSampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootRedisSampleApplication.class, args); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + return template; + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + + /** + * Creates and configures a RedisCacheManager bean. + * RedisCacheManager: Manages caching operations and configurations for Redis. + * RedisCacheConfiguration: Configures caching behavior, including cache name prefixes, TTL, and whether null values should be cached. + * + * @param connectionFactory the RedisConnectionFactory used to connect to the Redis server. + * @return a configured RedisCacheManager instance. + * - **Prefix Cache Names:** Cache names are prefixed with the package name of the class to avoid naming collisions. + * - **Entry TTL (Time-to-Live):** Cache entries will expire after 1 hour. + * - **Disable Caching Null Values:** Null values are not cached. + */ + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .prefixCacheNameWith(this.getClass().getPackageName() + ".") + .entryTtl(Duration.ofHours(1)) + .disableCachingNullValues(); + + return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build(); + } + +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBookRatings.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBookRatings.java new file mode 100644 index 000000000..b933209cd --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBookRatings.java @@ -0,0 +1,67 @@ +package id.my.hendisantika.springbootredissample.boot; + +import id.my.hendisantika.springbootredissample.model.Book; +import id.my.hendisantika.springbootredissample.model.BookRating; +import id.my.hendisantika.springbootredissample.model.User; +import id.my.hendisantika.springbootredissample.repository.BookRatingRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Random; +import java.util.stream.IntStream; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.43 + * To change this template use File | Settings | File Templates. + */ +@Slf4j +@Order(4) +@Component +@RequiredArgsConstructor +public class CreateBookRatings implements CommandLineRunner { + + private final RedisTemplate redisTemplate; + private final BookRatingRepository bookRatingRepo; + @Value("${app.numberOfRatings}") + private Integer numberOfRatings; + @Value("${app.ratingStars}") + private Integer ratingStars; + + @Override + public void run(String... args) throws Exception { + if (bookRatingRepo.count() == 0) { + Random random = new Random(); + IntStream.range(0, numberOfRatings).forEach(n -> { + String bookId = redisTemplate.opsForSet().randomMember(Book.class.getName()); + String userId = redisTemplate.opsForSet().randomMember(User.class.getName()); + int stars = random.nextInt(ratingStars) + 1; + + User user = new User(); + user.setId(userId); + + Book book = new Book(); + book.setId(bookId); + + BookRating rating = BookRating.builder() // + .user(user) // + .book(book) // + .rating(stars).build(); + bookRatingRepo.save(rating); + }); + + log.info(">>>> BookRating created..."); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBooks.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBooks.java new file mode 100644 index 000000000..0d9808476 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateBooks.java @@ -0,0 +1,96 @@ +package id.my.hendisantika.springbootredissample.boot; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import id.my.hendisantika.springbootredissample.model.Book; +import id.my.hendisantika.springbootredissample.model.Category; +import id.my.hendisantika.springbootredissample.repository.BookRepository; +import id.my.hendisantika.springbootredissample.repository.CategoryRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.44 + * To change this template use File | Settings | File Templates. + */ +@Slf4j +@Order(3) +@Component +@RequiredArgsConstructor +public class CreateBooks implements CommandLineRunner { + + private final BookRepository bookRepository; + + private final CategoryRepository categoryRepository; + private final ResourceLoader resourceLoader; + + @Override + public void run(String... args) throws Exception { + if (bookRepository.count() == 0) { + ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeReference = new TypeReference<>() { + }; + + Resource resource = resourceLoader.getResource("classpath:/data/books/"); + File directory = resource.getFile(); + File[] files = directory.listFiles((dir, name) -> name.endsWith(".json")); + + if (files == null || files.length == 0) { + log.warn("No JSON files found in /data/books/ directory."); + return; + } + + Map categories = new HashMap<>(); + log.info("files -> {}", files); + + Arrays.stream(files).forEach(file -> { + try { + log.info(">>>> Processing Book File: {}", file.getPath()); + String categoryName = file.getName().substring(0, file.getName().lastIndexOf("_")); + log.info(">>>> Category: {}", categoryName); + + Category category; + if (!categories.containsKey(categoryName)) { + category = Category.builder().name(categoryName).build(); + categoryRepository.save(category); + categories.put(categoryName, category); + } else { + category = categories.get(categoryName); + } + + InputStream inputStream = resourceLoader.getResource("classpath:/data/books/" + file.getName()).getInputStream(); + List books = mapper.readValue(inputStream, typeReference); + books.forEach((book) -> { + book.addCategory(category); + bookRepository.save(book); + }); + log.info(">>>> {} Books Saved!", books.size()); + } catch (IOException e) { + log.error("Unable to import books from file: {}", file.getName(), e); + } + }); + + log.info(">>>> Loaded Book Data and Created books..."); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateRoles.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateRoles.java new file mode 100644 index 000000000..a71b5c1ff --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateRoles.java @@ -0,0 +1,39 @@ +package id.my.hendisantika.springbootredissample.boot; + +import id.my.hendisantika.springbootredissample.model.Role; +import id.my.hendisantika.springbootredissample.repository.RoleRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.45 + * To change this template use File | Settings | File Templates. + */ +@Slf4j +@Order(1) +@Component +@RequiredArgsConstructor +public class CreateRoles implements CommandLineRunner { + private final RoleRepository roleRepository; + + @Override + public void run(String... args) throws Exception { + if (roleRepository.count() == 0) { + Role adminRole = Role.builder().name("admin").build(); + roleRepository.save(adminRole); + Role customerRole = Role.builder().name("customer").build(); + roleRepository.save(customerRole); + log.info(">>>> Created admin and customer roles..."); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateUsers.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateUsers.java new file mode 100644 index 000000000..5d7f43e9e --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/boot/CreateUsers.java @@ -0,0 +1,88 @@ +package id.my.hendisantika.springbootredissample.boot; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import id.my.hendisantika.springbootredissample.model.Role; +import id.my.hendisantika.springbootredissample.model.User; +import id.my.hendisantika.springbootredissample.repository.RoleRepository; +import id.my.hendisantika.springbootredissample.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.46 + * To change this template use File | Settings | File Templates. + */ +@Slf4j +@Order(2) +@Component +@RequiredArgsConstructor +public class CreateUsers implements CommandLineRunner { + + private final RoleRepository roleRepository; + + private final UserRepository userRepository; + + private final BCryptPasswordEncoder passwordEncoder; + + private final ResourceLoader resourceLoader; + + + @Override + public void run(String... args) throws Exception { + if (userRepository.count() == 0) { + // load the roles + Role admin = roleRepository.findFirstByname("admin"); + Role customer = roleRepository.findFirstByname("customer"); + + try { + // create a Jackson object mapper + ObjectMapper mapper = new ObjectMapper(); + // create a type definition to convert the array of JSON into a List of Users + TypeReference> typeReference = new TypeReference<>() { + }; + // make the JSON data available as an input stream + Resource resource = resourceLoader.getResource("classpath:data/users/users.json"); + InputStream inputStream = resource.getInputStream(); + // convert the JSON to objects + List users = mapper.readValue(inputStream, typeReference); + + users.stream().forEach((user) -> { + user.setPassword(passwordEncoder.encode(user.getPassword())); + user.addRole(customer); +// userRepository.save(user); + }); + userRepository.saveAll(users); + log.info(">>>> {} Users Saved!", users.size()); + } catch (IOException e) { + log.info(">>>> Unable to import users: {}", e.getMessage()); + } + + User adminUser = new User(); + adminUser.setName("Itadori Yuji"); + adminUser.setEmail("yuji@yopmail.com"); + adminUser.setPassword(passwordEncoder.encode("53cret"));// + adminUser.addRole(admin); + + userRepository.save(adminUser); + log.info(">>>> Loaded User Data and Created users..."); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/BookController.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/BookController.java new file mode 100644 index 000000000..e1002b6cf --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/BookController.java @@ -0,0 +1,91 @@ +package id.my.hendisantika.springbootredissample.controller; + +import id.my.hendisantika.springbootredissample.model.Book; +import id.my.hendisantika.springbootredissample.model.Category; +import id.my.hendisantika.springbootredissample.repository.BookRepository; +import id.my.hendisantika.springbootredissample.repository.CategoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.48 + * To change this template use File | Settings | File Templates. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/books") +public class BookController { + + private final BookRepository bookRepository; + + private final CategoryRepository categoryRepository; + + /** + * Retrieves a paginated list of books. + * Caches the result to avoid hitting the database on repeated requests with the same parameters. + * + * @param page the page number to retrieve (default is 0). + * @param size the number of items per page (default is 10). + * @return a ResponseEntity containing the paginated books, page number, total pages, and total elements. + */ + @GetMapping + @Cacheable(value = "booksCache", key = "#page + '-' + #size") + public Map getBooks(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "10") Integer size) { + Pageable paging = PageRequest.of(page, size); + Page pagedResult = bookRepository.findAll(paging); + List books = pagedResult.hasContent() ? pagedResult.getContent() : Collections.emptyList(); + + Map response = new HashMap<>(); + response.put("books", books); + response.put("page", pagedResult.getNumber()); + response.put("pages", pagedResult.getTotalPages()); + response.put("total", pagedResult.getTotalElements()); + + return response; + } + + /** + * Retrieves all categories. + * Caches the result to avoid hitting the database on repeated requests. + * + * @return an Iterable of categories. + */ + @GetMapping("/categories") + @Cacheable(value = "categoriesCache") + public Iterable getCategories() { + return categoryRepository.findAll(); + } + + @GetMapping("/{isbn}") + public Book getIsbn(@PathVariable("isbn") String isbn) { + Optional book = bookRepository.findById(isbn); + if (book.isPresent()) { + return book.get(); + } else { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/UserController.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/UserController.java new file mode 100644 index 000000000..b399aa230 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/controller/UserController.java @@ -0,0 +1,42 @@ +package id.my.hendisantika.springbootredissample.controller; + +import id.my.hendisantika.springbootredissample.model.User; +import id.my.hendisantika.springbootredissample.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.49 + * To change this template use File | Settings | File Templates. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/users") +public class UserController { + + private final UserRepository userRepository; + + @GetMapping + public Iterable getUsers(@RequestParam(defaultValue = "yuji@yopmail.com") String email) { + if (email.isEmpty()) { + return userRepository.findAll(); + } else { + Optional user = Optional.ofNullable(userRepository.findFirstByEmail(email)); + return user.isPresent() ? List.of(user.get()) : Collections.emptyList(); + } + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Book.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Book.java new file mode 100644 index 000000000..5557a8536 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Book.java @@ -0,0 +1,81 @@ +package id.my.hendisantika.springbootredissample.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Reference; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.37 + * To change this template use File | Settings | File Templates. + */ + +/** + * Represents a book entity stored in a Redis database. + * This class is annotated with {@code @RedisHash} to indicate that it should be stored as a Redis hash. + * It uses Lombok annotations to automatically generate boilerplate code like getters, setters, + * and {@code equals()} and {@code hashCode()} methods. + * + *

+ * The {@code Book} class includes various attributes related to a book, such as its title, description, + * language, and more. It also maintains relationships with authors and categories. + *

+ * + *

+ * The {@code @EqualsAndHashCode.Include} annotation is applied to the {@code id} field to include it + * in the {@code equals()} and {@code hashCode()} methods generated by Lombok. + *

+ * + *

+ * The class supports adding categories to a book through the {@code addCategory} method. + *

+ * + *
{@code
+ * Example usage:
+ * Book book = new Book();
+ * book.setTitle("Redis in Action");
+ * book.addCategory(new Category("Technology"));
+ * }
+ * + * @author Lynne + */ +@Data +@RedisHash +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class Book implements Serializable { + + @Id + @EqualsAndHashCode.Include + private String id; + + private String title; + private String subtitle; + private String description; + private String language; + private Long pageCount; + private String thumbnail; + private Double price; + private String currency; + private String infoLink; + + private Set authors; + + @Reference + private Set categories = new HashSet(); + + public void addCategory(Category category) { + categories.add(category); + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/BookRating.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/BookRating.java new file mode 100644 index 000000000..f8df13875 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/BookRating.java @@ -0,0 +1,38 @@ +package id.my.hendisantika.springbootredissample.model; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Reference; +import org.springframework.data.redis.core.RedisHash; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.38 + * To change this template use File | Settings | File Templates. + */ +@Data +@Builder +@RedisHash +public class BookRating { + @Id + private String id; + + @NotNull + @Reference + private User user; + + @NotNull + @Reference + private Book book; + + @NotNull + private Integer rating; +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Category.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Category.java new file mode 100644 index 000000000..2f0856c8b --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Category.java @@ -0,0 +1,32 @@ +package id.my.hendisantika.springbootredissample.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.38 + * To change this template use File | Settings | File Templates. + */ +@Data +@Builder +@RedisHash +@AllArgsConstructor +@NoArgsConstructor +public class Category implements Serializable { + @Id + private String id; + private String name; +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Role.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Role.java new file mode 100644 index 000000000..b185bc108 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/Role.java @@ -0,0 +1,52 @@ +package id.my.hendisantika.springbootredissample.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.38 + * To change this template use File | Settings | File Templates. + */ + +/** + * Represents a role entity stored in Redis. + * + *

This class is annotated with {@link Builder} to provide a builder pattern for creating instances, and + * {@link Data} to generate getters, setters, equals, hashCode, and toString methods. The class is also annotated + * with {@link RedisHash} to indicate that it is a Redis hash stored in Redis.

+ */ +@Data +@Builder +@RedisHash +@AllArgsConstructor +@NoArgsConstructor +public class Role { + + /** + * The unique identifier for the role. + * + *

This field is marked with {@link Id} to indicate it is the primary key in Redis.

+ */ + @Id + private String id; + + /** + * The name of the role. + * + *

This field is indexed to allow for efficient querying by role name.

+ */ + @Indexed + private String name; +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/User.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/User.java new file mode 100644 index 000000000..542f752b1 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/model/User.java @@ -0,0 +1,128 @@ +package id.my.hendisantika.springbootredissample.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Reference; +import org.springframework.data.annotation.Transient; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.util.HashSet; +import java.util.Set; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.40 + * To change this template use File | Settings | File Templates. + */ + +/** + * Represents a user entity stored in Redis with associated attributes and behavior. + * + *

This class is annotated with {@link EqualsAndHashCode} and {@link ToString} to include only + * explicitly specified fields in equality checks and string representation, respectively. It also + * uses the {@link Data} annotation to generate getters, setters, and other common methods. + *

+ * + *

Each user has an ID, a name, an email, a password, and an optional password confirmation. + * Additionally, users can be associated with multiple roles. + *

+ * + *

The class is annotated with {@link RedisHash} to indicate that it is a Redis hash stored in Redis.

+ */ +@Data +@RedisHash +@JsonIgnoreProperties(value = {"password", "passwordConfirm"}, allowSetters = true) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString(onlyExplicitlyIncluded = true) +@AllArgsConstructor +@NoArgsConstructor +public class User { + + /** + * The unique identifier for the user. + * + *

This field is marked with {@link Id} to indicate it is the primary key in Redis. It is also + * included in the string representation of the user.

+ */ + @Id + @ToString.Include + private String id; + + /** + * The name of the user. + * + *

This field must be non-null and have a length between 2 and 48 characters. It is included in + * the string representation of the user.

+ */ + @NotNull + @Size(min = 2, max = 48) + @ToString.Include + private String name; + + /** + * The email address of the user. + * + *

This field must be non-null, a valid email address, and is included in both the equality + * checks and string representation of the user. It is also indexed for faster lookups.

+ */ + @Email + @NotNull + @EqualsAndHashCode.Include + @ToString.Include + @Indexed + private String email; + + /** + * The password for the user. + * + *

This field is required but not included in equality checks or the string representation of the + * user for security reasons.

+ */ + @NotNull + private String password; + + /** + * A confirmation password used for validation purposes. + * + *

This field is transient and not persisted in Redis. It is used for password confirmation + * during user creation or updates.

+ */ + @Transient + private String passwordConfirm; + + /** + * The roles associated with the user. + * + *

This field is a set of {@link Role} objects and is used to manage the user's roles. It is not + * included in equality checks or the string representation of the user. The default is an empty + * set.

+ */ + @Reference + private Set roles = new HashSet(); + + /** + * Adds a role to the user. + * + *

This method allows adding a {@link Role} to the user's set of roles.

+ * + * @param role the {@link Role} to be added + */ + public void addRole(Role role) { + roles.add(role); + } +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRatingRepository.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRatingRepository.java new file mode 100644 index 000000000..a89272405 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRatingRepository.java @@ -0,0 +1,20 @@ +package id.my.hendisantika.springbootredissample.repository; + +import id.my.hendisantika.springbootredissample.model.BookRating; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.41 + * To change this template use File | Settings | File Templates. + */ +@Repository +public interface BookRatingRepository extends CrudRepository { +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRepository.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRepository.java new file mode 100644 index 000000000..9a9a96855 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/BookRepository.java @@ -0,0 +1,21 @@ +package id.my.hendisantika.springbootredissample.repository; + +import id.my.hendisantika.springbootredissample.model.Book; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.41 + * To change this template use File | Settings | File Templates. + */ +@Repository +public interface BookRepository extends PagingAndSortingRepository, CrudRepository { +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/CategoryRepository.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/CategoryRepository.java new file mode 100644 index 000000000..54f6c98e7 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/CategoryRepository.java @@ -0,0 +1,20 @@ +package id.my.hendisantika.springbootredissample.repository; + +import id.my.hendisantika.springbootredissample.model.Category; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.41 + * To change this template use File | Settings | File Templates. + */ +@Repository +public interface CategoryRepository extends CrudRepository { +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/RoleRepository.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/RoleRepository.java new file mode 100644 index 000000000..e735bcabb --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/RoleRepository.java @@ -0,0 +1,38 @@ +package id.my.hendisantika.springbootredissample.repository; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.42 + * To change this template use File | Settings | File Templates. + */ + +import id.my.hendisantika.springbootredissample.model.Role; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Repository interface for {@link Role} entities. + * + *

This interface extends {@link CrudRepository} to provide basic CRUD operations for {@link Role} entities. + * It also includes a custom query method to find a {@link Role} by its name.

+ */ +@Repository +public interface RoleRepository extends CrudRepository { + + /** + * Finds the first {@link Role} entity by its name. + * + *

This method retrieves a {@link Role} entity where the name matches the specified role name. + * If multiple roles have the same name, only the first one is returned.

+ * + * @param role the name of the role to search for + * @return the first {@link Role} with the given name, or {@code null} if no role is found + */ + Role findFirstByname(String role); +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/UserRepository.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/UserRepository.java new file mode 100644 index 000000000..2bb563ace --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/java/id/my/hendisantika/springbootredissample/repository/UserRepository.java @@ -0,0 +1,21 @@ +package id.my.hendisantika.springbootredissample.repository; + +import id.my.hendisantika.springbootredissample.model.User; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by IntelliJ IDEA. + * Project : spring-boot-redis-sample + * User: hendisantika + * Link: s.id/hendisantika + * Email: hendisantika@yahoo.co.id + * Telegram : @hendisantika34 + * Date: 05/04/25 + * Time: 07.42 + * To change this template use File | Settings | File Templates. + */ +@Repository +public interface UserRepository extends CrudRepository { + User findFirstByEmail(String email); +} diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/application.properties b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/application.properties new file mode 100644 index 000000000..58c226445 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.application.name=bookstore +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.password=${REDIS_PASSWORD:53cret} +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +app.numberOfRatings=5000 +app.ratingStars=5 diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/books/_books.json b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/books/_books.json new file mode 100644 index 000000000..e2813fba2 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/books/_books.json @@ -0,0 +1,52 @@ +[ + { + "id": "book123", + "title": "The Hitchhiker's Guide to the Galaxy", + "subtitle": "A Trilogy in Five Parts", + "description": "Seconds before the Earth is demolished to make way for a galactic freeway, Arthur Dent is plucked off the planet by his friend Ford Prefect, a researcher for the revised edition of The Hitchhiker's Guide to the Galaxy who, for the last fifteen years, has been posing as an out-of-work actor.", + "language": "English", + "pageCount": 224, + "thumbnail": "https://example.com/hitchhikers-guide-thumbnail.jpg", + "price": 12.99, + "currency": "USD", + "infoLink": "https://example.com/hitchhikers-guide-info", + "authors": [ + "Douglas Adams" + ], + "categories": [ + { + "id": "category_scifi", + "name": "Science Fiction" + }, + { + "id": "category_humor", + "name": "Humor" + } + ] + }, + { + "id": "book1234", + "title": "The Hitchhiker's Guide to the Galaxy", + "subtitle": "A Trilogy in Five Parts", + "description": "Seconds before the Earth is demolished to make way for a galactic freeway, Arthur Dent is plucked off the planet by his friend Ford Prefect, a researcher for the revised edition of The Hitchhiker's Guide to the Galaxy who, for the last fifteen years, has been posing as an out-of-work actor.", + "language": "English", + "pageCount": 212, + "thumbnail": "https://example.com/hitchhikers-guide-thumbnail.jpg", + "price": 12.99, + "currency": "USD", + "infoLink": "https://example.com/hitchhikers-guide-info", + "authors": [ + "Douglas Adams" + ], + "categories": [ + { + "id": "category_scifi", + "name": "Science Fiction" + }, + { + "id": "category_humor", + "name": "Humor" + } + ] + } +] diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/users/users.json b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/users/users.json new file mode 100644 index 000000000..0891454aa --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/main/resources/data/users/users.json @@ -0,0 +1,71 @@ +[ + { + "id": "18c72298-80e6-401a-be63-eef50ed2f4c0", + "name": "Yuji Itadori", + "email": "yuji.itadori@jujutsukaisen.com", + "password": "hashedPasswordYuji", + "passwordConfirm": "hashedPasswordYuji", + "roles": [ + { + "id": "admin", + "name": "admin" + } + ] + }, + { + "id": "18c72298-80e6-401a-be63-eef50ed2f4c1", + "name": "Megumi Fushiguro", + "email": "megumi.fushiguro@jujutsukaisen.com", + "password": "hashedPasswordMegumi", + "passwordConfirm": "hashedPasswordMegumi", + "roles": [ + { + "id": "customer", + "name": "customer" + } + ] + }, + { + "id": "18c72298-80e6-401a-be63-eef50ed2f4c2", + "name": "Nobara Kugisaki", + "email": "nobara.kugisaki@jujutsukaisen.com", + "password": "hashedPasswordNobara", + "passwordConfirm": "hashedPasswordNobara", + "roles": [ + { + "id": "customer", + "name": "customer" + } + ] + }, + { + "id": "18c72298-80e6-401a-be63-eef50ed2f4c3", + "name": "Satoru Gojo", + "email": "satoru.gojo@jujutsukaisen.com", + "password": "hashedPasswordSatoru", + "passwordConfirm": "hashedPasswordSatoru", + "roles": [ + { + "id": "customer", + "name": "customer" + } + ] + }, + { + "id": "18c72298-80e6-401a-be63-eef50ed2f4c4", + "name": "Ryomen Sukuna", + "email": "ryomen.sukuna@jujutsukaisen.com", + "password": "hashedPasswordSukuna", + "passwordConfirm": "hashedPasswordSukuna", + "roles": [ + { + "id": "customer", + "name": "customer" + }, + { + "id": "customer", + "name": "customer" + } + ] + } +] diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/test/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplicationTests.java b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/test/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplicationTests.java new file mode 100644 index 000000000..ac8586e93 --- /dev/null +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/src/test/java/id/my/hendisantika/springbootredissample/SpringBootRedisSampleApplicationTests.java @@ -0,0 +1,56 @@ +package id.my.hendisantika.springbootredissample; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Testcontainers +class SpringBootRedisSampleApplicationTests { + + private static final String REDIS_IMAGE_NAME = "redis:7.0-alpine"; + private static final int REDIS_PORT = 6379; + @Container + private static final GenericContainer redis = + new GenericContainer<>(DockerImageName.parse(REDIS_IMAGE_NAME)) + .withExposedPorts(REDIS_PORT); + @Autowired + private StringRedisTemplate redisTemplate; + + @BeforeAll + static void beforeAll() { + // No need to start the container here, @Container will handle it + } + + @AfterAll + static void afterAll() { + // No need to stop the container here, @Container will handle it + } + + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.redis.host", redis::getHost); + registry.add("spring.data.redis.port", () -> redis.getMappedPort(REDIS_PORT)); + } + + @Test + void testSaveKeyValueAndGetValue() { + final String key = "testKey", value = "testValue"; + + redisTemplate.opsForValue().set(key, value); + + assertThat(redisTemplate.opsForValue().get(key)).isEqualTo(value); + } + +} diff --git a/jdk_21_maven/em/embedded/rest/pom.xml b/jdk_21_maven/em/embedded/rest/pom.xml index 143794554..6d484f746 100644 --- a/jdk_21_maven/em/embedded/rest/pom.xml +++ b/jdk_21_maven/em/embedded/rest/pom.xml @@ -13,6 +13,7 @@ person-controller + spring-boot-redis-sample diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml new file mode 100644 index 000000000..9145f1924 --- /dev/null +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + evomaster-benchmark-jdk21-em-embedded-rest-spring-boot-redis-sample + jar + + + org.evomaster + evomaster-benchmark-jdk21-em-embedded-rest + 4.3.0 + + + + + + org.springframework.boot + spring-boot-starter-parent + 3.5.6 + pom + import + + + + + + + org.testcontainers + testcontainers + compile + + + junit + junit + compile + 4.11 + + + + \ No newline at end of file diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java new file mode 100644 index 000000000..7cb994826 --- /dev/null +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java @@ -0,0 +1,110 @@ +package em.embedded.com.hendisantika; + +import com.hendisantika.SpringBootRedisSampleApplication; +import org.evomaster.client.java.controller.EmbeddedSutController; +import org.evomaster.client.java.controller.InstrumentedSutStarter; +import org.evomaster.client.java.controller.api.dto.SutInfoDto; +import org.evomaster.client.java.controller.api.dto.auth.AuthenticationDto; +import org.evomaster.client.java.controller.problem.ProblemInfo; +import org.evomaster.client.java.controller.problem.RestProblem; +import org.evomaster.client.java.sql.DbSpecification; +import org.evomaster.client.java.controller.redis.ReflectionBasedRedisClient; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; +import java.util.Map; + +public class EmbeddedEvoMasterController extends EmbeddedSutController { + + private static final int REDIS_PORT = 6379; + + private final GenericContainer redis = new GenericContainer<>("redis:7.0") + .withExposedPorts(REDIS_PORT); + + private ConfigurableApplicationContext ctx; + private String redisHost; + private int redisPort; + + public static void main(String[] args) { + int port = 40100; + if (args.length > 0) port = Integer.parseInt(args[0]); + EmbeddedEvoMasterController controller = new EmbeddedEvoMasterController(port); + InstrumentedSutStarter starter = new InstrumentedSutStarter(controller); + starter.start(); + } + + public EmbeddedEvoMasterController() { this(40100); } + + public EmbeddedEvoMasterController(int port) { setControllerPort(port); } + + @Override + public String startSut() { + redis.start(); + redisHost = redis.getHost(); + redisPort = redis.getMappedPort(REDIS_PORT); + + ctx = new SpringApplicationBuilder(SpringBootRedisSampleApplication.class) + .properties( + "--server.port=0", + "spring.data.redis.host=" + redisHost, + "spring.data.redis.port=" + redisPort + ).run(); + + return "http://localhost:" + getSutPort(); + } + + @Override + public void stopSut() { + if (ctx != null) { ctx.stop(); ctx.close(); } + if (redis.isRunning()) redis.stop(); + } + + @Override + public void resetStateOfSUT() { + try (ReflectionBasedRedisClient client = + new ReflectionBasedRedisClient(redisHost, redisPort, 0)) { + client.flushAll(); + } + } + + @Override + public ReflectionBasedRedisClient getRedisConnection() { + return new ReflectionBasedRedisClient(redisHost, redisPort, 0); + } + + @Override + public boolean isSutRunning() { + return ctx != null && ctx.isRunning(); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.hendisantika."; + } + + @Override + public ProblemInfo getProblemInfo() { + return new RestProblem( + "http://localhost:" + getSutPort() + "/v3/api-docs", null + ); + } + + @Override + public SutInfoDto.OutputFormat getPreferredOutputFormat() { + return SutInfoDto.OutputFormat.JAVA_JUNIT_5; + } + + @Override + public List getInfoForAuthentication() { return null; } + + @Override + public List getDbSpecifications() { return null; } + + protected int getSutPort() { + return (Integer)((Map) ctx.getEnvironment() + .getPropertySources().get("server.ports").getSource()) + .get("local.server.port"); + } +} \ No newline at end of file diff --git a/openapi-swagger/spring-boot-redis-sample.json b/openapi-swagger/spring-boot-redis-sample.json new file mode 100644 index 000000000..f1bb06044 --- /dev/null +++ b/openapi-swagger/spring-boot-redis-sample.json @@ -0,0 +1,207 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/api/users": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getUsers", + "parameters": [ + { + "name": "email", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "yuji@yopmail.com" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/books": { + "get": { + "tags": [ + "book-controller" + ], + "operationId": "getBooks", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object", + "additionalProperties": { + + } + } + } + } + } + } + } + }, + "/api/books/{isbn}": { + "get": { + "tags": [ + "book-controller" + ], + "operationId": "getIsbn", + "parameters": [ + { + "name": "isbn", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + }, + "/api/books/categories": { + "get": { + "tags": [ + "book-controller" + ], + "operationId": "getCategories", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Book": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "subtitle": { + "type": "string" + }, + "description": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer", + "format": "int64" + }, + "thumbnail": { + "type": "string" + }, + "price": { + "type": "number", + "format": "double" + }, + "currency": { + "type": "string" + }, + "infoLink": { + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "categories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Category" + }, + "uniqueItems": true + } + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file From 6a08e0ace94b0a2ca919dea8cae3fc99ca364d78 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Mon, 10 Aug 2026 22:29:29 -0300 Subject: [PATCH 2/8] modified version in POM for evomaster-benchmark-jdk21-em-embedded-rest --- jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml index 9145f1924..750848746 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml @@ -10,7 +10,7 @@ org.evomaster evomaster-benchmark-jdk21-em-embedded-rest - 4.3.0 + 4.3.1-SNAPSHOT From d33e41242442bd272c9306c304da0b1d73bb0b39 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Tue, 11 Aug 2026 00:44:32 -0300 Subject: [PATCH 3/8] pipeline fixes --- .../em/embedded/rest/spring-boot-redis-sample/pom.xml | 5 +++++ .../com/hendisantika/EmbeddedEvoMasterController.java | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml index 750848746..643a58075 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml @@ -15,6 +15,11 @@ + + id.my.hendisantika + spring-boot-redis-sample + 0.0.1-SNAPSHOT + org.springframework.boot spring-boot-starter-parent diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java index 7cb994826..0d2b130d7 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java @@ -63,9 +63,11 @@ public void stopSut() { @Override public void resetStateOfSUT() { - try (ReflectionBasedRedisClient client = - new ReflectionBasedRedisClient(redisHost, redisPort, 0)) { + ReflectionBasedRedisClient client = new ReflectionBasedRedisClient(redisHost, redisPort, 0); + try { client.flushAll(); + } finally { + client.close(); } } From 3c81e8b9e9aa65e6546504f8c20e4ea8637526ce Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Tue, 11 Aug 2026 01:44:11 -0300 Subject: [PATCH 4/8] pipeline fixes --- .../embedded/rest/spring-boot-redis-sample/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml index 643a58075..29876f75b 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/pom.xml @@ -15,15 +15,10 @@ - - id.my.hendisantika - spring-boot-redis-sample - 0.0.1-SNAPSHOT - org.springframework.boot spring-boot-starter-parent - 3.5.6 + 4.1.0 pom import @@ -31,6 +26,11 @@ + + id.my.hendisantika + spring-boot-redis-sample + 0.0.1-SNAPSHOT + org.testcontainers testcontainers From a7fdb4abaaedfe6fe8223d9a54d4e1de80a92c00 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Tue, 11 Aug 2026 02:36:11 -0300 Subject: [PATCH 5/8] pipeline fixes --- .../cs/rest/spring-boot-redis-sample/pom.xml | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml index 2c6700338..70107f76a 100644 --- a/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml +++ b/jdk_21_maven/cs/rest/spring-boot-redis-sample/pom.xml @@ -2,20 +2,41 @@ 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 4.1.0 - - + + + + + + + + + id.my.hendisantika spring-boot-redis-sample 0.0.1-SNAPSHOT spring-boot-redis-sample spring-boot-redis-sample + + + + + org.springframework.boot + spring-boot-starter-parent + 4.1.0 + pom + import + + + + 21 + UTF-8 + UTF-8 + 3.15.0 + org.springframework.boot @@ -29,7 +50,7 @@ --> + without enabling the full security filter chain --> org.springframework.security spring-security-crypto @@ -119,7 +140,10 @@ org.apache.maven.plugins maven-compiler-plugin + ${maven-compiler-plugin.version} + 21 + 21 org.springframework.boot @@ -135,7 +159,17 @@ org.springframework.boot spring-boot-maven-plugin + 4.1.0 + + + + repackage + + + + spring-boot-redis-sample + sut org.projectlombok From 22305bc4a2a277fc2d9464965a040dad456a9183 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Tue, 11 Aug 2026 03:17:41 -0300 Subject: [PATCH 6/8] pipeline fix --- .../embedded/com/hendisantika/EmbeddedEvoMasterController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java index 0d2b130d7..b6561ee0a 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java @@ -1,6 +1,6 @@ package em.embedded.com.hendisantika; -import com.hendisantika.SpringBootRedisSampleApplication; +import id.my.hendisantika.springbootredissample.SpringBootRedisSampleApplication; import org.evomaster.client.java.controller.EmbeddedSutController; import org.evomaster.client.java.controller.InstrumentedSutStarter; import org.evomaster.client.java.controller.api.dto.SutInfoDto; From 2c030e7670150535473c9329491f5f4ced52ce4b Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Tue, 11 Aug 2026 17:53:09 -0300 Subject: [PATCH 7/8] statistics and readme --- README.md | 4 +++- statistics/data.csv | 2 +- statistics/suts.R | 1 + statistics/table_emb.md | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b889de715..a8a48b417 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ How to setup authentication information, based on the current content of the ini Auth configuration files can found in the [auth](auth) folder. -### REST: Java/Kotlin (36) +### REST: Java/Kotlin (37) * **Bibliothek** (MIT), [jdk_17_gradle/cs/rest/bibliothek](jdk_17_gradle/cs/rest/bibliothek), from [https://github.com/PaperMC/bibliothek](https://github.com/PaperMC/bibliothek) @@ -145,6 +145,8 @@ Auth configuration files can found in the [auth](auth) folder. * **Spring-batch-rest** (Apache), [jdk_8_maven/cs/rest/original/spring-batch-rest](jdk_8_maven/cs/rest/original/spring-batch-rest), from [https://github.com/chrisgleissner/spring-batch-rest](https://github.com/chrisgleissner/spring-batch-rest) +* **Spring Boot Redis Sample** (not-known license), [jdk_21_maven/cs/rest/spring-boot-redis-sample](jdk_21_maven/cs/rest/spring-boot-redis-sample), from [https://github.com/hendisantika/spring-boot-redis-sample](https://github.com/hendisantika/spring-boot-redis-sample) + * **Spring Boot Restful API Example** (MIT), [jdk_17_maven/cs/rest/spring-rest-example](jdk_17_maven/cs/rest/spring-rest-example), from [https://github.com/phantasmicmeans/spring-boot-restful-api-example](https://github.com/phantasmicmeans/spring-boot-restful-api-example) * **Spring ECommerce** (not-known license), [jdk_8_maven/cs/rest/original/spring-ecommerce](jdk_8_maven/cs/rest/original/spring-ecommerce), from [https://github.com/SaiUpadhyayula/SpringAngularEcommerce](https://github.com/SaiUpadhyayula/SpringAngularEcommerce) diff --git a/statistics/data.csv b/statistics/data.csv index 82710e553..a9df7f958 100644 --- a/statistics/data.csv +++ b/statistics/data.csv @@ -47,4 +47,4 @@ TRUE,signal-registration,gRPC,Java,JDK 17,Maven,177,13652,,UNDEFINED,5,FALSE,htt TRUE,webgoat,REST,Java,JDK 21,Maven,355,27638,H2,GPL,204,TRUE,https://github.com/WebGoat/WebGoat FALSE,ind0,REST,Java,JDK 8,Gradle,103,17039,PostgreSQL,Proprietary,20,FALSE,UNDEFINED FALSE,ind1,REST,Java;Kotlin,JDK 11,Gradle,163,15240,PostgreSQL,Proprietary,53,FALSE,UNDEFINED - +TRUE,spring-boot-redis-sample,REST,Java,JDK 21,Maven,18,868,Redis,UNDEFINED,4,FALSE,https://github.com/hendisantika/spring-boot-redis-sample \ No newline at end of file diff --git a/statistics/suts.R b/statistics/suts.R index a39817b9a..f45fe484d 100644 --- a/statistics/suts.R +++ b/statistics/suts.R @@ -36,6 +36,7 @@ suts <- function(){ "session-service", "spring-actuator-demo", "spring-batch-rest", +"spring-boot-redis-sample", "spring-ecommerce", "spring-rest-example", "swagger-petstore", diff --git a/statistics/table_emb.md b/statistics/table_emb.md index bff09c9a9..f793f1002 100644 --- a/statistics/table_emb.md +++ b/statistics/table_emb.md @@ -36,6 +36,7 @@ |REST|__session-service__|1471|15|8|Java|JDK 8|Maven|MongoDB|| |REST|__spring-actuator-demo__|117|5|2|Java|JDK 8|Maven||✓| |REST|__spring-batch-rest__|3668|65|5|Java|JDK 8|Maven||| +|REST|__spring-boot-redis-sample__|868|18|4|Java|JDK 21|Maven|Redis|| |REST|__spring-ecommerce__|2223|58|26|Java|JDK 8|Maven|MongoOB, Redis, Elasticsearch|✓| |REST|__spring-rest-example__|1426|32|9|Java|JDK 17|Maven|MySQL|| |REST|__swagger-petstore__|1631|23|19|Java|JDK 8|Maven||| From 25bab9ee167e1105316037cf704014c7ce00dc81 Mon Sep 17 00:00:00 2001 From: Alexander Szyrej Date: Thu, 13 Aug 2026 01:30:33 -0300 Subject: [PATCH 8/8] Added external controller for spring boot redis sample --- .../EmbeddedEvoMasterController.java | 2 +- jdk_21_maven/em/external/rest/pom.xml | 1 + .../rest/spring-boot-redis-sample/pom.xml | 61 +++++++ .../ExternalEvoMasterController.java | 162 ++++++++++++++++++ 4 files changed, 225 insertions(+), 1 deletion(-) rename jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/{com/hendisantika => id/my/hendisantika/springbootredissample}/EmbeddedEvoMasterController.java (98%) create mode 100644 jdk_21_maven/em/external/rest/spring-boot-redis-sample/pom.xml create mode 100644 jdk_21_maven/em/external/rest/spring-boot-redis-sample/src/main/java/em/external/id/my/hendisantika/springbootredissample/ExternalEvoMasterController.java diff --git a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/id/my/hendisantika/springbootredissample/EmbeddedEvoMasterController.java similarity index 98% rename from jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java rename to jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/id/my/hendisantika/springbootredissample/EmbeddedEvoMasterController.java index b6561ee0a..fb7c46dbc 100644 --- a/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/com/hendisantika/EmbeddedEvoMasterController.java +++ b/jdk_21_maven/em/embedded/rest/spring-boot-redis-sample/src/main/java/em/embedded/id/my/hendisantika/springbootredissample/EmbeddedEvoMasterController.java @@ -1,4 +1,4 @@ -package em.embedded.com.hendisantika; +package em.embedded.id.my.hendisantika.springbootredissample; import id.my.hendisantika.springbootredissample.SpringBootRedisSampleApplication; import org.evomaster.client.java.controller.EmbeddedSutController; diff --git a/jdk_21_maven/em/external/rest/pom.xml b/jdk_21_maven/em/external/rest/pom.xml index 4a511f300..03934a0df 100644 --- a/jdk_21_maven/em/external/rest/pom.xml +++ b/jdk_21_maven/em/external/rest/pom.xml @@ -14,6 +14,7 @@ person-controller + spring-boot-redis-sample \ No newline at end of file diff --git a/jdk_21_maven/em/external/rest/spring-boot-redis-sample/pom.xml b/jdk_21_maven/em/external/rest/spring-boot-redis-sample/pom.xml new file mode 100644 index 000000000..567189243 --- /dev/null +++ b/jdk_21_maven/em/external/rest/spring-boot-redis-sample/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + evomaster-benchmark-jdk21-em-external-rest-spring-boot-redis-sample + jar + + + org.evomaster + evomaster-benchmark-jdk21-em-external-rest + 4.3.1-SNAPSHOT + + + + + org.testcontainers + testcontainers + compile + + + junit + junit + compile + 4.11 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + spring-boot-redis-sample-evomaster-runner + + + + em.external.id.my.hendisantika.ExternalEvoMasterController + org.evomaster.client.java.instrumentation.InstrumentingAgent + org.evomaster.client.java.instrumentation.InstrumentingAgent + true + true + + + + + + + + + + \ No newline at end of file diff --git a/jdk_21_maven/em/external/rest/spring-boot-redis-sample/src/main/java/em/external/id/my/hendisantika/springbootredissample/ExternalEvoMasterController.java b/jdk_21_maven/em/external/rest/spring-boot-redis-sample/src/main/java/em/external/id/my/hendisantika/springbootredissample/ExternalEvoMasterController.java new file mode 100644 index 000000000..e78863838 --- /dev/null +++ b/jdk_21_maven/em/external/rest/spring-boot-redis-sample/src/main/java/em/external/id/my/hendisantika/springbootredissample/ExternalEvoMasterController.java @@ -0,0 +1,162 @@ +package em.external.id.my.hendisantika.springbootredissample; + +import org.evomaster.client.java.controller.ExternalSutController; +import org.evomaster.client.java.controller.InstrumentedSutStarter; +import org.evomaster.client.java.controller.api.dto.SutInfoDto; +import org.evomaster.client.java.controller.api.dto.auth.AuthenticationDto; +import org.evomaster.client.java.controller.problem.ProblemInfo; +import org.evomaster.client.java.controller.problem.RestProblem; +import org.evomaster.client.java.controller.redis.ReflectionBasedRedisClient; +import org.evomaster.client.java.sql.DbSpecification; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; + +public class ExternalEvoMasterController extends ExternalSutController { + + private static final int DEFAULT_CONTROLLER_PORT = 40100; + private static final int DEFAULT_SUT_PORT = 12345; + private static final int REDIS_PORT = 6379; + + private final GenericContainer redis = new GenericContainer<>("redis:7.0") + .withExposedPorts(REDIS_PORT); + + private String redisHost; + private int redisPort; + + public static void main(String[] args) { + int controllerPort = DEFAULT_CONTROLLER_PORT; + if (args.length > 0) controllerPort = Integer.parseInt(args[0]); + + int sutPort = DEFAULT_SUT_PORT; + if (args.length > 1) sutPort = Integer.parseInt(args[1]); + + String jarLocation = "cs/rest/spring-boot-redis-sample/target"; + if (args.length > 2) jarLocation = args[2]; + if (!jarLocation.endsWith(".jar")) { + jarLocation += "/spring-boot-redis-sample-sut.jar"; + } + + int timeoutSeconds = 120; + if (args.length > 3) timeoutSeconds = Integer.parseInt(args[3]); + + String command = "java"; + if (args.length > 4) command = args[4]; + + ExternalEvoMasterController controller = + new ExternalEvoMasterController(controllerPort, jarLocation, sutPort, timeoutSeconds, command); + InstrumentedSutStarter starter = new InstrumentedSutStarter(controller); + starter.start(); + } + + private final int sutPort; + private final int timeoutSeconds; + private String jarLocation; + + public ExternalEvoMasterController() { + this(DEFAULT_CONTROLLER_PORT, "../target/spring-boot-redis-sample-sut.jar", DEFAULT_SUT_PORT, 120, "java"); + } + + public ExternalEvoMasterController(int controllerPort, String jarLocation, int sutPort, int timeoutSeconds, String command) { + this.sutPort = sutPort; + this.jarLocation = jarLocation; + this.timeoutSeconds = timeoutSeconds; + setControllerPort(controllerPort); + setJavaCommand(command); + } + + @Override + public String[] getInputParameters() { + return new String[]{ + "--server.port=" + sutPort, + "--spring.data.redis.host=" + redisHost, + "--spring.data.redis.port=" + redisPort + }; + } + + @Override + public String[] getJVMParameters() { + return new String[]{}; + } + + @Override + public String getBaseURL() { + return "http://localhost:" + sutPort; + } + + @Override + public String getPathToExecutableJar() { + return jarLocation; + } + + @Override + public String getLogMessageOfInitializedServer() { + return "Started SpringBootRedisSampleApplication in "; + } + + @Override + public long getMaxAwaitForInitializationInSeconds() { + return timeoutSeconds; + } + + @Override + public void preStart() { + redis.start(); + redisHost = redis.getHost(); + redisPort = redis.getMappedPort(REDIS_PORT); + } + + @Override + public void postStart() {} + + @Override + public void preStop() {} + + @Override + public void postStop() { + if (redis.isRunning()) redis.stop(); + } + + @Override + public void resetStateOfSUT() { + ReflectionBasedRedisClient client = new ReflectionBasedRedisClient(redisHost, redisPort, 0); + try { + client.flushAll(); + } finally { + client.close(); + } + } + + @Override + public ReflectionBasedRedisClient getRedisConnection() { + return new ReflectionBasedRedisClient(redisHost, redisPort, 0); + } + + @Override + public String getPackagePrefixesToCover() { + return "id.my.hendisantika."; + } + + @Override + public ProblemInfo getProblemInfo() { + return new RestProblem( + getBaseURL() + "/v3/api-docs", + null + ); + } + + @Override + public SutInfoDto.OutputFormat getPreferredOutputFormat() { + return SutInfoDto.OutputFormat.JAVA_JUNIT_5; + } + + @Override + public List getInfoForAuthentication() { + return null; + } + + @Override + public List getDbSpecifications() { + return null; + } +} \ No newline at end of file