From d52ece6cd76e9b0a1c30b114a103733a4e493001 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 26 Nov 2025 20:41:35 +0100 Subject: [PATCH 1/9] Add Flyway version property to pom.xml --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 116d40d..f85a6cd 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 23 5.23.0 5.18.0 + 11.1.0 From 3e2dab01b5e2bb0fdcfb57d814eafe404f1c1a32 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 27 Nov 2025 14:48:21 +0100 Subject: [PATCH 2/9] Add detailed logging and error handling to OIDC User Service --- .../de/avatic/lcc/config/SecurityConfig.java | 106 +++++++++++------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java index be0b2fe..079f381 100644 --- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java +++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java @@ -262,61 +262,81 @@ public class SecurityConfig { final OidcUserService delegate = new OidcUserService(); return (userRequest) -> { - OidcUser oidcUser = delegate.loadUser(userRequest); - Integer userId = null; + try { + log.info("=== OIDC User Service called ==="); - // Debug: Print all claims - log.debug("=== ID Token Claims ==="); - oidcUser.getIdToken().getClaims().forEach((key, value) -> - log.debug("{}: {}", key, value) - ); - log.debug("======================"); + OidcUser oidcUser = delegate.loadUser(userRequest); + log.info("OIDC User loaded successfully"); - Set mappedAuthorities = new HashSet<>(oidcUser.getAuthorities()); + Integer userId = null; - User user = null; + // Debug: Print all claims + log.debug("=== ID Token Claims ==="); + oidcUser.getIdToken().getClaims().forEach((key, value) -> + log.debug("{}: {}", key, value) + ); + log.debug("======================"); - String workdayId = oidcUser.getAttribute(workdayClaim); - String email = oidcUser.getAttribute(emailClaim); + Set mappedAuthorities = new HashSet<>(oidcUser.getAuthorities()); - String firstName = oidcUser.getAttribute(firstnameClaim); - String lastName = oidcUser.getAttribute(lastNameClaim); + User user = null; + String workdayId = oidcUser.getAttribute(workdayClaim); + String email = oidcUser.getAttribute(emailClaim); - if (identifyBy.equals("email") && email != null && !email.isEmpty()) { - log.debug("Fetch user by email {}", email); - user = userRepository.getByEmail(email); - } else if (identifyBy.equals("workday") && workdayId != null && !workdayId.isEmpty()) { - log.debug("Fetch user by workday id {}", workdayId); - user = userRepository.getByWorkdayId(workdayId).orElse(null); - } + String firstName = oidcUser.getAttribute(firstnameClaim); + String lastName = oidcUser.getAttribute(lastNameClaim); - if (user != null) { - userId = user.getId(); - } else { - if (email != null && firstName != null && lastName != null && (ignoreWorkdayClaim || workdayId != null)) { - var isFirstUser = userRepository.count() == 0; - user = LccOidcUser.createDatabaseUser(email, firstName, lastName, ignoreWorkdayClaim ? generateRandomWorkdayId() : workdayId, isFirstUser); - userId = userRepository.update(user); + log.info("Claims extracted - email: {}, workdayId: {}, firstName: {}, lastName: {}", + email, workdayId, firstName, lastName); - } else { - log.debug("Unable to create user {} / {}", email, workdayId); - mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_NONE")); + if (identifyBy.equals("email") && email != null && !email.isEmpty()) { + log.debug("Fetch user by email {}", email); + user = userRepository.getByEmail(email); + } else if (identifyBy.equals("workday") && workdayId != null && !workdayId.isEmpty()) { + log.debug("Fetch user by workday id {}", workdayId); + user = userRepository.getByWorkdayId(workdayId).orElse(null); } - } - if (user != null) { - user.getGroups().forEach(g -> log.debug("Local group: {}", g.getName())); - user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName().toUpperCase()))); - } + if (user != null) { + userId = user.getId(); + log.info("User found with ID: {}", userId); + } else { + if (email != null && firstName != null && lastName != null && (ignoreWorkdayClaim || workdayId != null)) { + log.info("Creating new user"); + var isFirstUser = userRepository.count() == 0; + user = LccOidcUser.createDatabaseUser(email, firstName, lastName, ignoreWorkdayClaim ? generateRandomWorkdayId() : workdayId, isFirstUser); + userId = userRepository.update(user); + log.info("New user created with ID: {}", userId); + } else { + log.warn("Unable to create user - email: {}, firstName: {}, lastName: {}, workdayId: {}", + email, firstName, lastName, workdayId); + mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_NONE")); + } + } - return new LccOidcUser( - mappedAuthorities, - oidcUser.getIdToken(), - oidcUser.getUserInfo(), - "preferred_username", - userId - ); + if (user != null) { + user.getGroups().forEach(g -> log.debug("Local group: {}", g.getName())); + user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName().toUpperCase()))); + } + + log.info("=== OIDC User Service completed successfully ==="); + + return new LccOidcUser( + mappedAuthorities, + oidcUser.getIdToken(), + oidcUser.getUserInfo(), + "preferred_username", + userId + ); + + } catch (Exception e) { + log.error("=== FATAL ERROR in oidcUserService ===", e); + log.error("Exception type: {}", e.getClass().getName()); + log.error("Exception message: {}", e.getMessage()); + log.error("Stack trace:", e); + throw e; // Re-throw to maintain Spring Security behavior + } }; } From e2e7f5f9e3e19f900f58d8f7cf251b4756d48c6d Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 27 Nov 2025 17:27:07 +0100 Subject: [PATCH 3/9] Update store to handle nested `users` and `groups` response objects --- src/frontend/src/store/group.js | 2 +- src/frontend/src/store/users.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/store/group.js b/src/frontend/src/store/group.js index c3c2e35..f5ddf1a 100644 --- a/src/frontend/src/store/group.js +++ b/src/frontend/src/store/group.js @@ -18,7 +18,7 @@ export const useGroupStore = defineStore('group', { this.loading = true; const url = `${config.backendUrl}/groups`; const resp = await performRequest(this,'GET', url, null); - this.groups = resp.data; + this.groups = resp.data.groups; this.loading = false; } } diff --git a/src/frontend/src/store/users.js b/src/frontend/src/store/users.js index 9adc32c..9d48c3d 100644 --- a/src/frontend/src/store/users.js +++ b/src/frontend/src/store/users.js @@ -56,8 +56,8 @@ export const useUsersStore = defineStore('users', { }; this.loading = false; - this.empty = data.length === 0; - this.users = data; + this.empty = data.users.length === 0; + this.users = data.users; } } From 75bff6886a49cf7bcb162de2e3b5939a451878c1 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 27 Nov 2025 17:26:50 +0100 Subject: [PATCH 4/9] Switch `listGroups` and `listUsers` endpoints to use root objects --- .../lcc/controller/users/GroupController.java | 10 +++++++--- .../lcc/controller/users/UserController.java | 11 ++++++----- .../avatic/lcc/dto/users/GroupContainerDTO.java | 16 ++++++++++++++++ .../avatic/lcc/dto/users/UserContainerDTO.java | 16 ++++++++++++++++ 4 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/dto/users/GroupContainerDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/users/UserContainerDTO.java diff --git a/src/main/java/de/avatic/lcc/controller/users/GroupController.java b/src/main/java/de/avatic/lcc/controller/users/GroupController.java index 314ff35..c9af699 100644 --- a/src/main/java/de/avatic/lcc/controller/users/GroupController.java +++ b/src/main/java/de/avatic/lcc/controller/users/GroupController.java @@ -1,5 +1,6 @@ package de.avatic.lcc.controller.users; +import de.avatic.lcc.dto.users.GroupContainerDTO; import de.avatic.lcc.dto.users.GroupDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.service.users.GroupService; @@ -34,16 +35,19 @@ public class GroupController { */ @GetMapping({"/", ""}) @PreAuthorize("hasAnyRole('RIGHT-MANAGEMENT', 'SERVICE')") - public ResponseEntity> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit, - @RequestParam(defaultValue = "1") @Min(1) int page) { + public ResponseEntity listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit, + @RequestParam(defaultValue = "1") @Min(1) int page) { SearchQueryResult groups = groupService.listGroups(page, limit); + var container = new GroupContainerDTO(); + container.setGroups(groups.toList()); + return ResponseEntity.ok() .header("X-Total-Count", String.valueOf(groups.getTotalElements())) .header("X-Page-Count", String.valueOf(groups.getTotalPages())) .header("X-Current-Page", String.valueOf(page)) - .body(groups.toList()); + .body(container); } } diff --git a/src/main/java/de/avatic/lcc/controller/users/UserController.java b/src/main/java/de/avatic/lcc/controller/users/UserController.java index 674f98c..6db709d 100644 --- a/src/main/java/de/avatic/lcc/controller/users/UserController.java +++ b/src/main/java/de/avatic/lcc/controller/users/UserController.java @@ -1,19 +1,17 @@ package de.avatic.lcc.controller.users; +import de.avatic.lcc.dto.users.UserContainerDTO; import de.avatic.lcc.dto.users.UserDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.service.users.UserService; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import org.hibernate.validator.constraints.Length; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; import java.util.Optional; /** @@ -41,17 +39,20 @@ public class UserController { */ @GetMapping({"/", ""}) @PreAuthorize("hasRole('RIGHT-MANAGEMENT')") - public ResponseEntity> listUsers( + public ResponseEntity listUsers( @RequestParam(defaultValue = "20") @Min(1) int limit, @RequestParam(defaultValue = "1") @Min(1) int page) { SearchQueryResult users = userService.listUsers(page, limit); + var container = new UserContainerDTO(); + container.setUsers( users.toList()); + return ResponseEntity.ok() .header("X-Total-Count", String.valueOf(users.getTotalElements())) .header("X-Page-Count", String.valueOf(users.getTotalPages())) .header("X-Current-Page", String.valueOf(page)) - .body(users.toList()); + .body(container); } diff --git a/src/main/java/de/avatic/lcc/dto/users/GroupContainerDTO.java b/src/main/java/de/avatic/lcc/dto/users/GroupContainerDTO.java new file mode 100644 index 0000000..73db58e --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/users/GroupContainerDTO.java @@ -0,0 +1,16 @@ +package de.avatic.lcc.dto.users; + +import java.util.List; + +public class GroupContainerDTO { + + private List groups; + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } +} diff --git a/src/main/java/de/avatic/lcc/dto/users/UserContainerDTO.java b/src/main/java/de/avatic/lcc/dto/users/UserContainerDTO.java new file mode 100644 index 0000000..f8c694f --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/users/UserContainerDTO.java @@ -0,0 +1,16 @@ +package de.avatic.lcc.dto.users; + +import java.util.List; + +public class UserContainerDTO { + + private List users; + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} From c3ea8d14a1a37628639f743941261ffb97bf6cd0 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 27 Nov 2025 17:26:20 +0100 Subject: [PATCH 5/9] Refactored CI workflows: removed auto-tag.yml, merged tagging logic into build.yml. Added app version management in Dockerfile and pom.xml. --- .gitea/workflows/build.yml | 85 +++++++++++++++++++++++++++++++------- dockerfile | 12 +++++- pom.xml | 5 +++ 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 65f49b5..56230ba 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,12 +1,10 @@ -name: Build and Push Docker Image +name: Build, Tag and Push Docker Image on: push: branches: - main - dev - tags: - - 'v*' pull_request: branches: - main @@ -18,6 +16,61 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version and create tag (main only) + id: version + run: | + if [ "${{ gitea.ref_name }}" = "main" ]; then + # Hole letzten Tag + LATEST_TAG=$(git tag -l "v*" --sort=-v:refname | head -n 1) + + if [ -z "$LATEST_TAG" ]; then + NEW_TAG="v1.0.0" + BUMP="minor" + else + VERSION=${LATEST_TAG#v} + MAJOR=$(echo $VERSION | cut -d. -f1) + MINOR=$(echo $VERSION | cut -d. -f2) + PATCH=$(echo $VERSION | cut -d. -f3) + + COMMITS=$(git log ${LATEST_TAG}..HEAD --pretty=format:"%s" 2>/dev/null || echo "") + + if [ -z "$COMMITS" ]; then + echo "Keine neuen Commits seit ${LATEST_TAG}" + echo "skip=true" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> $GITHUB_OUTPUT + exit 0 + fi + + if echo "$COMMITS" | grep -qiE "^BREAKING CHANGE:|^[^:]+!:|breaking:|major:"; then + MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0; BUMP="major" + elif echo "$COMMITS" | grep -qiE "^feat:|^feature:|minor:"; then + MINOR=$((MINOR + 1)); PATCH=0; BUMP="minor" + else + PATCH=$((PATCH + 1)); BUMP="patch" + fi + + NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" + fi + + # Prüfe ob Tag bereits existiert + if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then + echo "Tag ${NEW_TAG} existiert bereits" + echo "skip=true" >> $GITHUB_OUTPUT + echo "version=${NEW_TAG#v}" >> $GITHUB_OUTPUT + else + echo "Erstelle neues Tag: ${NEW_TAG} (${BUMP})" + echo "new_tag=${NEW_TAG}" >> $GITHUB_OUTPUT + echo "version=${NEW_TAG#v}" >> $GITHUB_OUTPUT + echo "skip=false" >> $GITHUB_OUTPUT + fi + else + # Dev branch: snapshot version + echo "version=dev-${{ gitea.sha }}" >> $GITHUB_OUTPUT + echo "skip=true" >> $GITHUB_OUTPUT + fi - name: Login to Gitea Container Registry run: | @@ -27,8 +80,8 @@ jobs: id: tags run: | IMAGE_BASE="git.avatic.de/lcc_public/lcc" - TAGS="" - + VERSION="${{ steps.version.outputs.version }}" + TAGS="-t ${IMAGE_BASE}:${VERSION}" TAGS="${TAGS} -t ${IMAGE_BASE}:${{ gitea.sha }}" if [ "${{ gitea.ref_name }}" = "main" ]; then @@ -38,11 +91,6 @@ jobs: TAGS="${TAGS} -t ${IMAGE_BASE}:dev" fi - if [[ "${{ gitea.ref }}" == refs/tags/* ]]; then - VERSION="${{ gitea.ref_name }}" - TAGS="${TAGS} -t ${IMAGE_BASE}:${VERSION}" - fi - echo "tags=${TAGS}" >> $GITHUB_OUTPUT echo "image_base=${IMAGE_BASE}" >> $GITHUB_OUTPUT @@ -50,13 +98,16 @@ jobs: run: | docker build \ --build-arg BUILDKIT_INLINE_CACHE=1 \ + --build-arg APP_VERSION=${{ steps.version.outputs.version }} \ ${{ steps.tags.outputs.tags }} \ . - name: Push Docker images run: | IMAGE_BASE="${{ steps.tags.outputs.image_base }}" + VERSION="${{ steps.version.outputs.version }}" + docker push ${IMAGE_BASE}:${VERSION} docker push ${IMAGE_BASE}:${{ gitea.sha }} if [ "${{ gitea.ref_name }}" = "main" ]; then @@ -65,13 +116,17 @@ jobs: elif [ "${{ gitea.ref_name }}" = "dev" ]; then docker push ${IMAGE_BASE}:dev fi - - if [[ "${{ gitea.ref }}" == refs/tags/* ]]; then - docker push ${IMAGE_BASE}:${{ gitea.ref_name }} - fi + + - name: Create and push git tag + if: gitea.ref_name == 'main' && steps.version.outputs.skip != 'true' && steps.version.outputs.new_tag != '' + run: | + git config user.name "Gitea Actions" + git config user.email "actions@gitea.local" + git tag -a ${{ steps.version.outputs.new_tag }} -m "Release ${{ steps.version.outputs.new_tag }}" + git push origin ${{ steps.version.outputs.new_tag }} - name: Deploy to Docker - if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'dev') + if: gitea.event_name == 'push' && (gitea.ref_name == 'main' || gitea.ref_name == 'dev') run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key diff --git a/dockerfile b/dockerfile index d077963..cb88806 100644 --- a/dockerfile +++ b/dockerfile @@ -7,14 +7,22 @@ RUN npm run build FROM maven:3.9-eclipse-temurin-23 AS backend-build WORKDIR /app + +ARG APP_VERSION=0.0.1-SNAPSHOT + COPY pom.xml ./ COPY src ./src -# copy frontend COPY --from=frontend-build /app/frontend/dist ./src/main/resources/static -RUN mvn clean package -DskipTests +RUN mvn versions:set -DnewVersion=${APP_VERSION} -DgenerateBackupPoms=false && \ + mvn clean package -DskipTests FROM eclipse-temurin:23-jre-alpine WORKDIR /app + +ARG APP_VERSION=0.0.1-SNAPSHOT +ENV APP_VERSION=${APP_VERSION} +LABEL version="${APP_VERSION}" + COPY --from=backend-build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index f85a6cd..4d1cfbd 100644 --- a/pom.xml +++ b/pom.xml @@ -209,6 +209,11 @@ + + org.codehaus.mojo + versions-maven-plugin + 2.18.0 + org.jvnet.jaxb jaxb-maven-plugin From c85cadeeda75efffa5ba76256d3028c0256863f4 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Nov 2025 13:01:59 +0100 Subject: [PATCH 6/9] Add logging improvements, shutdown listener, and request filter - Introduced `ShutdownListener` to log application shutdown details and thread stack dump. - Added `RequestLoggerFilter` to log incoming HTTP requests and responses. - Enhanced `LccApplication` with memory usage logging at start and end. - Replaced `System.out` calls with SLF4J logging in `DevUserEmulationFilter`. - Updated `pom.xml` to mark devtools dependency as optional. --- pom.xml | 6 +-- .../java/de/avatic/lcc/LccApplication.java | 19 +++++-- .../avatic/lcc/config/ShutdownListener.java | 20 +++++++ .../config/filter/DevUserEmulationFilter.java | 12 +++-- .../config/filter/RequestLoggerFilter.java | 54 +++++++++++++++++++ 5 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/config/ShutdownListener.java create mode 100644 src/main/java/de/avatic/lcc/config/filter/RequestLoggerFilter.java diff --git a/pom.xml b/pom.xml index 4d1cfbd..0eeb7f2 100644 --- a/pom.xml +++ b/pom.xml @@ -33,11 +33,6 @@ 11.1.0 - - - org.springframework.boot - spring-boot-starter-batch - org.springframework.boot spring-boot-starter-jdbc @@ -139,6 +134,7 @@ org.springframework.boot spring-boot-devtools runtime + true org.mockito diff --git a/src/main/java/de/avatic/lcc/LccApplication.java b/src/main/java/de/avatic/lcc/LccApplication.java index c6fd3cb..00195e9 100644 --- a/src/main/java/de/avatic/lcc/LccApplication.java +++ b/src/main/java/de/avatic/lcc/LccApplication.java @@ -1,13 +1,26 @@ package de.avatic.lcc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LccApplication { - public static void main(String[] args) { - SpringApplication.run(LccApplication.class, args); - } + static Logger logger = LoggerFactory.getLogger(LccApplication.class); + + public static void main(String[] args) { + + Runtime runtime = Runtime.getRuntime(); + long usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024; + logger.info("LCC Start - Memory: {} used, {} total, {} free, {} max ", usedMemory, runtime.totalMemory() / 1024 / 1024, runtime.freeMemory() / 1024 / 1024, runtime.maxMemory() / 1024 / 1024); + + SpringApplication.run(LccApplication.class, args); + + usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024; + logger.info("LCC End - Memory: {} used, {} total, {} free, {} max ", usedMemory, runtime.totalMemory() / 1024 / 1024, runtime.freeMemory() / 1024 / 1024, runtime.maxMemory() / 1024 / 1024); + + } } diff --git a/src/main/java/de/avatic/lcc/config/ShutdownListener.java b/src/main/java/de/avatic/lcc/config/ShutdownListener.java new file mode 100644 index 0000000..f2f35b0 --- /dev/null +++ b/src/main/java/de/avatic/lcc/config/ShutdownListener.java @@ -0,0 +1,20 @@ +package de.avatic.lcc.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class ShutdownListener { + + private static final Logger log = LoggerFactory.getLogger(ShutdownListener.class); + + @EventListener + public void onShutdown(ContextClosedEvent event) { + log.error("Application shutdown. Context: {}, Thread: {}", event.getApplicationContext(), Thread.currentThread().getName()); + log.error("Thread stack dump:"); + Thread.dumpStack(); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/config/filter/DevUserEmulationFilter.java b/src/main/java/de/avatic/lcc/config/filter/DevUserEmulationFilter.java index c203e9c..0f6b5b0 100644 --- a/src/main/java/de/avatic/lcc/config/filter/DevUserEmulationFilter.java +++ b/src/main/java/de/avatic/lcc/config/filter/DevUserEmulationFilter.java @@ -10,6 +10,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -32,6 +34,8 @@ import java.util.Set; @Profile("dev | test") public class DevUserEmulationFilter extends OncePerRequestFilter { + private final Logger log = LoggerFactory.getLogger(DevUserEmulationFilter.class); + private static final String DEV_USER_ID_SESSION_KEY = "dev.emulated.user.id"; private final UserRepository userRepository; @@ -48,17 +52,17 @@ public class DevUserEmulationFilter extends OncePerRequestFilter { Integer emulatedUserId = (Integer) session.getAttribute(DEV_USER_ID_SESSION_KEY); // Add logging to debug - System.out.println("DevUserEmulationFilter - Session ID: " + session.getId()); - System.out.println("DevUserEmulationFilter - Emulated User ID: " + emulatedUserId); + log.debug("DevUserEmulationFilter - Session ID: " + session.getId()); + log.debug("DevUserEmulationFilter - Emulated User ID: " + emulatedUserId); if(emulatedUserId != null) { User user = userRepository.getById(emulatedUserId); if (user != null) { setEmulatedUser(user); - System.out.println("DevUserEmulationFilter - Set user: " + user.getEmail()); + log.debug("DevUserEmulationFilter - Set user: " + user.getEmail()); } } else { - System.out.println("DevUserEmulationFilter - " + request.getRequestURI() + " - No emulated user set"); + log.debug("DevUserEmulationFilter - " + request.getRequestURI() + " - No emulated user set"); } filterChain.doFilter(request, response); diff --git a/src/main/java/de/avatic/lcc/config/filter/RequestLoggerFilter.java b/src/main/java/de/avatic/lcc/config/filter/RequestLoggerFilter.java new file mode 100644 index 0000000..0613e79 --- /dev/null +++ b/src/main/java/de/avatic/lcc/config/filter/RequestLoggerFilter.java @@ -0,0 +1,54 @@ +package de.avatic.lcc.config.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RequestLoggerFilter implements Filter { + + private static final Logger log = LoggerFactory.getLogger(RequestLoggerFilter.class); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + long startTime = System.currentTimeMillis(); + + log.info(">>> INCOMING REQUEST: {} {} from {} - Headers: {}", + httpRequest.getMethod(), + httpRequest.getRequestURI(), + httpRequest.getRemoteAddr(), + getHeadersAsString(httpRequest)); + + try { + chain.doFilter(request, response); + } finally { + long duration = System.currentTimeMillis() - startTime; + log.info("<<< RESPONSE: {} {} -> Status: {} ({}ms)", + httpRequest.getMethod(), + httpRequest.getRequestURI(), + httpResponse.getStatus(), + duration); + } + } + + private String getHeadersAsString(HttpServletRequest request) { + StringBuilder headers = new StringBuilder(); + var headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.append(headerName).append("=") + .append(request.getHeader(headerName)).append("; "); + } + return headers.toString(); + } +} From 575b5fad1cb3934e67a2e96f785db9f0c323f8ea Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Nov 2025 13:24:32 +0100 Subject: [PATCH 7/9] Remove unused annotations and imports from model classes --- .../avatic/lcc/model/db/nodes/OutboundCountryMapping.java | 2 -- src/main/java/de/avatic/lcc/model/db/premises/Premise.java | 2 -- src/main/java/de/avatic/lcc/model/taric/Geo.java | 7 ------- src/main/java/de/avatic/lcc/model/taric/GeoDesc.java | 5 ----- src/main/java/de/avatic/lcc/model/taric/LegalBase.java | 4 ---- .../java/de/avatic/lcc/model/taric/MeasureExclusion.java | 5 ----- .../java/de/avatic/lcc/model/taric/MeasureSeriesDesc.java | 4 ---- src/main/java/de/avatic/lcc/model/taric/MonetaryUnit.java | 6 ------ src/main/java/de/avatic/lcc/model/taric/Nomenclature.java | 7 ------- src/main/java/de/avatic/lcc/model/taric/Unit.java | 7 ------- 10 files changed, 49 deletions(-) diff --git a/src/main/java/de/avatic/lcc/model/db/nodes/OutboundCountryMapping.java b/src/main/java/de/avatic/lcc/model/db/nodes/OutboundCountryMapping.java index be90c00..7934a62 100644 --- a/src/main/java/de/avatic/lcc/model/db/nodes/OutboundCountryMapping.java +++ b/src/main/java/de/avatic/lcc/model/db/nodes/OutboundCountryMapping.java @@ -2,12 +2,10 @@ package de.avatic.lcc.model.db.nodes; import de.avatic.lcc.model.db.country.Country; import jakarta.validation.constraints.NotNull; -import org.springframework.data.annotation.Id; public class OutboundCountryMapping { - @Id private Integer id; @NotNull diff --git a/src/main/java/de/avatic/lcc/model/db/premises/Premise.java b/src/main/java/de/avatic/lcc/model/db/premises/Premise.java index f3eb75e..472d19c 100644 --- a/src/main/java/de/avatic/lcc/model/db/premises/Premise.java +++ b/src/main/java/de/avatic/lcc/model/db/premises/Premise.java @@ -7,7 +7,6 @@ import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import jdk.jfr.Unsigned; -import org.springframework.data.annotation.Id; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -15,7 +14,6 @@ import java.time.LocalDateTime; public class Premise { - @Id private Integer id; private LocalDateTime createdAt; diff --git a/src/main/java/de/avatic/lcc/model/taric/Geo.java b/src/main/java/de/avatic/lcc/model/taric/Geo.java index 5958f83..20b4329 100644 --- a/src/main/java/de/avatic/lcc/model/taric/Geo.java +++ b/src/main/java/de/avatic/lcc/model/taric/Geo.java @@ -1,12 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - -import java.util.Set; - public class Geo { private String iso3166Code; diff --git a/src/main/java/de/avatic/lcc/model/taric/GeoDesc.java b/src/main/java/de/avatic/lcc/model/taric/GeoDesc.java index 7170b91..0054a06 100644 --- a/src/main/java/de/avatic/lcc/model/taric/GeoDesc.java +++ b/src/main/java/de/avatic/lcc/model/taric/GeoDesc.java @@ -1,10 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; public class GeoDesc { diff --git a/src/main/java/de/avatic/lcc/model/taric/LegalBase.java b/src/main/java/de/avatic/lcc/model/taric/LegalBase.java index cc63ffd..534956c 100644 --- a/src/main/java/de/avatic/lcc/model/taric/LegalBase.java +++ b/src/main/java/de/avatic/lcc/model/taric/LegalBase.java @@ -1,9 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; public class LegalBase { diff --git a/src/main/java/de/avatic/lcc/model/taric/MeasureExclusion.java b/src/main/java/de/avatic/lcc/model/taric/MeasureExclusion.java index 02cb181..b13c3d7 100644 --- a/src/main/java/de/avatic/lcc/model/taric/MeasureExclusion.java +++ b/src/main/java/de/avatic/lcc/model/taric/MeasureExclusion.java @@ -1,10 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.NotNull; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; public class MeasureExclusion { diff --git a/src/main/java/de/avatic/lcc/model/taric/MeasureSeriesDesc.java b/src/main/java/de/avatic/lcc/model/taric/MeasureSeriesDesc.java index c37d1c4..ad49408 100644 --- a/src/main/java/de/avatic/lcc/model/taric/MeasureSeriesDesc.java +++ b/src/main/java/de/avatic/lcc/model/taric/MeasureSeriesDesc.java @@ -1,9 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; public class MeasureSeriesDesc { diff --git a/src/main/java/de/avatic/lcc/model/taric/MonetaryUnit.java b/src/main/java/de/avatic/lcc/model/taric/MonetaryUnit.java index ce1ac37..3189b92 100644 --- a/src/main/java/de/avatic/lcc/model/taric/MonetaryUnit.java +++ b/src/main/java/de/avatic/lcc/model/taric/MonetaryUnit.java @@ -1,11 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - -import java.util.Set; - public class MonetaryUnit { private String monetaryUnitCode; diff --git a/src/main/java/de/avatic/lcc/model/taric/Nomenclature.java b/src/main/java/de/avatic/lcc/model/taric/Nomenclature.java index 1534896..9b157e3 100644 --- a/src/main/java/de/avatic/lcc/model/taric/Nomenclature.java +++ b/src/main/java/de/avatic/lcc/model/taric/Nomenclature.java @@ -1,13 +1,6 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; -import java.util.List; import java.util.Set; /** diff --git a/src/main/java/de/avatic/lcc/model/taric/Unit.java b/src/main/java/de/avatic/lcc/model/taric/Unit.java index cbf2e2f..11a09f9 100644 --- a/src/main/java/de/avatic/lcc/model/taric/Unit.java +++ b/src/main/java/de/avatic/lcc/model/taric/Unit.java @@ -1,12 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - -import java.util.Set; - public class Unit { private String unitCode; From 15ff5fa9faacdd690d6da6078472cedad12d9869 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Nov 2025 13:31:56 +0100 Subject: [PATCH 8/9] Remove unused annotations and imports from taric model classes --- src/main/java/de/avatic/lcc/model/taric/Footnote.java | 7 ------- src/main/java/de/avatic/lcc/model/taric/FootnotesDesc.java | 4 ---- .../java/de/avatic/lcc/model/taric/MeasureActionDesc.java | 2 -- .../de/avatic/lcc/model/taric/NomenclatureFootnote.java | 5 ----- 4 files changed, 18 deletions(-) diff --git a/src/main/java/de/avatic/lcc/model/taric/Footnote.java b/src/main/java/de/avatic/lcc/model/taric/Footnote.java index 5446ada..b840f1c 100644 --- a/src/main/java/de/avatic/lcc/model/taric/Footnote.java +++ b/src/main/java/de/avatic/lcc/model/taric/Footnote.java @@ -1,13 +1,6 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; -import org.springframework.data.annotation.Id; - import java.time.LocalDate; -import java.util.Set; /** * Represents a footnote reference diff --git a/src/main/java/de/avatic/lcc/model/taric/FootnotesDesc.java b/src/main/java/de/avatic/lcc/model/taric/FootnotesDesc.java index da19d84..91cd315 100644 --- a/src/main/java/de/avatic/lcc/model/taric/FootnotesDesc.java +++ b/src/main/java/de/avatic/lcc/model/taric/FootnotesDesc.java @@ -1,9 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.Size; - import java.time.LocalDate; public class FootnotesDesc { diff --git a/src/main/java/de/avatic/lcc/model/taric/MeasureActionDesc.java b/src/main/java/de/avatic/lcc/model/taric/MeasureActionDesc.java index 3ed421f..bab5375 100644 --- a/src/main/java/de/avatic/lcc/model/taric/MeasureActionDesc.java +++ b/src/main/java/de/avatic/lcc/model/taric/MeasureActionDesc.java @@ -1,7 +1,5 @@ package de.avatic.lcc.model.taric; -import jakarta.validation.constraints.Size; - import java.time.LocalDate; public class MeasureActionDesc { diff --git a/src/main/java/de/avatic/lcc/model/taric/NomenclatureFootnote.java b/src/main/java/de/avatic/lcc/model/taric/NomenclatureFootnote.java index 19997e7..874a65d 100644 --- a/src/main/java/de/avatic/lcc/model/taric/NomenclatureFootnote.java +++ b/src/main/java/de/avatic/lcc/model/taric/NomenclatureFootnote.java @@ -1,10 +1,5 @@ package de.avatic.lcc.model.taric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.validation.constraints.NotNull; - /** * Represents a footnote reference for a nomenclature */ From eb45c5b17b0552bc7a154c32174e06f12f1c4793 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Nov 2025 13:44:27 +0100 Subject: [PATCH 9/9] Add memory usage logging to `ShutdownListener` during application shutdown --- src/main/java/de/avatic/lcc/config/ShutdownListener.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/de/avatic/lcc/config/ShutdownListener.java b/src/main/java/de/avatic/lcc/config/ShutdownListener.java index f2f35b0..efc907d 100644 --- a/src/main/java/de/avatic/lcc/config/ShutdownListener.java +++ b/src/main/java/de/avatic/lcc/config/ShutdownListener.java @@ -14,6 +14,14 @@ public class ShutdownListener { @EventListener public void onShutdown(ContextClosedEvent event) { log.error("Application shutdown. Context: {}, Thread: {}", event.getApplicationContext(), Thread.currentThread().getName()); + + Runtime runtime = Runtime.getRuntime(); + long usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024; + log.info("Memory: {} used, {} total, {} free, {} max ", usedMemory, runtime.totalMemory() / 1024 / 1024, runtime.freeMemory() / 1024 / 1024, runtime.maxMemory() / 1024 / 1024); + + + + log.error("Application shutdown. Context: {}, Thread: {}", event.getApplicationContext(), Thread.currentThread()); log.error("Thread stack dump:"); Thread.dumpStack(); }