diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..5a07848f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,255 @@
+# AGENTS.md - Agate Development Guide
+
+This document provides essential information for agentic coding agents working on the Agate authentication server project.
+
+## Project Overview
+
+Agate is a central authentication server for OBiBa applications built with:
+ - **Java 21** with Spring Boot 4.0.0
+- **Maven** multi-module project
+- **MongoDB** for data persistence
+- **Apache Shiro** for security
+- **Jersey/JAX-RS** for REST API
+- **Angular** frontend (TypeScript)
+
+## Module Structure
+
+```
+agate/
+├── agate-core/ # Core business logic, domain models, services
+├── agate-web-model/ # Generated protobuf models and DTOs
+├── agate-rest/ # REST API endpoints and mappers
+├── agate-ui/ # Angular frontend application
+├── agate-webapp/ # Spring Boot web application
+└── agate-dist/ # Distribution and packaging
+```
+
+## Build and Development Commands
+
+### Core Maven Commands
+```bash
+# Build entire project
+make install # or: mvn install
+
+# Build without tests
+mvn install -Dmaven.test.skip=true
+
+# Clean build
+make clean # or: mvn clean
+
+# Build specific modules
+make core # Build agate-core module
+make rest # Build agate-rest module
+make ui # Build agate-ui module
+make webapp # Build agate-webapp module
+```
+
+### Testing Commands
+```bash
+# Run all tests
+mvn test
+
+# Run tests for specific module
+mvn test -pl agate-core
+mvn test -pl agate-rest
+
+# Run single test class
+mvn test -Dtest=UserServiceTest
+
+# Run single test method
+mvn test -Dtest=UserServiceTest#testCreateUser
+
+# Run tests with specific profile
+mvn test -Pdev
+```
+
+### Development Server
+```bash
+# Start development server (HTTP:8081, HTTPS:8444)
+make debug
+
+# Start with production profile
+make run-prod
+
+# View logs
+make log # Tail main log
+make restlog # Tail REST log
+```
+
+### Other Useful Commands
+```bash
+# Dependency analysis
+make dependencies-tree
+make dependencies-update
+make plugins-update
+
+# Database operations
+make drop-mongo # Drop MongoDB database
+make clear-log # Clear log files
+```
+
+## Code Style Guidelines
+
+### Package Structure
+- Base package: `org.obiba.agate`
+- Follow standard Maven directory layout
+- Domain models in `org.obiba.agate.domain`
+- Services in `org.obiba.agate.service`
+- Repositories in `org.obiba.agate.repository`
+- REST resources in `org.obiba.agate.web.rest`
+- Controllers in `org.obiba.agate.web.controller`
+
+### Naming Conventions
+- **Classes**: PascalCase (e.g., `UserService`, `UserRepository`)
+- **Methods**: camelCase (e.g., `createUser()`, `findByEmail()`)
+- **Constants**: UPPER_SNAKE_CASE (e.g., `DEFAULT_PAGE_SIZE`)
+- **Variables**: camelCase with descriptive names
+- **Packages**: lowercase with dots separating words
+
+### Import Organization
+1. `java.*` and `jakarta.*` imports
+2. Third-party library imports
+3. `org.obiba.*` imports
+- Use wildcard imports only for constant classes
+- Remove unused imports
+
+### Annotations Usage
+- `@Inject` for dependency injection
+- `@Component` for Spring-managed beans
+- `@Service` for service layer classes
+- `@Repository` for data access classes
+- `@Document` for MongoDB entities
+- `@Path`, `@GET`, `@POST` for JAX-RS endpoints
+- `@RequiresRoles` for Shiro security
+- `@Timed` for metrics collection
+
+### Error Handling
+- Create specific exception classes extending `RuntimeException`
+- Use standard HTTP status codes in REST responses
+- Implement exception mappers for REST layers
+- Log errors at appropriate levels
+- Return meaningful error messages
+
+### Testing Guidelines
+- Test classes end with `Test` suffix
+- Use JUnit for unit tests
+- Use AssertJ for assertions
+- Mock dependencies with EasyMock
+- Follow AAA pattern (Arrange, Act, Assert)
+- Test both happy path and error scenarios
+
+### Security Patterns
+- Use Shiro annotations for method-level security
+- Apply principle of least privilege
+- Validate all input parameters
+- Sanitize output data
+- Use HTTPS in production
+- Implement proper session management
+
+### Database Patterns
+- Use `@Document` annotation for MongoDB entities
+- Add `@Indexed` for frequently queried fields
+- Extend `AbstractAuditableDocument` for audit trails
+- Use repository pattern for data access
+- Consider data consistency and transaction boundaries
+
+### API Design
+- Use RESTful conventions
+- Implement proper HTTP status codes
+- Use DTOs for API responses
+- Version APIs when breaking changes
+- Document endpoints with proper comments
+- Handle pagination for large datasets
+
+## Configuration Profiles
+
+### Available Profiles
+- `dev` - Development with debug features
+- `ci-build` - Continuous integration build
+- Default production settings
+
+### Environment Variables
+- `AGATE_HOME` - Application home directory
+- `AGATE_LOG` - Log file directory
+
+## Key Dependencies
+
+### Core Framework
+- Spring Boot 3.5.7
+- Spring Data MongoDB
+- Apache Shiro 1.13.0
+- Jersey 3.1.3
+
+### Utilities
+- Google Guava
+- Apache Commons Lang
+- Joda Time
+
+### Testing
+- JUnit
+- AssertJ
+- EasyMock
+
+## Development Notes
+
+- Java 21 is required (update alternatives if needed)
+- Use MongoDB for development and testing
+- Frontend builds are handled through Maven plugin
+- Follow GPL3 license requirements
+- Maintain backward compatibility when possible
+- Write comprehensive tests for new features
+
+## Common Patterns
+
+### Service Layer
+```java
+@Service
+public class UserService {
+
+ @Inject
+ private UserRepository userRepository;
+
+ @Timed
+ public User createUser(UserDTO dto) {
+ validateUserDTO(dto);
+ User user = new User();
+ // mapping logic
+ return userRepository.save(user);
+ }
+}
+```
+
+### REST Resource
+```java
+@Component
+@Path("/users")
+@RequiresRoles("agate-administrator")
+public class UsersResource {
+
+ @Inject
+ private UserService userService;
+
+ @POST
+ @Timed
+ public Response createUser(UserDTO dto) {
+ // implementation
+ }
+}
+```
+
+### Domain Entity
+```java
+@Document
+public class User extends AbstractAuditableDocument {
+
+ @Indexed(unique = true)
+ private String name;
+
+ @Indexed(unique = true)
+ private String email;
+
+ // getters and setters
+}
+```
+
+This guide should help agents understand the project structure, conventions, and common patterns used in Agate development.
\ No newline at end of file
diff --git a/agate-core/pom.xml b/agate-core/pom.xml
index b19f5a58..855b9a84 100644
--- a/agate-core/pom.xml
+++ b/agate-core/pom.xml
@@ -32,8 +32,12 @@
- org.eclipse.jetty.ee10
- jetty-ee10-servlet
+ org.eclipse.jetty.ee11
+ jetty-ee11-servlet
+
+
+ org.springframework.boot
+ spring-boot-jdbc
io.dropwizard.metrics
@@ -132,7 +136,7 @@
org.springframework.boot
- spring-boot-starter-aop
+ spring-boot-starter-aspectj
org.springframework.boot
diff --git a/agate-core/src/main/java/org/obiba/agate/security/AgateRealmFactory.java b/agate-core/src/main/java/org/obiba/agate/security/AgateRealmFactory.java
index 99c6ee59..9c4e5864 100644
--- a/agate-core/src/main/java/org/obiba/agate/security/AgateRealmFactory.java
+++ b/agate-core/src/main/java/org/obiba/agate/security/AgateRealmFactory.java
@@ -21,7 +21,6 @@
import org.obiba.oidc.shiro.realm.OIDCRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
@@ -131,8 +130,6 @@ private JndiLdapContextFactory createLdapContextFactory(String configName, Strin
}
private DataSource createDataSource(String configName, JdbcRealmConfig content) {
- DataSourceBuilder builder = DataSourceBuilder.create();
-
String url = content.getUrl();
String driverClassName = getValidDriverClassName(configName, url);
@@ -144,14 +141,13 @@ private DataSource createDataSource(String configName, JdbcRealmConfig content)
return null;
}
- builder
- .type(DriverManagerDataSource.class)
- .url(url)
- .username(content.getUsername())
- .password(content.getPassword())
- .driverClassName(driverClassName);
+ DriverManagerDataSource ds = new DriverManagerDataSource();
+ ds.setUrl(url);
+ ds.setUsername(content.getUsername());
+ ds.setPassword(content.getPassword());
+ ds.setDriverClassName(driverClassName);
- return builder.build();
+ return ds;
}
private String getValidDriverClassName(String configName, String url) {
diff --git a/agate-webapp/pom.xml b/agate-webapp/pom.xml
index 57b16819..db4bb79e 100644
--- a/agate-webapp/pom.xml
+++ b/agate-webapp/pom.xml
@@ -77,8 +77,8 @@
- org.eclipse.jetty.ee10
- jetty-ee10-servlets
+ org.eclipse.jetty.ee11
+ jetty-ee11-servlets
diff --git a/agate-webapp/src/main/java/org/obiba/agate/Application.java b/agate-webapp/src/main/java/org/obiba/agate/Application.java
index 93e5a3df..73bf62a1 100644
--- a/agate-webapp/src/main/java/org/obiba/agate/Application.java
+++ b/agate-webapp/src/main/java/org/obiba/agate/Application.java
@@ -18,7 +18,6 @@
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -26,7 +25,7 @@
import jakarta.inject.Inject;
import java.util.Arrays;
-@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }, scanBasePackages = "org.obiba")
+@SpringBootApplication(scanBasePackages = "org.obiba")
@EnableScheduling
public class Application implements InitializingBean {
diff --git a/agate-webapp/src/main/java/org/obiba/agate/config/WebConfiguration.java b/agate-webapp/src/main/java/org/obiba/agate/config/WebConfiguration.java
index 32855035..43e3a1a2 100644
--- a/agate-webapp/src/main/java/org/obiba/agate/config/WebConfiguration.java
+++ b/agate-webapp/src/main/java/org/obiba/agate/config/WebConfiguration.java
@@ -33,8 +33,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
-import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
+
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer;
@@ -54,7 +53,7 @@
@ComponentScan({"org.obiba.agate", "org.obiba.shiro"})
@PropertySource("classpath:agate-webapp.properties")
@AutoConfigureAfter(SecurityConfiguration.class)
-public class WebConfiguration implements ServletContextInitializer, JettyServerCustomizer, EnvironmentAware {
+public class WebConfiguration implements ServletContextInitializer, EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(WebConfiguration.class);
@@ -87,25 +86,7 @@ public void setEnvironment(Environment environment) {
contextPath = environment.getProperty("server.servlet.context-path", "");
}
- @Bean
- public WebServerFactoryCustomizer containerCustomizer() throws Exception {
- return factory -> {
- factory.setServerCustomizers(Collections.singleton(WebConfiguration.this)); // FIXME is this necessary?
- if (!Strings.isNullOrEmpty(contextPath) && contextPath.startsWith("/"))
- factory.setContextPath(contextPath);
- };
- }
- @Override
- public void customize(Server server) {
- customizeSsl(server);
-
- GzipHandler gzipHandler = new GzipHandler();
- gzipHandler.setIncludedMethods("PUT", "POST", "GET");
- gzipHandler.setInflateBufferSize(2048);
- gzipHandler.setHandler(server.getHandler());
- server.setHandler(gzipHandler);
- }
private void customizeSsl(Server server) {
if (httpsPort <= 0) return;
diff --git a/agate-webapp/src/main/java/org/obiba/agate/config/locale/AngularCookieLocaleResolver.java b/agate-webapp/src/main/java/org/obiba/agate/config/locale/AngularCookieLocaleResolver.java
index ad4102c2..3d1ad744 100644
--- a/agate-webapp/src/main/java/org/obiba/agate/config/locale/AngularCookieLocaleResolver.java
+++ b/agate-webapp/src/main/java/org/obiba/agate/config/locale/AngularCookieLocaleResolver.java
@@ -48,9 +48,8 @@ public AngularCookieLocaleResolver(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
- @Override
protected Locale determineDefaultLocale(HttpServletRequest request) {
- Locale defaultLocale = super.determineDefaultLocale(request);
+ Locale defaultLocale = Locale.getDefault();
// validate default locale, which could come from the Accept-Language header
List configLocales = configurationService.getConfiguration().getLocales();
List languageLocales = configLocales.stream()
@@ -59,6 +58,10 @@ protected Locale determineDefaultLocale(HttpServletRequest request) {
return languageLocales.isEmpty() ? configLocales.stream().findFirst().orElse(Locale.ENGLISH) : languageLocales.get(0);
}
+ protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
+ return TimeZone.getDefault();
+ }
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
parseLocaleCookieIfNecessary(request);
diff --git a/agate-webapp/src/main/java/org/obiba/agate/web/controller/ErrorControllerImpl.java b/agate-webapp/src/main/java/org/obiba/agate/web/controller/ErrorControllerImpl.java
index a0ca50d9..7706f6a2 100644
--- a/agate-webapp/src/main/java/org/obiba/agate/web/controller/ErrorControllerImpl.java
+++ b/agate-webapp/src/main/java/org/obiba/agate/web/controller/ErrorControllerImpl.java
@@ -1,7 +1,6 @@
package org.obiba.agate.web.controller;
import org.owasp.esapi.ESAPI;
-import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
@@ -10,7 +9,7 @@
import org.springframework.web.servlet.ModelAndView;
@Controller
-public class ErrorControllerImpl implements ErrorController {
+public class ErrorControllerImpl {
@GetMapping("/error")
public ModelAndView error(@RequestParam(value = "error", required = false, defaultValue = "404") String status, @RequestParam(defaultValue = "") String message) {
diff --git a/pom.xml b/pom.xml
index 01d324bf..798b474d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.5.7
+ 4.0.1
@@ -48,7 +48,7 @@
33.2.1-jre
1
3.1.3
- 12.0.10
+ 12.1.5
0.7
0.12.3
2.8.2
@@ -61,7 +61,7 @@
7.4
3.0.7
3.0.0
- 5.5.1
+ 5.6.2
8.4.0
8.4.0
10.0.2
@@ -222,12 +222,12 @@
- org.eclipse.jetty.ee10
+ org.eclipse.jetty.ee11
jetty-ee10-servlet
${jetty.version}
- org.eclipse.jetty.ee10
+ org.eclipse.jetty.ee11
jetty-ee10-servlets
${jetty.version}